并发编程之多线程线程安全(下)-程序员宅基地

技术标签: java  

1、什么是 Volatile?

volatile 是一个类型修饰符,具有可见性,也就是说一旦某个线程修改了该被 volatile 修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。

在 java 中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是 CPU 缓存上进行的,之后才会同步到主存中,而加了 volatile 修饰符的变量则是直接读写主存。

[可以搜索了解一下 java 中的内存模型]

看下面一段代码:

class ThreadVolatileDemo extends Thread {
     

    public boolean flag = true;

    @Override
    public void run() {
        System.out.println("开始执行子线程....");
        while (flag) {
        }
        System.out.println("线程停止");
    }

    public void setRuning(boolean flag) {
        this.flag = flag;
    }
}

public class ThreadVolatile {
    public static void main(String[] args) throws InterruptedException {
        ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
        threadVolatileDemo.start();
        Thread.sleep(3000);
        threadVolatileDemo.setRuning(false);
        System.out.println("flag 已经设置成false");
        Thread.sleep(1000);
        System.out.println(threadVolatileDemo.flag);
    }
}

运行结果:

开始执行子线程....
flag 已经设置成false
false

已经将结果设置为 fasle 为什么?还一直在运行呢。

原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。

解决办法使用 volatile 关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值。

2、Volatile 与 Synchronize 的区别?
  1. volatile 虽然具有可见性但是并不能保证原子性。
  2. 性能方面,synchronized 关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而 volatile 关键字在某些情况下性能要优于synchronized。但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。

上篇以及本篇多次提及原子性,借此重新了解一下多线程中的三大特性。

原子性、可见性、有序性。

2.1、什么是原子性?

如果有了解过事务的小伙伴,这一块就比较好理解了,所谓的原子性即一个或多个操作,要么全部执行完成,要么就都不执行,如果只执行了一部分,对不起,你得撤销已经完成的操作。

举个例子:

账户 A 向账户 B 转账 1000 元,那么必然包括 2 个操作:从账户 A 减去 1000,向账户 B 加上 1000,这两个操作必须要具备原子性才能保证不出现一些意外的问题发生。

总结:所谓的原子性其实就是保证数据一致、线程安全的一部分。

2.2、什么是可见性?

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程就能够立即看到修改的值。

若两个线程在不同的 cpu,有个变量 i ,线程 1 改变了 i 的值还没有刷新到主存,线程 2 又使用了 i,那么这个 i 值肯定还是之前的,线程 1 对变量的修改,线程2 没有看到,这就是可见性问题了。

2.3、什么是有序性?

即程序执行时按照代码书写的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。(本文不对指令重排作介绍,但不代表它不重要,它是理解 java 并发原理时非常重要的一个概念)。

重排序文章留空,后面补充。

3、多线程之间的通讯

什么是多线程之间的通讯?

多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。画图演示:

多线程之间的通讯需求:

第一个线程写入(input)用户,另一个线程读取(out)用户,实现读一个写一个操作。

代码实现:

共享资源实习类 Res

class Res2{
     
    public String userName;
    public String userSex;
}

class InputThread extends Thread{

    private Res2 mRes;

    public InputThread(Res2 mRes){
        this.mRes = mRes;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (mRes){
                if (count == 0) {
                    mRes.userName = "余胜军";
                    mRes.userSex = "男";
                } else {
                    mRes.userName = "小紅";
                    mRes.userSex = "女";
                }
                count = (count + 1) % 2;
            }
        }
    }
}

class OutThread extends Thread{

    private Res2 mRes;

    public OutThread(Res2 mRes){
        this.mRes = mRes;
    }

    @Override
    public void run() {
        while (true){
            System.out.println(mRes.userName + "--" + mRes.userSex);
        }
    }
}

public class Demo9 {

    public static void main(String[] args){

        Res2 res = new Res2();
        InputThread intThrad = new InputThread(res);
        OutThread outThread = new OutThread(res);
        intThrad.start();
        outThread.start();

    }

}

打印结果:

...
余胜军--男
小紅--女
小紅--女
余胜军--男
小紅--女

在代码中我们用到了 synchronized 关键字解决线程线程安全问题,所以实现了正确打印,如果不使用 synchronized 的话,可能会出现如下打印的脏数据:

余胜军--女
小紅--女
小紅--男
余胜军--男
小紅--女
wait()、 notify() 方法。

关于该方法的介绍:

  1. 因为涉及到对象锁,他们必须都放在 synchronized 中来使用。
  2. wait 必须暂停当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行。
  3. notify/notifyall:唤醒锁池中的线程,使之运行。

了解了 wait、notify 方法后,我们来改造一下上边的代码:

class Res2{
     
    public String userName;
    public String userSex;
    public boolean flag = false;/*线程通讯标识*/
}

class InputThread extends Thread{

    private Res2 mRes;

