线程安全——wait和notiy

文章讨论了Java中线程安全的问题,重点介绍了wait和notify方法在处理线程间通信和避免死锁中的作用。wait使线程进入等待状态,而notify唤醒等待的线程。同时,对比了sleep方法的不同。文章还探讨了单例模式,包括饿汉模式和懒汉模式,并指出懒汉模式的线程不安全问题,提出了解决方案,如加锁和使用volatile关键字来确保线程安全。
摘要由CSDN通过智能技术生成

线程安全——wait和notify


需要得到一个线程安全,我们可以使用多种方法来保证线程安全,前面我们了解了如何使用synchronized来对一个线程进行加锁操作。//加链接

使用wait和notify

先想一个画面,相当于张三(线程)在银行ATM(资源库)取钱,但是此时ATM(资源库)中已经没有现金(资源)了。于是张三离开ATM机,接下来与其他线程竞争进入ATM机器进行取钱或者存钱操作(获取资源或者释放资源),因为线程调度是随机的,如果依旧是张三抢到锁,长此以往,就会造成死锁!

而wait和notify就可以很好解决这个问题。

wait和notify是Object的方法,只要你使用object的数据类型就可以使用wait和notify。

​ wait:是一个相当于join操作,不过join是等待线程结束,而wait是等待,线程还没有结束。

notify:需要和wait配合使用。
解决方案如下:张三没有取到钱,于是将张三进行wait操作,此时张三不参与ATM(锁竞争),其他线程就有了机会,假如李四存了钱在ATM(释放资源),相当于执行了notify操作(唤醒),此时张三被唤醒,继续执行后续操作,也就相对保证了线程安全。

//代码演示
public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        //等待
        Thread t1 =new Thread(()->{
            synchronized (object){
                System.out.println("wait 之前");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait 之后");
            }
        });
        t1.start();
        Thread.sleep(1000);
        //唤醒
        Thread t2 =new Thread(()->{
            //只有等t1线程释放锁,t2才抢到锁,才开始执行线程中的代码,注意这里只有一把锁,不是有两个锁代码就代表这两把锁,因为使用了同一个object对象,所以锁也只有一把
        synchronized (object){
            System.out.println("notify开始");
            object.notify();
            System.out.println("notify结束");
        }
        });
        t2.start();
    }
}

在这里插入图片描述

wait执行步骤:

  1. 解锁操作,使得其他线程可以进行锁竞争

  2. 阻塞等待,执行wait的线程状态改变为阻塞

  3. 唤醒重新获取锁

    notify执行需要注意:

  4. 需要与wait一起放入synchronized中使用

  5. 只有当wait开始等待,执行notify通知唤醒阻塞线程,阻塞结束,线程继续运行,wait重新获取锁,向下执行。

wait带参数版本:可以指定等待时间(join也可以指定时间),这是为了防止等待时间过长浪费系统CPU。

object.wait(xxxx);//填入秒数*1000

比较sleep和wait

不同点:设计两个方法的适用场景不同,wait是解决线程之间执行的顺序,需要搭配锁使用,而且需要唤醒操作。sleep只是让线程进行休眠操作,只要时间到就会继续执行线程,sleep也不需要搭配锁使用。
相同点:可以设置等待时间,使得线程进行休眠。

//使用wait语法规格
synchronized (object){
    try {
        object.wait(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
//使用sleep语法规格
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

单例模式的介绍

单例模式是一种设计模式(软件开发中棋谱一样的存在),某个类在进程中只有唯一的一个实例。
单例模式:
1.饿汉模式(急迫):提前将所有的准备工作都做好。
2.懒汉模式(从容不迫):直到需要的时候才做准备,与饿汉模式相比之下,效率更加高
假如在打开一个文档时,饿汉模式做的就是将文档中所有内容全部加载,而懒汉则是一边使用一边加载,如果还没有使用则不会进行加载操作,这样大大的节省了系统资源。

//设计一个单例模式(饿汉模式)
class Singleton{
    //唯一实例的本体
    private static Singleton instance = new Singleton();
    //获取实例的方法
    public static Singleton getInstance() {
        return instance;
    }
    //禁止外部重新创建实例对象
    private Singleton(){};
}
public class SingerThread {
    public static void main(String[] args) {
        //s1 和 s2 是同一个对象
        Singleton s1=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();
    }
}

在这里插入图片描述

在这里插入图片描述

此处无法new新对象,所以成立了一个单个实例

//设计一个单例模式(懒汉模式与饿汉模式基本相似)
class Singletons{
    private static Singletons instance;
    //获取实例的方法,只有当需要才进行new对象
    public static Singletons getInstance() {
        if(instance == null){
            instance = new Singletons();
        }
        return instance;
    }
    private Singletons(){}
}
public class SingerThread {
    public static void main(String[] args) {
        //s1 和 s2 是同一个对象
        Singletons s1 = Singletons.getInstance();
        Singletons s2 = Singletons.getInstance();
    }
}

请问此时设计的单例模式是否符合线程安全?
饿汉模式是满足线程安全的,懒汉是不满足的
分析代码可以发现,懒汉中线程唯一性不能保证,可能会导致多个对象产生,内存中对象占量太大可能会导致程序崩溃!!!
如何解决懒汉模式中存在的问题呢?
我们回忆一下之前是如何解决线程安全问题的,加锁操作,将其设置为原子操作,使用voilter
这里也是一样的,解决懒汉模式中安全问题。
单例模式的安全问题
饿汉模式:天然安全
懒汉模式:不安全
加锁操作思路:针对加锁进判断,进行双重判断保证线程安全。

缺点:容易造成阻塞等待,加锁解锁操作很浪费时间(非必要不加锁)

class Singleton{
    private static Singleton instance;
    //加锁操作加在方法中
    synchronized public static Singleton getInstance() {
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
    private Singleton(){}
}

原子操作:加锁将if和new变成一个原子操作

class Singleton{
    private static Singleton instance;
    public static Singleton getInstance() {
        //两个if条件,第一个是判定需要加锁,第二个是判断是否要创建对象
        if (instance==null){
            synchronized (Singleton.class){
                if(instance == null){
                instance = new Singleton();
                }
            }
        }
        return instance;
    }
    private Singleton(){}
}

指令重排序允许在调用构造方法之前将对象传递,而容易被其他线程抢先调度,而其他线程得到的对象是一个不完整的对象,容易造成bug。

使用voilter:禁止指令重排序,让后续线程正常拿到对象

class Singleton{
    //禁止指令重排序,每次获取instance都需要重新在内存中读取
    volatile private static Singleton instance;
    public static Singleton getInstance() {
            //两个if条件,第一个是判定需要加锁,第二个是判断是否要创建对象
        if (instance==null){
            synchronized (Singleton.class){
                if(instance == null){
                instance = new Singleton();
                }
            }
        }
        return instance;
    }
    private Singleton(){}
}

以上就是将单例模式中的懒汉变成安全线程的几种方法,欢迎大家指点交流!ㄟ(≧◇≦)ㄏ

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值