之前,我们在前面已经介绍过了线程的安全性,本篇我们将继续来深挖这个问题,继续来探讨什么线程安全,原子性及加锁机制。
1,什么是线程安全?
线程安全,有两个关键词,“共享”和“可变”。
共享是指可以被多个线程同时访问;
可变是指变量的值在生命周期内是可以变化的;
一个对象是否需要线程安全的,取决于它是否被多个线程访问;
而如何保证一个对象的线程安全,则需要采用同步机制来协同对对象可变状态的访问。
下面,我们给线程安全下一个明确的定义:当多个线程访问这个类时,如果这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
这里有我经常喜欢问面试者的一个问题,Servlet是线程安全的吗?
其实这个问题,并没有标准答案。这个问题的关键是看Servlet是有状态的,还是无状态的。什么叫有状态的?简单来说,就是在Servlet中定义了一个全局变量,然后在相关的doService或doGet、doPost等方法中,对这个全局变量做了更新操作,那么这个时候就说明是有状态的,反之就是无状态的。而当有状态时,就需要考虑线程安全的处理,否则不需要。
来,我们给大家一个结论:
无状态的对象一定是线程安全的。
2,原子性
我们在之前的文章也说过这个问题,比如一个简单的操作,count++,其实是不具备原子性的,因为这个步骤实际会被拆分为三个步骤,即 读取---修改---写入,而这三个步骤有可能在某个时刻因CPU时间片的切换问题,而只执行其中一两个步骤,这就不具备原子性。
在JDK中,为了解决这个问题,java.util.concurrent.atomic包提供了很多的类,来保证数据操作的原子性,比如我们之前的程序可以修改为
注明:之前的程序相关文章https://www.jianshu.com/p/369d23609470
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,这个锁将被释放。
大家如果有问题,欢迎留言,留言我也不一定有时间回答。。。。。。
网友评论