继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

高并发编程-04-线程的安全性

慕勒551688
关注TA
已关注
手记 250
粉丝 39
获赞 133

之前,我们在前面已经介绍过了线程的安全性,本篇我们将继续来深挖这个问题,继续来探讨什么线程安全,原子性及加锁机制。

1,什么是线程安全?

线程安全,有两个关键词,“共享”和“可变”。

共享是指可以被多个线程同时访问;

可变是指变量的值在生命周期内是可以变化的;

一个对象是否需要线程安全的,取决于它是否被多个线程访问;

而如何保证一个对象的线程安全,则需要采用同步机制来协同对对象可变状态的访问。

下面,我们给线程安全下一个明确的定义:当多个线程访问这个类时,如果这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

这里有我经常喜欢问面试者的一个问题,Servlet是线程安全的吗?

其实这个问题,并没有标准答案。这个问题的关键是看Servlet是有状态的,还是无状态的。什么叫有状态的?简单来说,就是在Servlet中定义了一个全局变量,然后在相关的doService或doGet、doPost等方法中,对这个全局变量做了更新操作,那么这个时候就说明是有状态的,反之就是无状态的。而当有状态时,就需要考虑线程安全的处理,否则不需要。

来,我们给大家一个结论:

无状态的对象一定是线程安全的。

多线程环境下,多个线程共享一个资源,且进行的不是原子性操作,这个时候就要考虑线程的安全控制问题

2,原子性

我们在之前的文章也说过这个问题,比如一个简单的操作,count++,其实是不具备原子性的,因为这个步骤实际会被拆分为三个步骤,即 读取---修改---写入,而这三个步骤有可能在某个时刻因CPU时间片的切换问题,而只执行其中一两个步骤,这就不具备原子性。

在JDK中,为了解决这个问题,java.util.concurrent.atomic包提供了很多的类,来保证数据操作的原子性,比如我们之前的程序可以修改为

public class System {

    private AtomicInteger integer = new AtomicInteger(0);

    public int getCount(){

        return integer.incrementAndGet();

    }

}

3,锁机制

通过上述的描述,我们知道,当一个类包含了一个有状态的变量时,只要采用一个线程安全的对象就可以搞定这个问题,但是,在这里要提醒大家一点,如果一个类包含了多个有状态的变量,这个时候可不是单纯的加几个线程安全的对象就可以搞定问题。

一个判断标准就是,只要程序中存在“先判断,再更新”,那么就要保证这两个操作在一个原子操作里面,才能保证线程安全。

关于java锁机制的一些特点,我在这跟大家罗列下:

内置锁、监视锁、互斥锁、可重入锁都是在这个锁的特点

内置锁、监视锁:这两个说的是一个意思,java的每一个对象都可以用来做内置锁,也就是为什么我们的wait、notify方法定义在Object类的原因。

互斥锁:表示最多只有一个线程可以持有这把锁。

可重入锁:是指当线程A请求一个由线程B持有的锁时,线程B会进入阻塞状态;而如果线程A如果再访问另一段代码,而这个代码的锁是已经被线程A持有的,这个时候请求是可以成功的,这就叫可重入。

跟大家讲讲这个锁的实现原理:

JVM为每个锁设置两个属性,获取计数值和所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程持有,当线程请求一个未被持有的锁时,JVM将记录锁的持有者,并且计数值+1。如果同一个线程再次获取这个锁,则计数值将递增,而当线程退出同步代码块时,计数器会相应递减,当计数值为0,这个锁将被释放。

大家如果有问题,欢迎留言,留言我也不一定有时间回答。。。。。。

binary viewer 可以查看字节码文件

通过javap -verbose *.class 也可以看到字节码内容


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP