内置锁(二)synchronized下的等待通知机制

一、等待/通知机制的简介

线程之间的协作:

??为了完成某个任务,线程之间需要进行协作,采取的方式:中断、互斥,以及互斥上面的线程的挂起、唤醒;如:生成者--消费者模式、或者某个动作完成,可以唤醒下一个线程、管道流已准备等等;

等待/通知机制:

? ?等待/通知机制 是线程之间的协作一种常用的方式之一,在显示锁Lock 和 内置锁synchronized都有对应的实现方式。

等待/通知机制 经典的使用方式,便是在生产者与消费者的模式中使用:

1、生产者负责生产商品,并送到仓库中存储;

2、消费者则从仓库中获取商品,享受商品;

3、仓库的容量有限,当仓库满了后,生产者就会停止生产,进入等待状态。直到消费者消耗了一部分商品,通知生产者,仓库有空位了,生产者才会继续生产商品;

4、当消费者的消耗速度过快,消耗光仓库的商品时,也会停止消耗,进入等待状态,直到生产者生产了商品,并通知唤醒消费者时,消费者才继续消耗商品。

二、等待/通知机制 在synchronized下的实现:

在内置锁下,等待/通知机制 是依赖于wait( )和 notify、notifyAll 来实现的,这三个方法都是继承自根类Object中。

下面是这几个方法的API描述,wait()方法有3个版本:

void wait():

在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。

void wait(long timeout):

超时等待,在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。如果时间当时间到来了,线程还是没有被唤醒或中断,那么就会自动唤醒;

void wait(long timeout, int nanos):

参数timeout的单位是毫秒,参数nanos的单位是纳秒,即等待时间精确到纳秒。其他特性与wait(long timeout)一样。

void notify():

唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。

void notifyAll():

唤醒在此对象监视器上等待的所有线程。

1、调用 wait、notify、notifyAll 前,必须先获取锁

??在调用 wait、notify、notifyAll 前,必须获取锁(对象监视器),而且这三个方法是由synchronized的对象锁(对象监视器)来调用。对象监视器 中维护着两个队列:就绪队列,等待队列。调用了wait()方法就是使线程进入等待队列,不再参与锁的竞争,直到被唤醒才能重新进入就绪队列,才可能获取锁,再次被执行。

如果在没有获取锁(对象监视器)的情况下,将会抛出IllegalMonitorStateException 异常:

wait、notify、notifyAll 方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:

  • 通过执行此对象的同步实例方法。
  • 通过执行在此对象上进行同步的 synchronized 语句的正文。
  • 对于 Class 类型的对象,可以通过执行该类的同步静态方法。

一次只能有一个线程拥有对象的监视器。

抛出:

IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。

??看下面的例子,消费者想要10个以上的商品,如果不够,则等待生产者生产足够的商品,生产者再来唤醒消费者线程。

//生产者与消费者互斥使用仓库
    public static List<String>  warehouse = new LinkedList<>();
