3.1 共享带来的问题
两个线程对同一个数分别执行相同次数的+1和-1操作,那么最后的结果一定是0吗?
package com.sharing_model;
/**
* 共享问题:
* 两个线程对同一个数分别执行相同次数的+1和-1操作,那么最后的结果一定是0吗?
*/
public class SharingProblem {
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000 ; i++) {
num++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000 ; i++) {
num--;
}
}, "t2");
t1.start();
t2.start();
//让t1,t2线程都执行完成
t1.join();
t2.join();
System.out.println(num);
}
}
问题分析:
以上的结果可能是正数、负数、零。为什么?因为Java中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析
例如对i++而言(i为静态变量),实际会产生如下的JVM字节码指令:
而对应i–也是类似:
而Java的内存模型如下,完成静态变量的自增、自减需要在主存和工作内存中进行数据交换:
3.2 临界区
一个程序运行多个线程本身是没有问题的,问题出在多个线程访问共享资源,多个线程读共享资源其实也没有问题,问题出在多个线程对共享资源的读写操作时发生指令交错,就会出现问题
一段代码块中如果存在对共享资源的多线程读写操作,称这段代码为临界区
如下所示:
static int counter = 0;
//临界区
static void increment() {
counter++;
}
//临界区
static void decrement() {
counter--;
}
3.3 竞态条件
多个线程在临界区中执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
3.4 synchronize解决方案
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
-
阻塞式的解决方案:synchronized,Lock
-
非阻塞式的解决方案:原子变量
本次课使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的对象锁,同一时刻至多只有一个线程能持有对象锁,其他线程再想获取这个对象锁的时候就会阻塞住。这样就能保住拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
注意:
虽然Java中互斥和同步都可以采用synchronize关键字来完成,但它们还是有区别的:
- 互斥是保住临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
- 同步是由于线程执行的先后、顺序不同、需要一个线程等待其他线程运行到某个点
package com.sharing_model;
import java.io.ObjectInput;
/**
* Synchronized解决同步问题
*/
public class Synchronized {
static int counter = 0;
static Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
synchronized (lock) {
counter++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000 ; i++) {
synchronized (lock) {
counter--;
}
}
}, "t2");
t1.start();
t2.start();
System.out.println(counter);
}
}
3.5 synchronized面对对象改进
package com.sharing_model;
import javax.print.attribute.standard.Chromaticity;
import java.security.ProtectionDomain;
/**
* 锁对象面对对象改进
*/
public class Synchronized_OOP {
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();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.decrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(room.getNumber());
}
}
class Room {
private int number = 0;
public void increment() {
synchronized (this) {
number++;
}
}
public void decrement() {
synchronized (this) {
number--;
}
}
public int getNumber() {
synchronized (this) {
return number;
}
}
}