转自:https://blog.csdn.net/weixin_37695911/article/details/106668435
线程的主要状态及切换:
1.初始-NEW(还未调用start())
2.运行-RUNNABLE(就绪(READY):调用了start(),运行中(RUNNING):获得了时间片这两者统称为运行)
3.阻塞-BLOCKED 因为synchronized没获取到锁的线程就会阻塞在同步队列中(同步队列中放的是尝试获取锁但失败的线程)
4.等待-WAITING(具体情况见下图,会进入等待队列,等待进入同步队列去竞争同步锁)(进入等待队列后,需要notify()唤醒,以进入同步队列)
5.超时等待-TIMED_WAITING
6.销毁-TERMINATED
等待队列(本是Object里的方法,但影响了线程)
-
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
-
与等待队列相关的步骤和图
object.notify():将任意一个线程从绿色等待队列移动到红色同步队列
待synchronized同步代码块结束后,会自动释放object锁对象,这时同步队列中的所有线程争抢对象锁
* 1.线程1获取对象A的锁,正在使用对象A。
* 2.线程1调用对象A的wait()方法。
* 3.线程1释放对象A的锁,并马上进入等待队列。
* 4.锁池里面的对象争抢对象A的锁。
* 5.线程5获得对象A的锁,进入synchronized块,使用对象A。
* 6.线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入同步队列。若线程5调用对象A的notify()方法,则唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入同步队列。
* 7.notifyAll()方法所在synchronized结束,线程5释放对象A的锁。
* 8.同步队列的线程争抢对象锁,但线程1什么时候能抢到就不知道了。
注意:等待队列里许许多多的线程都wait()在一个对象上,此时某一线程调用了对象的notify()方法,那唤醒的到底是哪个线程?随机?队列FIFO?or sth else?java文档就简单的写了句:选择是任意性的(The choice is arbitrary and occurs at the discretion of the implementation)。
synchronized只有一个等待队列,一个同步队列
juc.locks包下有多个等待队列,一个同步队列
同步队列是在同步的环境下才有的概念,一个对象(即一个锁)永远只对应一个同步队列。
问题:为什么每个并发包中的同步器会有多个等待队列呢??
不同于synchronized同步队列和等待队列只有一个,AQS的等待队列是有多个,因为AQS可以实现排他锁(ReentrantLock)和非排他锁(ReentrantReadWriteLock——读写锁),读写锁就是一个需要多个等待队列的锁。等待队列(Condition)用来保存被阻塞的线程的。因为读写锁是一对锁,所以需要两个等待队列来分别保存被阻塞的读锁和被阻塞的写锁。
为什么要在竞争锁资源和同步队列中引入等待队列这个概念:
在synchronized中还不好解释,
但是在lock,在condition中,多少个condition就对应多少个等待队列,这样就可以区分出coder希望将当前获得锁的线程放进哪个等待队列中,以达到精准的通知/等待机制。
conditionX.await(); //将当前线程lock锁释放,把当前线程放进conditonX对应的等待队列中
conditionX.signalAll(); //把conditonX对应的等待队列中的所有线程唤醒,放进同步队列中
更深的东西涉及到AQS-抽象同步队列!
同步队列状态
1. 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁而失败的线程。
2. 当一个线程1(在等待队列中)被另外一个线程2唤醒时(在线程2里调用Object.notify()),1线程进入同步队列,去争夺对象锁。
3. 同步队列是在同步的环境下才有的概念,一个对象(即一个锁)永远只对应一个同步队列。(此处的一个对象指的就是synchronized(Object object)中的这个object)
感觉是在Object中,多个线程竞争1个锁对象,对应1个等待列队,对应1个同步队列。(正确!)
juc.locks包下,多个线程竞争1个lock锁对象,有多个等待队列,对应1个同步队列
总结:在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的 Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列。
一些我自己的总结:
1.sleep()释放CPU资源,不释放锁:同步线程之间,如果某个线程在同步代码块内调用sleep()方法,会让其他线程干等,因为sleep()方法并不会让当前线程退出代码块。sleep()方法导致了线程暂停指定的时间,让出CPU给其他线程。但是该线程的监控状态依然保持着,当其指定的时间到了又会自动恢复运行状态。
2.wait()释放CPU资源,也释放锁(使自己进入等待队列,依靠notify()以进入同步队列去竞争同步资源)
3.notify()不释放CPU资源,等到自己同步代码块执行完毕后再释放锁(即自己同步代码执行完毕后,再唤醒一个进程离开等待队列,进入同步队列,再让同步队列中的随机一个进程获得锁)
句句都是精华:
1.wait()、notify()是Object类的方法;
2.两者必须在同步代码中使用,调用者就是这个同步代码的锁对象;
3.Object类的锁对象的方法调用会影响锁对象所在的线程;
4.锁对象一旦调用wait(),会让该线程立刻释放锁对象、CPU;
5.锁对象调用notify()后,锁对象所在线程不会立即释放锁对象、CPU,而是等到同步代码执行完毕后,才释放锁对象,并唤醒一个在等待队列的线程,让其进入同步队列,让同步队列中的线程自由竞争,获得CPU以获取调用了notify()方法的线程所释放的锁对象。
关于wait、notify,自己写的一个简单demo,模仿生产者消费者,有很多地方不贴近现实,但是可以跑:
/*
* 写个生产者、消费者,
* 一共两个线程,
* 生产者每5秒生产一次,消费者每5秒消费一次,
* 初始时有一个产品,
* 仓库最多装一个。
* (利用wait()/notify())
* */
public class A20200108 {
static boolean flag = true; //初始时有产品
static Object lock =new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(new Wait(),"consumer").start();
TimeUnit.SECONDS.sleep(2);
new Thread(new Notify(),"producer").start();
}
static class Wait implements Runnable{
@Override
public void run() {
while (true){
try {
TimeUnit.SECONDS.sleep(5);
synchronized (lock){ //消费者进入仓库
while(!flag){ //如果没有产品
System.out.println(Thread.currentThread().getName() + "没有产品,进入等待队列");
lock.wait();
}
System.out.println(Thread.currentThread().getName() + "有产品,消费呗");
flag = false;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Notify implements Runnable{
@Override
public void run() {
while (true){
try {
TimeUnit.SECONDS.sleep(5);
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "来了,准备生产,唤醒消费者");
flag = true;
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
join:由线程调用该方法。(和sleep一样,都是由Thread调用)
join的功能在内部是使用wait()方法来实现的。
作用:让调用join方法的当前线程(注意当前线程的含义,很有可能是指main线程)进入等待队列(相当于隐式调用了wait),当调用join方法的线程执行完毕时(如main线程中的一个A线程),再唤醒当前线程(如主线程)(唤醒:离开等待队列,进入同步队列)(即main线程放弃锁给其他线程后,下一个获得锁的不一定是main,main也需要去公平竞争)(相当于隐式调用了notify())
(即在主线程内,让线程A调用join(),会让主线程隐式调用了wait(),从而进入等待队列,等线程A执行完毕后,再唤醒main线程)
join():
主要作用是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
举例:可以在main线程中写
threadA.join();
/*
此代码的意思是阻塞main线程,直到threadA执行完毕(之前threadA已经开始执行了,因为join()方法必须在线程start()方法调用之后调用)(threadA.join()是Thread方法,不像wait()、notify()需要放在同步块里执行)
*/
join()方法必须在线程start()方法调用之后调用才有意义。一个线程都还未开始运行,同步是不具有任何意义的。
即先要threadA.start()后,调用threadA.join()才有意义。
感觉join和wait的区别就在唤醒时机:后者需要在别的线程里手动唤醒(by 锁对象.notify()),前者则只需要threadA.join()执行完毕,调用了这行代码的当前线程就会被唤醒。
哇,看完恍然大悟,原来线程类的join()还能和Object类中的wait()方法扯上关系
wait、notify都是Object类的方法
lock、unlock是ReentrantLock类(实现了Lock接口)的方法
前两者需要借助在synchronized中发挥作用;
后两者必须在lock中,由conditon调用。