线程安全
对于下面这段代码,输出是什么?
package com.conrrentcy.thread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThreadSafeProblem {
private final static Object locker = new Object();
private static int count = 0;
private static final Logger log = LoggerFactory
.getLogger(ThreadSafeProblem.class);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count --;
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.info(" count value is {}", count);
}
}
以上的结果可能是整数也可能是负数也可能是0。因为java对静态变量的自增、自减并不是原子性的。要彻底理解,必须从字节码角度来看。
对于静态count++ 而言 ,会产生如下JVM的指令
getstatic count //获取静态变量的值
iconst_1 //准备常量1
iadd //自增
putstatic count // 将修改后的在值放入count
对于静态count-- 而言 ,会产生如下JVM的指令
getstatic count //获取静态变量的值
iconst_1 //准备常量1
isub //自减
putstatic count // 将修改后的在值放入count
java内存模型中,主存的每个线程的内存需要同步,主存是真正进行运算的地方, 主存运算完的值需要同步回线程内存。 在多个线程的情况下,如果主存的值可以被多个线程修改,那么当 同步回去时,就有极大可能和单独一个线程修改的值不同。这就产生了线程安全问题。
image.png如果指令按照线程顺序运行,就不会有任何问题
image.png如果出现交错,就可能有两种情况
-
负数
image.png -
正数
image.png
临界区
多线程的的问题主要是对共享资源发生读写指令交错,就会出现问题。
一个代码块如果存在对共享资源的多线程读写操作,称之为临界区。
竞态条件
多个线程在临界区运行,由于代码执行序列导致结果无法预测,称之为竞态条件。
互斥
避免竞态条件的发生,可以用以下方式:
- 阻塞式 synchronize lock
- 非阻塞式 原子变量
这章主要介绍synchronize 关键字的阻塞方法,就是对象锁。
package com.conrrentcy.thread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThreadSafeProblem {
private final static Object locker = new Object();
private static int count = 0;
private static final Logger log = LoggerFactory
.getLogger(ThreadSafeProblem.class);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (locker) {
count++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (locker) {
count--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.info(" count value is {}", count);
}
}
下面代码,运行结果每次都为0。
image.png
思考
- 如果synchronized 加在 for 循环外面
- 如果t1 synchronized object1 , t2 synchronized object2
- 如果t1 synchronized, t2不加
这三种情况各是什么行为?
synchronized 加在对象 和 方法上有什么区别?
变量的线程安全分析
成员变量和静态变量
- 如果他们没有共享,则线程安全
- 如果共享并且只有读操作,线程安全。
- 如果共享有读写操作,则需要考虑线程安全。(有可能只有写操作么?)
package com.conrrentcy.thread;
import java.util.ArrayList;
import java.util.List;
public class ThreadSafeReference {
public static void main(String[] args) throws InterruptedException {
ThreadUnSafeList tl = new ThreadUnSafeList();
for(int i=0; i<2;i++){
new Thread(()->{
tl.method1(200);
},"t"+i).start();
}
ThreadSafeList tl2 = new ThreadSafeList();
for(int i=0;i <2;i++){
new Thread(()->{
tl2.method1(200);
},"t"+i).start();
}
}
}
class ThreadUnSafeList {
private List list = new ArrayList();
public void method1(int loopNumber){
for( int i=0; i<loopNumber;i++){
method2();
method3();
}
}
private void method2(){
list.add("1");
}
private void method3(){
list.remove(0);
}
}
class ThreadSafeList {
public void method1(int loopNumber){
List list = new ArrayList();
for( int i=0; i<loopNumber;i++){
method2(list);
method3(list);
}
}
private void method2(List list){
list.add("1");
}
private void method3(List list){
list.remove(0);
}
}
比较一下ThreadUnSafeList 和 ThreadSafeList 有啥不同?下面是ThreadUnSafeList 的内存模型
image.png
局部变量
- 局部变量是线程安全的
- 局部变量引用的对象如果没有逃离方法的作用域,线程安全
- 局部变量引用的对象超出方法的作用域,需要考虑线程安全。
- 局部变量在栈帧中重建多份,不存在共享
public static void test(){
int i=0;
i++;
}
image.png
下面是刚才代码ThreadSafeList 的内存模型
image.png但是,光是局部变量也不能完全保证线程安全,需要保证在同一个线程内部没有起另外一个线程, 下面的代码就不是线程安全的,主要是在method3, 又另外起了一个线程。导致list 虽然对是一个local 变量,但是在method3, 内部就不再是一个线程变量了。
package com.conrrentcy.thread;
import java.util.ArrayList;
import java.util.List;
public class ThreadExpose {
public static void main(String[] args) {
ThreadSafeList2 tlc= new ThreadSafeListChild();
for(int i=0;i <2;i++){
new Thread(()->{
tlc.method1(200);
},"t"+i).start();
}
}
}
class ThreadSafeList2 {
public void method1(int loopNumber){
List list = new ArrayList();
for( int i=0; i<loopNumber;i++){
method2(list);
method3(list);
}
}
public void method2(List list){
list.add("1");
}
public void method3(List list){
list.remove(0);
}
}
class ThreadSafeListChild extends ThreadSafeList2{
public void method3(List list){
new Thread(()->{
list.remove(0);
},"tchild").start();
}
}
class modifier - private, final 能够加强线程安全