线程状态切换 详解等待队列和同步队列的关系

线程的主要状态及切换:

1.初始-NEW(还未调用start())

2.运行-RUNNABLE(就绪(READY):调用了start()       ,      运行中(RUNNING):获得了时间片         这两者统称为运行)

3.阻塞-BLOCKED 因为synchronized没获取到锁的线程就会阻塞在同步队列中(同步队列中放的是尝试获取锁但失败的线程)

4.等待-WAITING(具体情况见下图,会进入等待队列,等待进入同步队列去竞争同步锁)(进入等待队列后,需要notify()唤醒,以进入同步队列)

5.超时等待-TIMED_WAITING 

6.销毁-TERMINATED 

 

等待队列(本是Object里的方法,但影响了线程)

  1. 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。

  2. 与等待队列相关的步骤和图

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()方法扯上关系

 

1

wait、notify都是Object类的方法

lock、unlock是ReentrantLock类(实现了Lock接口)的方法

 

前两者需要借助在synchronized中发挥作用;

后两者必须在lock中,由conditon调用。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值