public static void main(String[] args) {
        //生产者线程
        Thread thread_1 = new Thread(){
             @Override
             public void run() {
                //对象监视器为warehouse,必须先获取这个对象监视器
                synchronized ( warehouse) {
                    int i = 0;
                    //生产10个商品
                    while(warehouse.size()<=10){
                        ++i;
                    //生产商品,添加进仓库
                     warehouse.add("生产了商品goods"+i);
                     //当商品数量足够时,便唤醒消费者线程
                     if(warehouse.size()>=10){
                         warehouse.notify();
                         //生产任务完成,跳出循环,结束运行,从而可以释放锁
                         break;
                    }
                }
                }
            }
         };

         //消费者线程
        Thread thread_2 = new Thread(){
            @Override
            public void run() {
                synchronized (warehouse) {
                    try {//如果仓库的商品数量不能满足消费者
                        if(warehouse.size()<10){
                            //消费者进入等待队列,等待被唤醒
                            warehouse.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                   //消费商品
                    for(String goods:warehouse){
                        System.out.println("消费者消费了商品:"+goods);
                    }
                }
            }
         };
         //
         thread_2.start();
         thread_2.setPriority(Thread.MAX_PRIORITY);
         thread_1.start();
    }

运行结果:

消费者消费了商品:商品goods1

消费者消费了商品:商品goods2

消费者消费了商品:商品goods3

消费者消费了商品:商品goods4

消费者消费了商品:商品goods5

消费者消费了商品:商品goods6

消费者消费了商品:商品goods7

消费者消费了商品:商品goods8

消费者消费了商品:商品goods9

消费者消费了商品:商品goods10

2、wait()方法是可以被中断的

??如果线程在等待过程中被中断,那么线程的等待状态就会被打断,即由对象监视器的等待队列进入就绪队列;所以,可以总结一下,线程在等待状态被唤醒的情况:

  • 被其他线程调用notify、notifyAll方法唤醒;
  • 如果是超时等待,那么时间到来了,线程也会自动唤醒。
  • 有中断发生,线程的等待状态被打断,强制唤醒。
public static void main(String[] args) throws InterruptedException {

    final String lock = "lock";

    Thread thread = new Thread(){
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("我即将进入等待状态!");
                try {
                    //超时等待5秒
                    lock.wait(5000);
                } catch (InterruptedException e) {
                    System.out.println("啊!我被中断了");
                    e.printStackTrace();
                }
                System.out.println("我在运行中....");
            }
        }
    };

    thread.start();
    //main线程睡眠1秒后,中断线程thread
    Thread.sleep(1000);
    thread.interrupt();
}

运行结果:

?? 线程thread因为中断,还没等待5秒,就被提前唤醒。

3、wait()是释放锁,notify、notifyAll 不释放锁

调用wait()方法是线程马上释放锁,进入等待队列,而调用notify、notifyAll 后,线程不会释放锁,而仅仅唤醒线程而已,要等待当前运行完后,锁才是真的被释放,被唤醒的线程才可以竞争获取锁。

