1.线程安全的概念:
想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:
如果多线程环境下代码运行的结果是复核我们预期的,即在单线程环境应该得到的结果,则这个程序是线程安全的。
2.观察线程不安全:
创建一个静态变量COUNT,创建20个线程每个线程对COUNT加加一万次,预期结果是20万。
public class UnsafeThread {
public static int COUNT;
public static void main(String[] args) throws InterruptedException {
//开启20个线程,每个线程对 COUNT++ 一万次
//预期结果20万
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
COUNT++;
}
}
}).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(COUNT);
}
}
结果:低于20万,而且每次运行完得到的结果不同。
3.线程不安全的原因:
(1)原子性:
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还
没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
(2)可见性:
为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线 程之间不能及时看到改变,这个就是可见性问题。
(3)有序性:
如果是在单线程情况下,JVM、CPU指令集会对代码执行顺序进行优化,以提高执行效率。这种做法叫做指令重排序。但是在多线程环境下,有可能上一个线程还没有把某个任务处理好,比如还没有给某个变量初始化就写回主内存了,而其他线程就直接用。这样就会产生问题了。
4.解决前面的不安全线程
public class SafetTest {
private static int COUNT;
//锁定的是 SafetTest 类对象
public synchronized static void fun() {
COUNT++;
}
public static void main(String[] args) {
Object object = new Object();
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
fun();
//COUNT++;
//this 指的是 Runnable 对象,每个线程都 new 一个。
synchronized (this) {
COUNT++;
}
}
}
});
thread.start();
}
// > 1 使用 debug 方式; > 2 使用 run 方法
while (Thread.activeCount() > 2) {
//线程让步,使得当前线程从运行态转变为就绪态。
Thread.yield();
}
System.out.println(COUNT);
}
}
synchronized的底层是使用操作系统的mutex lock实现的。
1.当线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中
2.当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内 存中读取共享变量
synchronized用的锁是存在Java对象头里的。
同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。
注意 synchronized 锁定的对象:
(1) synchronized 静态方法:锁定当前类对象
(2) synchronized 实例方法:锁定当前对象(this)
(3) synchronized(object):锁定object对象
5.volatile关键字
修饰的共享变量,可以保证可见性,部分保证有序性。
volatile不能保证原子性,对于变量的操作:
操作本身是原子性的,使用volatile就是线程安全的,否则是线程不安全。
volatile保证共享变量的可见性和有序性。
对于可见性,volatile修饰的变量直接从主内存中操作变量。从而保证共享变量的读取安全。
对于有序性,volatile修饰的变量禁止指令重排序。建立内存屏障,其他线程不可以中断volatile修饰变量的操作。
class ThraedDemo {
private volatile int n;
}