@Slf4j
public class ShareTest01 {static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for(int i = 0; i < 5000; i++){count++;}});Thread t2 = new Thread(() -> {for(int i = 0; i < 5000; i++){count--;}},"t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}",count);}
}
问题分析:
对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i
对于 i- - 而言(i为静态变量), 实际会产生如下的JVM字节码指令:
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 自减
putstatic i // 将修改后的值存入静态变量i
而java的内存模型如下,完成静态变量的自增、自减需要在主存和工作内存中进行数据交换。(单线程下不会出现问题)
static int count = 0;
static void increment()
// 临界区
{ count++;
}
static void decrement()
// 临界区
{ count--;
}
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
为了避免临界区的竞态条件发生
synchronized 也就是速成的 【对象锁】,它采用互斥的方式让同一 时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁 的线程可以安全的执行临界区内的代码,不用担心线程上下文切换。
synchronized(对象) // 线程1, 线程2(blocked)
{临界区
}
解决方案
@Slf4j
public class ShareTest02 {static int count = 0;// 创建一个对象,想成一个房间,一回里面只能有一个static final Object room = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for(int i = 0; i < 5000; i++){// 房间大门synchronized(room){count++;}}});Thread t2 = new Thread(() -> {for(int i = 0; i < 5000; i++){// 房间大门synchronized(room){count--;}}},"t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}",count);}
}
使用面向对象的思想,将需要保护的共享变量放入一个类中
@Slf4j
public class ShareTest03 {public static void main(String[] args) throws InterruptedException {Room room = new Room();Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++){room.increment();}},"t1");Thread t2 = new Thread(() -> {for (int j = 0; j < 5000; j++){room.decrement();}},"t2");t1.start();t2.start();t1.join();t2.join();log.debug("count: {}" , room.getValue());}}class Room {int value = 0;public void increment(){synchronized (this){value++;}}public void decrement(){synchronized (this){value--;}}public int getValue(){synchronized (this){return value;}}}
class Test{public synchronized void test() {}}等价于class Test{public void test() {synchronized(this) {}}}class Test{public synchronized static void test() {}}等价于class Test{public static void test() {synchronized(Test.class) {}}}
不加synchronized的方法在多线程中就会发生混乱,指令交错。
@Slf4j
public class ThreadSafe01 {public static void main(String[] args) {Thread t1 = new Thread(() -> {int i = 10;i++;log.debug("局部变量i:{}",i);});Thread t2 = new Thread(() -> {int i = 10;i++;log.debug("局部变量i:{}",i);});t1.start();t2.start();}}
局部变量 i ,并没有共享,线程是安全的
@Slf4j
public class ThreadMemberTest01 {public static void main(String[] args) {ThreadUnSafe unSafe = new ThreadUnSafe();Thread t1 = new Thread(() -> {unSafe.method1(5000);log.debug("成员变量:{}",Arrays.asList(unSafe));});Thread t2 = new Thread(() -> {unSafe.method1(5000);log.debug("成员变量:{}",Arrays.asList(unSafe));});t1.start();t2.start();}}class ThreadUnSafe {ArrayList list = new ArrayList<>();public void method1(int loopNum){for (int i = 0; i < loopNum; i++){// 临界区 产生竞态条件method2();method3();}}private void method3() {list.remove(0);}private void method2() {list.add("1");}
}
这个多运行几次。
@Slf4j
public class ThreadMemberTest02 {public static void main(String[] args) {ThreadUnSafe unSafe = new ThreadUnSafe();Thread t1 = new Thread(() -> {unSafe.method1(5000);log.debug("成员变量:{}",Arrays.asList(unSafe));});Thread t2 = new Thread(() -> {unSafe.method1(5000);log.debug("成员变量:{}",Arrays.asList(unSafe));});t1.start();t2.start();}}
class ThreadSafe {public final void method1(int loopNum){ArrayList list = new ArrayList<>();for (int i = 0; i < loopNum; i++){// 临界区 产生竞态条件method2(list);method3(list);}}private void method3( ArrayList list) {list.remove(0);}private void method2( ArrayList list) {list.add("1");}}
方法访问修饰符带来的思考,如果把method2和method3的方法修改为public会不会代理线程安全问题?
class ThreadSafe{public void method1(int loopNumber) {ArrayList list = new ArrayList<>();for (int i = 0; i < loopNumber; i++) {method2(list);method3(list);}}public void method2(ArrayList list) { list.add("1"); }public void method3(ArrayList list) { list.remove(0); }
}class ThreadSafeSubClass extends ThreadSafe{@Overridepublic void method3(ArrayList list) {new Thread(() -> {list.remove(0);}).start();}
}
此时会出现线程安全问题,因为method1创建list对象操作list,调用method3时会创建另一个线程操作这个list,多个线程共享list,将出现线程安全问题。
String
Integer
StringBuffer
Random
Vector
Hashtable
java.util.concurrent 包下的类
线程安全指的是,多个线程调用他们同一实例的某个方法是线程安全的