    public InputThread(Res2 mRes){
        this.mRes = mRes;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (mRes){
               if(mRes.flag){
                   try {
                       mRes.wait();
                   }catch (Exception e){

                   }
               }
               if (count == 0) {
                    mRes.userName = "余胜军";
                    mRes.userSex = "男";
                } else {
                    mRes.userName = "小紅";
                    mRes.userSex = "女";
                }
                count = (count + 1) % 2;

                mRes.flag = true;
                mRes.notify();
            }
        }
    }
}

class OutThread extends Thread{

    private Res2 mRes;

    public OutThread(Res2 mRes){
        this.mRes = mRes;
    }

    @Override
    public void run() {
        while (true){

            synchronized (mRes){
                if(!mRes.flag){

                    try {
                        mRes.wait();
                    } catch (Exception e) {}
                }

                System.out.println(mRes.userName + "--" + mRes.userSex);

                mRes.flag = false;
                mRes.notify();
            }

        }
    }
}

public class Demo9 {

    public static void main(String[] args){

        Res2 res = new Res2();
        InputThread intThrad = new InputThread(res);
        OutThread outThread = new OutThread(res);
        intThrad.start();
        outThread.start();

    }

}
wait() 与 sleep() 区别?

sleep() 方法时属于 Thread 类的,而 wait() 方法是属于 Object 类的。

sleep() 方法导致了程序暂停执行指定的时间,让出 cpu 给其他线程,但是它的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。

在调用 sleep() 方法的过程中,线程不会释放对象锁。

而当调用 wait() 方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify() 方法后本线程才进入对象锁池准备,获取对象锁进入运行状态。

Lock锁(显示锁)

lock接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时需要手动获取锁和释放锁。

代码示例:

Lock lock  = new ReentrantLock();
lock.lock();
try{
    /*可能会出现线程安全的操作*/
}finally{
    /*一定在finally中释放锁*/
    /*也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常*/
    lock.ublock();
}
Condition用法—Lock中的wait()、notify()

Condition 的功能类似于在传统的线程技术中的 Object.wait() 和 Object.notify() 的功能。

代码:

Condition condition = lock.newCondition();

res. condition.await();  类似wait

res. Condition. Signal() 类似notify
synchronized 与 lock 的区别

synchronized 重量级,lock 轻量级锁,synchronized 不可控制,lock 可控制。

lock 相对 synchronized 比较灵活。

我创建了一个java相关的公众号,用来记录自己的学习之路,感兴趣的小伙伴可以关注一下微信公众号哈:niceyoo

转载于:https://www.cnblogs.com/niceyoo/p/11173243.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30673715/article/details/101666748

智能推荐

从数据仓库到数据结构:数据架构的演变之路-程序员宅基地

文章浏览阅读2.1k次。数据治理(DG):Experian数据质量报告表明,全球78%的组织受到数据治理不善的困扰,这导致人们对数据和从数据获得的洞察力产生不信任。数据治理告诉我们,在数据生命周期的任何时候,数据消费者都应该知道数据的位置、格式、使用关系以及与数据相关的任何其他相关信息,以避免数据债务。使数据成为可共享的资产:使数据成为可共享的资产强调我们将数据视为一种有价值的资源,可以在不同的系统之间共享和访问。从传统的数据仓库到现代的数据网格和数据结构方法,这些架构解决了特定的挑战,带来了新的机遇。

Java版工程行业管理系统源码-专业的工程管理软件- 工程项目各模块及其功能点清单-程序员宅基地

文章浏览阅读955次,点赞15次,收藏11次。二、企业通过数字化转型,不仅有利于优化业务流程、提升经营管理能力和风险控制能力,还可强有力地促进企业体制机制的全面创新。四、在企业里建立一个管过程、提效率、降风险、控成本的工程项目管理环境,科学化、规范化是至关重要的。1、项目列表:实现对项目列表的增删改查操作,包括查看各项目的立项人、创建时间、2、项目计划管理:项目计划查看和管理模块,可执行增删改查操作,包括查看甘特图。3、收支报表:项目收支报表,包含总体收支、项目收支和收支统计模块。1、项目汇总:项目汇总信息查看,包括进度、计划时间等信息。

杂项-安全:容灾系统-程序员宅基地

