活跃性
死锁
形成
死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,由于线程被无限期地阻塞,因此程序不可能正常终止
Java 死锁产生的四个必要条件:
- 互斥条件,即当资源被一个线程使用(占有)时,别的线程不能使用
- 不可剥夺条件,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
- 请求和保持条件,即当资源请求者在请求其他的资源的同时保持对原有资源的占有
- 循环等待条件,即存在一个等待循环队列:p1 要 p2 的资源,p2 要 p1 的资源,形成了一个等待环路
四个条件都成立的时候,便形成死锁。死锁情况下打破上述任何一个条件,便可让死锁消失
public class Dead { public static Object resources1 = new Object(); public static Object resources2 = new Object(); public static void main(String[] args) { new Thread(() -> { // 线程1:占用资源1 ,请求资源2 synchronized(resources1){ System.out.println("线程1已经占用了资源1,开始请求资源2"); Thread.sleep(2000);//休息两秒,防止线程1直接运行完成。 //2秒内线程2肯定可以锁住资源2 synchronized (resources2){ System.out.println("线程1已经占用了资源2"); } }).start(); new Thread(() -> { // 线程2:占用资源2 ,请求资源1 synchronized(resources2){ System.out.println("线程2已经占用了资源2,开始请求资源1"); Thread.sleep(2000); synchronized (resources1){ System.out.println("线程2已经占用了资源1"); } }} }).start(); } }
定位
定位死锁的方法:
-
使用 jps 定位进程 id,再用
jstack id
定位死锁,找到死锁的线程去查看源码,解决优化"Thread-1" #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting formonitor entry [0x000000001f54f000] java.lang.Thread.State: BLOCKED (on object monitor) #省略 "Thread-1" #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting for monitor entry [0x000000001f54f000] java.lang.Thread.State: BLOCKED (on object monitor) #省略 Found one Java-level deadlock: =================================================== "Thread-1": waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28) - waiting to lock <0x000000076b5bf1c0> (a java.lang.Object) - locked <0x000000076b5bf1d0> (a java.lang.Object) at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Thread-0": at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15) - waiting to lock <0x000000076b5bf1d0> (a java.lang.Object) - locked <0x000000076b5bf1c0> (a java.lang.Object) at thread.TestDeadLock$$Lambda$1/495053715
-
Linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用
top -Hp 进程id
来定位是哪个线程,最后再用 jstack 的输出来看各个线程栈 -
避免死锁:避免死锁要注意加锁顺序
-
可以使用 jconsole 工具,在
jdk\bin
目录下
活锁
活锁:指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程
两个线程互相改变对方的结束条件,最后谁也无法结束:
class TestLiveLock { static volatile int count = 10; static final Object lock = new Object(); public static void main(String[] args) { new Thread(() -> { // 期望减到 0 退出循环 while (count > 0) { Thread.sleep(200); count--; System.out.println("线程一count:" + count); } }, "t1").start(); new Thread(() -> { // 期望超过 20 退出循环 while (count < 20) { Thread.sleep(200); count++; System.out.println("线程二count:"+ count); } }, "t2").start(); } }
饥饿
饥饿:一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束
wait-ify
基本使用
需要获取对象锁后才可以调用 锁对象.wait()
,notify 随机唤醒一个线程,notifyAll 唤醒所有线程去竞争 CPU
Object 类 API:
public final void notify():唤醒正在等待对象监视器的单个线程。 public final void notifyAll():唤醒正在等待对象监视器的所有线程。 public final void wait():导致当前线程等待,直到另一个线程调用该对象的notify()方法或 notifyAll()方法。 public final native void wait(long timeout):有时限的等待, 到n毫秒后结束等待,或是被唤醒
说明:wait 是挂起线程,需要唤醒的都是挂起操作,阻塞线程可以自己去争抢锁,挂起的线程需要唤醒后去争抢锁
对比 sleep():
- 原理不同:sleep() 方法是属于 Thread 类,是线程用来控制自身流程的,使此线程暂停执行一段时间而把执行机会让给其他线程;wait() 方法属于 Object 类,用于线程间通信
- 对锁的处理机制不同:调用 sleep() 方法的过程中,线程不会释放对象锁,当调用 wait() 方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池(不释放锁其他线程怎么抢占到锁执行唤醒操作),但是都会释放 CPU
- 使用区域不同:wait() 方法必须放在**同步控制方法和同步代码块(先获取锁)**中使用,sleep() 方法则可以放在任何地方使用
底层原理:
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,唤醒后并不意味者立刻获得锁,需要进入 EntryList 重新竞争
代码优化
虚假唤醒:notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程
解决方法:采用 notifyAll
notifyAll 仅解决某个线程的唤醒问题,使用 if + wait 判断仅有一次机会,一旦条件不成立,无法重新判断
解决方法:用 while + wait,当条件不成立,再次 wait
@Slf4j(topic = "c.demo") public class demo { static final Object room = new Object(); static boolean hasCigarette = false; //有没有烟 static boolean hasTakeout = false; public static void main(String[] args) throws InterruptedException { new Thread(() -> { synchronized (room) { log.debug("有烟没?[{}]", hasCigarette); while (!hasCigarette) {//while防止虚假唤醒 log.debug("没烟,先歇会!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有烟没?[{}]", hasCigarette); if (hasCigarette) { log.debug("可以开始干活了"); } else { log.debug("没干成活..."); } } }, "小南").start(); new Thread(() -> { synchronized (room) { Thread thread = Thread.currentThread(); log.debug("外卖送到没?[{}]", hasTakeout); if (!hasTakeout) { log.debug("没外卖,先歇会!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("外卖送到没?[{}]", hasTakeout); if (hasTakeout) { log.debug("可以开始干活了"); } else { log.debug("没干成活..."); } } }, "小女").start(); Thread.sleep(1000); new Thread(() -> { // 这里能不能加 synchronized (room)? synchronized (room) { hasTakeout = true; //log.debug("烟到了噢!"); log.debug("外卖到了噢!"); room.notifyAll(); } }, "送外卖的").start(); } }
park-un
LockSupport 是用来创建锁和其他同步类的线程原语
LockSupport 类方法:
LockSupport.park()
:暂停当前线程,挂起原语LockSupport.unpark(暂停的线程对象)
:恢复某个线程的运行
public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("start..."); //1 Thread.sleep(1000);// Thread.sleep(3000) // 先 park 再 unpark 和先 unpark 再 park 效果一样,都会直接恢复线程的运行 System.out.println("park..."); //2 LockSupport.park(); System.out.println("resume...");//4 },"t1"); t1.start(); Thread.sleep(2000); System.out.println("unpark..."); //3 LockSupport.unpark(t1); }
LockSupport 出现就是为了增强 wait & notify 的功能:
- wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park、unpark 不需要
- park & unpark 以线程为单位来阻塞和唤醒线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify。类比生产消费,先消费发现有产品就消费,没有就等待;先生产就直接产生商品,然后线程直接消费
- wait 会释放锁资源进入等待队列,park 不会释放锁资源,只负责阻塞当前线程,会释放 CPU
原理:类似生产者消费者
- 先 park:
- 当前线程调用 Unsafe.park() 方法
- 检查 _counter ,本情况为 0,这时获得 _mutex 互斥锁
- 线程进入 _cond 条件变量挂起
- 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
- 唤醒 _cond 条件变量中的 Thread_0,Thread_0 恢复运行,设置 _counter 为 0
-
先 unpark:
- 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
- 当前线程调用 Unsafe.park() 方法
- 检查 _counter ,本情况为 1,这时线程无需挂起,继续运行,设置 _counter 为 0
安全分析
成员变量和静态变量:
- 如果它们没有共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够改变,分两种情况:
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全问题
局部变量:
- 局部变量是线程安全的
- 局部变量引用的对象不一定线程安全(逃逸分析):
- 如果该对象没有逃离方法的作用访问,它是线程安全的(每一个方法有一个栈帧)
- 如果该对象逃离方法的作用范围,需要考虑线程安全问题(暴露引用)
常见线程安全类:String、Integer、StringBuffer、Random、Vector、Hashtable、java.util.concurrent 包
-
线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的
-
每个方法是原子的,但多个方法的组合不是原子的,只能保证调用的方法内部安全:
Hashtable table = new Hashtable(); // 线程1,线程2 if(table.get("key") == null) { table.put("key", value); }
无状态类线程安全,就是没有成员变量的类
不可变类线程安全:String、Integer 等都是不可变类,内部的状态不可以改变,所以方法是线程安全
-
replace 等方法底层是新建一个对象,复制过去
Map<String,Object> map = new HashMap<>(); // 线程不安全 String S1 = "..."; // 线程安全 final String S2 = "..."; // 线程安全 Date D1 = new Date(); // 线程不安全 final Date D2 = new Date(); // 线程不安全,final让D2引用的对象不能变,但对象的内容可以变
抽象方法如果有参数,被重写后行为不确定可能造成线程不安全,被称之为外星方法:public abstract foo(Student s);
同步模式
保护性暂停
单任务版
Guarded Suspension,用在一个线程等待另一个线程的执行结果
- 有一个结果需要从一个线程传递到另一个线程,让它们关联同一个 GuardedObject
- 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
- JDK 中,join 的实现、Future 的实现,采用的就是此模式
public static void main(String[] args) { GuardedObject object = new GuardedObjectV2(); new Thread(() -> { sleep(1); object.complete(Arrays.asList("a", "b", "c")); }).start(); Object response = object.get(2500); if (response != null) { log.debug("get response: [{}] lines", ((List<String>) response).size()); } else { log.debug("can't get response"); } } class GuardedObject { private Object response; private final Object lock = new Object(); //获取结果 //timeout :最大等待时间 public Object get(long millis) { synchronized (lock) { // 1) 记录最初时间 long begin = System.currentTimeMillis(); // 2) 已经经历的时间 long timePassed = 0; while (response == null) { // 4) 假设 millis 是 1000,结果在 400 时唤醒了,那么还有 600 要等 long waitTime = millis - timePassed; log.debug("waitTime: {}", waitTime); //经历时间超过最大等待时间退出循环 if (waitTime <= 0) { log.debug("break..."); break; } try { lock.wait(waitTime); } catch (InterruptedException e) { e.printStackTrace(); } // 3) 如果提前被唤醒,这时已经经历的时间假设为 400 timePassed = System.currentTimeMillis() - begin; log.debug("timePassed: {}, object is null {}", timePassed, response == null); } return response; } } //产生结果 public void complete(Object response) { synchronized (lock) { // 条件满足,通知等待线程 this.response = response; log.debug("notify..."); lock.notifyAll(); } } }
多任务版
多任务版保护性暂停:
public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 3; i++) { new People().start(); } Thread.sleep(1000); for (Integer id : Mailboxes.getIds()) { new Postman(id, id + "号快递到了").start(); } } @Slf4j(topic = "c.People") class People extends Thread{ @Override public void run() { // 收信 GuardedObject guardedObject = Mailboxes.createGuardedObject(); log.debug("开始收信i d:{}", guardedObject.getId()); Object mail = guardedObject.get(5000); log.debug("收到信id:{},内容:{}", guardedObject.getId(),mail); } } class Postman extends Thread{ private int id; private String mail; //构造方法 @Override public void run() { GuardedObject guardedObject = Mailboxes.getGuardedObject(id); log.debug("开始送信i d:{},内容:{}", guardedObject.getId(),mail); guardedObject.complete(mail); } } class Mailboxes { private static Map<Integer, GuardedObject> boxes = new Hashtable<>(); private static int id = 1; //产生唯一的id private static synchronized int generateId() { return id++; } public static GuardedObject getGuardedObject(int id) { return boxes.remove(id); } public static GuardedObject createGuardedObject() { GuardedObject go = new GuardedObject(generateId()); boxes.put(go.getId(), go); return go; } public static Set<Integer> getIds() { return boxes.keySet(); } } class GuardedObject { //标识,Guarded Object private int id;//添加get set方法 }
顺序输出
顺序输出 2 1
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while (true) { //try { Thread.sleep(1000); } catch (InterruptedException e) { } // 当没有许可时,当前线程暂停运行;有许可时,用掉这个许可,当前线程恢复运行 LockSupport.park(); System.out.println("1"); } }); Thread t2 = new Thread(() -> { while (true) { System.out.println("2"); // 给线程 t1 发放『许可』(多次连续调用 unpark 只会发放一个『许可』) LockSupport.unpark(t1); try { Thread.sleep(500); } catch (InterruptedException e) { } } }); t1.start(); t2.start(); }
交替输出
连续输出 5 次 abc
public class day2_14 { public static void main(String[] args) throws InterruptedException { AwaitSignal awaitSignal = new AwaitSignal(5); Condition a = awaitSignal.newCondition(); Condition b = awaitSignal.newCondition(); Condition c = awaitSignal.newCondition(); new Thread(() -> { awaitSignal.print("a", a, b); }).start(); new Thread(() -> { awaitSignal.print("b", b, c); }).start(); new Thread(() -> { awaitSignal.print("c", c, a); }).start(); Thread.sleep(1000); awaitSignal.lock(); try { a.signal(); } finally { awaitSignal.unlock(); } } } class AwaitSignal extends ReentrantLock { private int loopNumber; public AwaitSignal(int loopNumber) { this.loopNumber = loopNumber; } //参数1:打印内容 参数二:条件变量 参数二:唤醒下一个 public void print(String str, Condition condition, Condition next) { for (int i = 0; i < loopNumber; i++) { lock(); try { condition.await(); System.out.print(str); next.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { unlock(); } } } }
异步模式
传统版
异步模式之生产者/消费者:
class ShareData { private int number = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment() throws Exception{ // 同步代码块,加锁 lock.lock(); try { // 判断 防止虚假唤醒 while(number != 0) { // 等待不能生产 condition.await(); } // 干活 number++; System.out.println(Thread.currentThread().getName() + "\t " + number); // 通知 唤醒 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement() throws Exception{ // 同步代码块,加锁 lock.lock(); try { // 判断 防止虚假唤醒 while(number == 0) { // 等待不能消费 condition.await(); } // 干活 number--; System.out.println(Thread.currentThread().getName() + "\t " + number); // 通知 唤醒 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } public class TraditionalProducerConsumer { public static void main(String[] args) { ShareData shareData = new ShareData(); // t1线程,生产 new Thread(() -> { for (int i = 0; i < 5; i++) { shareData.increment(); } }, "t1").start(); // t2线程,消费 new Thread(() -> { for (int i = 0; i < 5; i++) { shareData.decrement(); } }, "t2").start(); } }
改进版
异步模式之生产者/消费者:
- 消费队列可以用来平衡生产和消费的线程资源,不需要产生结果和消费结果的线程一一对应
- 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
- 消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
- JDK 中各种阻塞队列,采用的就是这种模式
public class demo { public static void main(String[] args) { MessageQueue queue = new MessageQueue(2); for (int i = 0; i < 3; i++) { int id = i; new Thread(() -> { queue.put(new Message(id,"值"+id)); }, "生产者" + i).start(); } new Thread(() -> { while (true) { try { Thread.sleep(1000); Message message = queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } } },"消费者").start(); } } //消息队列类,Java间线程之间通信 class MessageQueue { private LinkedList<Message> list = new LinkedList<>();//消息的队列集合 private int capacity;//队列容量 public MessageQueue(int capacity) { this.capacity = capacity; } //获取消息 public Message take() { //检查队列是否为空 synchronized (list) { while (list.isEmpty()) { try { sout(Thread.currentThread().getName() + ":队列为空,消费者线程等待"); list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //从队列的头部获取消息返回 Message message = list.removeFirst(); sout(Thread.currentThread().getName() + ":已消费消息--" + message); list.notifyAll(); return message; } } //存入消息 public void put(Message message) { synchronized (list) { //检查队列是否满 while (list.size() == capacity) { try { sout(Thread.currentThread().getName()+":队列为已满,生产者线程等待"); list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //将消息加入队列尾部 list.addLast(message); sout(Thread.currentThread().getName() + ":已生产消息--" + message); list.notifyAll(); } } } final class Message { private int id; private Object value; //get set }
阻塞队列
public static void main(String[] args) { ExecutorService consumer = Executors.newFixedThreadPool(1); ExecutorService producer = Executors.newFixedThreadPool(1); BlockingQueue<Integer> queue = new SynchronousQueue<>(); producer.submit(() -> { try { System.out.println("生产..."); Thread.sleep(1000); queue.put(10); } catch (InterruptedException e) { e.printStackTrace(); } }); consumer.submit(() -> { try { System.out.println("等待消费..."); Integer result = queue.take(); System.out.println("结果为:" + result); } catch (InterruptedException e) { e.printStackTrace(); } }); }