final String lock = "lock";

    Thread thread_1 = new Thread("thread_1"){
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(getName()+"进入等待状态");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName()+"被唤醒,继续运行...");
            }
        }
    };
    thread_1.start();

    Thread thread_2 = new Thread("thread_2"){
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(getName()+"在运行...");
                try {
                    //模拟占用着锁,运行了一秒,sleep在睡眠中是不会释放锁的
                    Thread.sleep(1000);
                    //在调用notifyAll方法前,线程1的状态
System.out.println("线程"+thread_1.getName()+"被唤醒前的状态是:"+thread_1.getState());
                    //唤醒在对象监视器的等待队列的所有线程
                    lock.notifyAll();
                    //模拟占用着锁,继续运行5次,约为5秒
                    int i = 0;
                    while(i<5){
System.out.println("线程"+thread_1.getName()+"的状态是:"+thread_1.getState());
                        sleep(1000);
                        i++;
                    }
                    System.out.println("线程"+getName()+"运行结束!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };

    thread_2.start();

运行结果:

thread_1进入等待状态

thread_2在运行...

线程thread_1被唤醒前的状态是:WAITING

线程thread_1的状态是:BLOCKED

线程thread_1的状态是:BLOCKED

线程thread_1的状态是:BLOCKED

线程thread_1的状态是:BLOCKED

线程thread_1的状态是:BLOCKED

线程thread_2运行结束!

thread_1被唤醒,继续运行...

三、易混淆的几个方法的区别

1、wait( )方法:

  • 先释放锁,再进入等待状态;
  • 使当前线程暂停运行,让出CPU,不再参与CPU的调度,直到被唤醒为止;

2、sleep()方法:

  • 在睡眠过程中,不释放锁;
  • 使当前线程暂停运行,让出CPU,不再参与CPU的调度,直到睡眠时间到来;

3、yield( )方法:

  • 不释放锁
  • 使当前线程暂停运行,给同等优先级或高优先级的线程让出CPU。但有可能让出CPU失败,因为调用yield( )方法后,当前线程由运行状态,变成就绪状态,会马上参与CPU的调度,也就说可能再次被调度,当前线程依旧占用着CPU。

原文地址:https://www.cnblogs.com/jinggod/p/8491029.html

时间: 2024-10-09 02:36:47

内置锁(二)synchronized下的等待通知机制的相关文章

012 内置锁和synchronized

一 . 概述 在前面我们说到线程安全性问题解决的核心就是同步,同步的核心就是保证原子性. 在java之中最早就支持语法层面的同步解决了,并且提供了synchronized的方式解决问题. 二 .内置锁 在java之中每一个对象都是一个内置锁,这个在JVM的体系之中就规定好了. 内置锁的规定也就决定我们可以拿任意的对象进行同步操作. 内置锁常常配合synchronized使用. 三 .synchronized 该关键词的作用是同步,需要配合内置锁进行使用. 常见的synchronized的使用方式

显式锁(四)Lock的等待通知机制Condition

?? 任意一个Java对象,都拥有一组监视器方法(定义在根类Object上),主要包括:wait( ).wait(long timeout).notify().notifyAll()方法:这些方法与关键字synchronized结合使用,可以实现 隐式锁的等待/通知机制.而显示锁Lock也实现了等待/通知机制:Condition接口也提供了类似Object的监视器方法,与Lock配合使用可以实现 显式锁的等待/通知机制,但是两者在使用方式和功能特性有所差别.总得来说,Condition接口更加灵

Java 并发:内置锁 Synchronized

摘要: 在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程访问某一共享.可变数据时,始终都不会导致数据破坏以及其他不该出现的结果.而所有的并发模式在解决这个问题时,采用的方案都是序列化访问临界资源 .在 Java 中,提供了两种方式来实现同步互斥访问:synchronized 和 Lock.本文针对 synchronized 内置锁 详细讨论了其在 Java 并发 中的应用,包括它的具体使用场景(同步方法.同步代码块.实例对象锁 和 Class 对象锁).可重

jvm内置锁synchronized不能被中断

很久没看技术书籍了,今天看了一下<七周七并发模型>前面两章讲的java,写的还是有深度的.看到了一个有demo,说jvm内置锁synchronized是不能被中断的.照着书上写了个demo,验证了一下,是不能被中断 /** * @Author: * @Description: jdk内置锁不能被中断 * @Date: Created in : 2018/10/4 下午11:34 **/ public class Uninterruptible { private static final Ob

java 并发——内置锁

坚持学习,总会有一些不一样的东西. 一.由单例模式引入 引用一下百度百科的定义-- 线程安全是多线程编程时的计算机程序代码中的一个概念.在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况. 文字定义总是很含糊,举个反例就很清楚了,想起之前总结过单例模式,就从单例模式开始吧.如果不清楚单例模式的新同学,可以看一下这篇总结: java中全面的单例模式多种实现方式总结 单例模式中,懒汉式的实现方案如下: public c

转:【Java并发编程】之一:可重入内置锁

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法. 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞.然而,由于内置锁是可重入的,因此如果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功."重入"意味着获取锁的操作的粒度是"线程",而不是调用.重入的一种实现 方法是,为每个锁关联一个

《java并发编程实战》读书笔记1--线程安全性,内置锁,重入,状态

什么是线程安全? 当多个线程访问某个类时,不管这些的线程的执行顺序如何,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 哈哈书上的解释,还是翻译过来的,看了半天还是觉得有点奇怪.比如说 "类都能表现出正确的行为" 是毛线意思?在网上搜了一番 "线程安全就是说多线程访问同一代码,不会产生不确定的结果" 这样反而跟容易理解,果然读书要么读原版中文书要么读原版英文书,看英文的翻译版真的是蛋疼无比.说到这里,我以前貌似把

Java并发编程(1):可重入内置锁

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法. 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞.然而,由于内置锁是可重入的,因此如果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功."重入"意味着获取锁的操作的粒度是"线程",而不是调用.重入的一种实现方法是,为每个锁关联一个获

使用ReentrantLock和Condition来代替内置锁和wait(),notify(),notifyAll()

使用ReentrantLock可以替代内置锁,当使用内置锁的时候,我们可以使用wait() nitify()和notifyAll()来控制线程之间的协作,那么,当我们使用ReentrantLock的时候,我们怎么来处理线程之间的写作呢? JDK5.0为我们提供了Condition对象来替代内置锁的 wait(),notify()和notifyAll()方法 内置锁的话,就只能有一个等待队列,所有的在某个对象上执行wait()方法的线程都会被加入到该对象的等待队列中去(线程会被挂起),需要其他的线