文章浏览阅读503次。ylbtech-杂项-安全:容灾系统容灾系统,对于IT而言,就是为计算机信息系统提供的一个能应付各种灾难的环境。当计算机系统在遭受如火灾、水灾、地震、战争等不可抗拒的自然灾难以及计算机犯罪、计算机病毒、掉电、网络/通信失败、硬件/软件错误和人为操作错误等人为灾难时,容灾系统将保证用户数据的安全性(数据容灾),甚至,一个更加完善的容灾系统,还能提供不间断的应用服务(应用容..._备中心不影响主中心性能,

C语言中的strlen()和sizeof()对比-程序员宅基地

文章浏览阅读490次。*1. strlen函数:**计算的是字符串str的长度,从字符的首地址开始遍历,以 ‘\0’ 为结束标志,然后将计算的长度返回,计算的长度并不包含’\0’。当我们遇到“\0"时我们就要停止读取,此时“\0"前字符的个数就是字符串的长度,注意:这里的“\0"只是结束标志,仅仅告诉我们strlen函数读取到这里就要停止了,“\0"不算做一个字符!!!**2. sizeof函数:**相比strlen函数,sizeof就简单多了,sizeof其实就是一个运算符,主要用来计算所占空间字节的大小。

一梦江湖网页提交问题服务器错误,【一梦江湖攻略】安宁寺侠士副本预备中(详细教程)...-程序员宅基地

文章浏览阅读438次。一梦江湖12月3日更新了什么体验优化调整一、更新内容1、面对面交易新增更新后,时装·十里荼蘼开放面对面交易。2、晓风开染色优化修正了晓风开·冠染白两鬓露黑的问题。3、白重预览优化修正了预览挂件·白重时的挂件角度错误问题。4、纸玩法开放材料购买为弘扬民间剪纸艺术,阴如穆决定放开手中杂货的门派购买限制,太阴以外的侠士也可在他那里购买用于剪纸的白纸、炭笔和染料了!5、神机万象修复修复了主动篆铭技的冷却时...

Python自动化操作pywinauto_python pywinauto-程序员宅基地

文章浏览阅读5.4k次,点赞8次,收藏38次。Python自动化操作(pywinauto)_python pywinauto

随便推点

java控制台内容覆盖_Java代码自动生成注释,运行后在控制台输入文件路径就可以将该路径下的文件都加上注释,不会覆盖已有的注释...-程序员宅基地

文章浏览阅读67次。Java代码自动生成注释,运行后在控制台输入文件路径就可以将该路径下的文件都加上注释,不会覆盖已有的注释代码片段:/*** 此类文件作用于为大量类文件* 加上类注释,方法注释* 加注释时不会覆盖已有注释* @author lKF44520* @date 2011-07-20*/public class RemarkHelper {public static void main(String[]..._修改javadoc注释怎么覆盖代码已有的注释

数字电视中相关概念1 :码率、符号率、带宽、宽带_符号率 范围 dvbc-程序员宅基地

文章浏览阅读7.3k次。数字通信的理论是:8MHz是载波带宽,因为调制是双边带的,其基带带宽为4MHz。Nyquist理论说,每Hz的带宽可以传输2symbol/s的数据,这个说法是说发送滤波器可以做到理想频率响应。那么在正常情况下做不到的,所以最常用的设计方法是升余弦响应,这种设计有个特征系数就滚降因子,如为0.15,所以可以使用的有效带宽就为4/1.15=3.478MHz。这样在3.478MHz的基带带宽内可以传输的_符号率 范围 dvbc

用中国高铁来谈谈AXI Outstanding能力_dma outstanding-程序员宅基地

文章浏览阅读1.8k次,点赞6次,收藏38次。好,我们一一对应上之后,我们以上海到北京的高铁为例,假设全上海的人都要坐高铁去北京,为了达到最高效率,那就是上海到北京的铁轨上高铁首尾相接,从上海虹桥排到北京南站,这些首位相接的高铁还都以310Km/h的速度前进(这里我们不考虑高铁停在北京南站下客减速的时间哈)。大家都知道AXI是ARM AMBA协议家族的一员,AXI的很多特性,例如分离的读写通道、Burst传输,Interleaving、乱序返回等特,显著提升了SOC互连的性能。和高铁列数的计算类似,我们首先需要确定AXI Master 在需要的场景。_dma outstanding

专访天谋科技谭新宇:我与 IoTDB 的这些年-程序员宅基地

文章浏览阅读1k次,点赞18次,收藏19次。从清华大学到天谋科技:一名 IoTDB 深度参与者的转换与成长。自 2020 年以来,在数字化、国产化浪潮叠加下,中国信创产业得以高速发展,从基础硬件到基础软件、应用软件再到信息安全层面均涌现出一批领先的项目和厂商。聚焦到基础软件层面,以 IoTDB 为代表的国产时序数据库正为工业、制造业等国家支柱行业的数字化转型、国产化替代筑基。作为一款从“0”到“1”自主研发的国产时序数据库,IoTDB 刚刚...

MATLAB知识点:条件判断switch-case-otherwise-end语句_matlab中判断条件切换-程序员宅基地

文章浏览阅读666次,点赞4次,收藏7次。条件判断switch-case-otherwise-end语句_matlab中判断条件切换

mysql隐式转换导致的索引失效分析_数据库隐式转换 索引失效-程序员宅基地

文章浏览阅读606次。本次测试使用的 MySQL 版本是 5.7.26,随着 MySQL 版本的更新某些特性可能会发生改变,本文不代表所述观点和结论于 MySQL 所有版本均准确无误,版本差异请自行甄别。原文:https://www.guitu18.com/post/2019/11/24/61.html前言数据库优化是一个任重而道远的任务,想要做优化必须深入理解数据库的各种特性。在开发过程中我们经常会遇到一些原因很简单但造成的后果却很严重的疑难杂症,这类问题往往还不容易定位,排查费时费力最后发现是一个很小的疏忽造成的,._数据库隐式转换 索引失效