【第二章:Java核心技术解析】第9节:Java进阶 - 高效并发编程(中)


大家好,很高兴我们可以继续学习交流Java高频面试题。在上一小节中,我们介绍了一些多线程并发编程的基础高频考察知识点,本小节,我们继续来交流学习多线程的相关知识点,主要包括原子性,可见性,有序性;常用的同步锁synchronized关键字,轻量级锁volatile关键字以及显式锁ReentrantLock等。

我们知道,多线程并发利用了CPU轮询时间片的特点,在一个线程进入阻塞状态时,可以快速切换到其余线程执行其余操作。CPU轮询时间片有利于提高其资源的利用率,最大限度的利用系统提供的处理能力,有效减少了用户的等待响应时间。但是多线程并发编程也存在着线程活性故障以及如何保证线程安全的问题

在上一小节中,我们阐述了何为线程活性故障。本小节中,主要对线程安全相关知识点进行阐述。我们先来看一个线程安全的经典问题:多个窗口售票问题。

Demo展示如下:

package niuke.thread;

public class Demo {

    public static void main(String[] args) {

        TicketSale ticketSale = new TicketSale();
        Thread Sale1 = new Thread(ticketSale, "售票口1");
        Thread Sale2 = new Thread(ticketSale, "售票口2");
        Thread Sale3 = new Thread(ticketSale, "售票口3");
        Thread Sale4 = new Thread(ticketSale, "售票口4");
        // 启动线程,开始售票
        Sale1.start();
        Sale2.start();
        Sale3.start();
        Sale4.start();
    }
}

class TicketSale implements Runnable {
    int ticketSum = 100;

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 有余票,就卖
        while (ticketSum > 0) {
            System.out.println(Thread.currentThread().getName() + "售出第" + (100 - ticketSum + 1) + "张票");
            ticketSum--;
        }
        System.out.println(Thread.currentThread().getName() + "表示没有票了");
    }
}

程序输出部分截图如下:

图片说明

由图中可以看出,在多线程并发情况下,出现了同一张票被多个窗口卖出的情况,也就是出现了线程安全的问题。多线程环境下的线程安全主要体现在原子性,可见性与有序性方面。接下来,我们依次介绍三大特性。

(1)原子性,可见性与有序性:

答:多线程环境下的线程安全主要体现在原子性,可见性与有序性方面。

原子性:

定义:对于涉及到共享变量访问的操作,若该操作从执行线程以外的任意线程来看是不可分割的,那么该操作就是原子操作,该操作具有原子性。即,其它线程不会“看到”该操作执行了部分的中间结果。

举例:银行转账流程中,A账户减少了100元,那么B账户就会多100元,这两个动作是一个原子操作。我们不会看到A减少了100元,但是B余额保持不变的中间结果。

原子性的实现方式:

  • 利用锁的排他性,保证同一时刻只有一个线程在操作一个共享变量
  • 利用CAS(Compare And Swap)保证
  • Java语言规范中,保证了除long和double型以外的任何变量的写操作都是原子操作
  • Java语言规范中又规定,volatile关键字修饰的变量可以保证其写操作的原子性

关于原子性,你应该注意的地方:

  • 原子性针对的是多个线程的共享变量,所以对于局部变量来说不存在共享问题,也就无所谓是否是原子操作
  • 单线程环境下讨论是否是原子操作没有意义
  • volatile关键字仅仅能保证变量写操作的原子性,不保证复合操作,比如说读写操作的原子性

可见性:

定义:可见性是指一个线程对于共享变量的更新,对于后续访问该变量的线程是否可见的问题。

为了阐述可见性问题,我们先来简单介绍处理器缓存的概念。

现代处理器处理速度远大于主内存的处理速度,所以在主内存和处理器之间加入了寄存器,高速缓存,写缓冲器以及无效化队列等部件来加速内存的读写操作。也就是说,我们的处理器可以和这些部件进行读写操作的交互,这些部件可以称为处理器缓存。

处理器对内存的读写操作,其实仅仅是与处理器缓存进行了交互。一个处理器的缓存上的内容无法被另外一个处理器读取,所以另外一个处理器必须通过缓存一致性协议来读取的其他处理器缓存中的数据,并且同步到自己的处理器缓存中,这样保证了

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java开发岗高频面试题全解析 文章被收录于专栏

<p> Java开发岗高频面试题全解析,专刊正文共计31节,已经全部更新完毕。专刊分9个模块来对Java岗位面试中的知识点进行解析,包括通用面试技能,Java基础,Java进阶,网络协议,常见框架以及算法,设计模式等。专刊串点成面的解析每个面试题背后的技术原理,由浅入深,循序渐进,力争让大家掌握面试题目的背后的技术原理,摒弃背题模式的陋习。 专刊详细信息,请查阅专刊大纲和开篇词的介绍。 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p> <p> <br /> </p>

全部评论
打卡...博主只是介绍了关于这些方面一些比较基础的知识。其实每个点都是可以引申很多知识的,需要自己查资料😁
6 回复 分享
发布于 2020-01-15 17:19
冲刷处理器缓存:将数据由处理器写到内存。 刷新处理器缓存,将数据由内存读到处理器。
3 回复 分享
发布于 2020-08-29 16:39
ReentrantLock和synchronized的区别: (1)是否支持公平锁 (2)显示加锁和释放锁 (3)是否可以中断 (4)是否可以获取锁状态,例如tryLock()
3 回复 分享
发布于 2020-06-21 12:29
打卡 一刷。
1 回复 分享
发布于 2020-09-10 09:35
讲的是啥啊
1 回复 分享
发布于 2020-04-13 19:25
我好菜啊T^T
1 回复 分享
发布于 2020-03-07 10:35
volatile那得底层实现还是不太懂
1 回复 分享
发布于 2020-01-04 20:39
打卡
点赞 回复 分享
发布于 2019-12-31 18:33
大家这一节如果不懂的可以去看看JMM,详细阐述了原子性,可见性,有序性
点赞 回复 分享
发布于 2021-09-28 11:57
volatile方式的i++,总共是四个步骤: i++实际为load、Increment、store、Memory Barriers 四个操作。 内存屏障是线程安全的,但是内存屏障之前的指令并不是.在某一时刻线程1将i的值load取出来,放置到cpu缓存中,然后再将此值放置到寄存器A中(寄存器A中保存的是中间值,没有直接修改i,因此其他线程并不会获取到这个自增1的值),然后A中的值自增1。如果在此时线程2也执行同样的操作(在线程1的寄存器中的i自增前),获取值i==10,自增1变为11,然后马上刷入主内存。此时由于线程2修改了i的值,实时的线程1中的i==10的值缓存失效(缓存一致性协议),重新从主内存中读取,变为11(但此时寄存器中的值是不会再刷新了)。接下来线程1恢复,寄存器中的i进行自增。将自增过后的A寄存器值11赋值给cpu缓存i,最终结果是11,而不是预计的两次自增12。这样就出现了线程安全问题。 链接:https://www.zhihu.com/question/329746124/answer/1205806238 是不是这样的,大家讨论讨论
点赞 回复 分享
发布于 2021-08-04 14:11
volatile不能保证原子性比如俩线程同时 x++
点赞 回复 分享
发布于 2021-03-02 14:24
请教一下,对于上面购票问题,如果多台机器,也就是分布式,使用synchronized是否可以解决?分布式中额度问题是个热点,也是个难点问题。目前都是用redis解决。请教还有其他什么高招吗?
点赞 回复 分享
发布于 2021-02-26 15:24
打卡
点赞 回复 分享
发布于 2021-02-23 14:47
“Java语言规范中,保证了除long和double型以外的任何变量的写操作都是原子操作” 你好,请问读操作是原子操作吗?
点赞 回复 分享
发布于 2020-10-08 10:11
二刷打卡
点赞 回复 分享
发布于 2020-08-20 11:12
synchronized实现可见性的时候,线程的寄存器也同时刷新了吧。如果处理器缓存刷新,线程不去读取,那数据也没有达到一致性。求博主帮忙解释一下。
点赞 回复 分享
发布于 2020-07-18 11:57
05/31 打卡
点赞 回复 分享
发布于 2020-06-01 10:50
楼主你好,在 Java语言规范中,保证了除long和double型以外的任何变量的写操作都是原子操作        这句话中,所说的变量是其余的6种基本变量吧
点赞 回复 分享
发布于 2020-05-22 10:36
打开
点赞 回复 分享
发布于 2020-02-25 16:08
看了网上的一些解释,都说volatile关键字并不能保证操作的原子性。所以博主前文提到的"Java语言规范中又规定,volatile关键字修饰的变量可以保证其写操作的原子性"是否严谨?
点赞 回复 分享
发布于 2020-02-25 11:48

相关推荐

不愿透露姓名的神秘牛友
昨天 11:30
点赞 评论 收藏
分享
06-08 22:25
门头沟学院 Java
从零开始的转码生活:这hr不会打开手机不分青红皂白给所有人群发这句话,过一会再给所有人再发一遍,这肯定会有重复的,不管,再过一会再发一遍
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
07-04 14:23
steelhead:你回的有问题,让人感觉你就是来学习的
点赞 评论 收藏
分享
评论
7
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务