问题
多线程分时系统是存在线程安全问题的,如下例子:
两个线程分别对同一个变量(初始值 = 0)做循环自增和自减操作各50000次,观察结果,并不等于初始值。
public class 分时系统线程安全问题 {
static int a = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
a++;
}
},"t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
a--;
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(a);
}
}
出现以上问题的原因就是,看似单个的Java语句(a++、a--)会被操作系统拆分为多几条指令。自增自减并不是原子操作。在多线程的情况下下面的8条指令是会交错执行的,必然导致结果!=预期
a++:
getstatic i //获取静态常量的值
iconst_1 //准备常量1
iadd //自增
putstatic //讲修改后的值写入静态变量i
a--:
getstatic i //获取静态常量的值
iconst_1 //准备常量1
isub //自减
putstatic //讲修改后的值写入静态变量i
临界区 Critical Section
一个程序运行多个线程本身没有问题,问题出在多个线程访问了共享资源,多个读写共享资源其实也没问题,但是多个线程在访问共享资源是出现了指令交错就会有问题。
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
static int counter = 0;
static void increment(){
//临界区
counter++;
}
static void decrement(){
//临界区
counter--;
}
竞态条件 Race condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
解决问题
1、应用之互斥
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
阻塞式的解决方案:synchronized,Lock
非阻塞式的解决方案:原子变量
synchronized
语法
synchronize(对象){
//临界区
}
static int a = 0;
/**
* 对象锁
*/
static Object lock = new Object();
/**
* 线程安全
*/
public static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
synchronized (lock) {
a++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
synchronized (lock) {
a--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(a);
}
synchronize利用对象锁保证了临界区内的代码块的原子性(不可分割)。
过程分析:
思考:
1、synchronize放在for循环外面能保证结果正确吗?(--原子性)
synchronized (lock) {
for (int i = 0; i < 50000; i++) {
a++;//a--;
}
}
答案:可以的synchronize关键字包含的代码块为一个整体不可分割。
2、t1里面synchronize(lock1)、t2里面synchronize(lock2)能保证结果正确吗?(--锁对象)
答案:不能,加锁要是同一个对象锁、保护共享资源,必须保证同一个对象锁。
3、t1加锁 synchronize(lock),t2不加锁能保证结果正确吗?(--锁对象)
答案:不能,同问题2.
面向对象的优化:
public class 分时系统线程安全问题2 {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
room.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
room.decrement();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(room.getA());
}
}
class Room {
private int a = 0;
public void increment() {
synchronized (this) {
a++;
}
}
public void decrement() {
synchronized (this) {
a--;
}
}
public int getA() {
return a;
}
}
synchronize的使用发:
1、加在普通法放上。
class Test{
public synchronized void test() {
}
}
//等价于
class Test{
public void test() {
synchronized(this) {
}
}
}
2、加在静态方法上
class Test{
public synchronized static void test() {
}
}
//等价于
class Test{
public static void test() {
synchronized(Test.class) {
}
}
}
标签:Thread,++,t2,t1,synchronize,int,多线程,public,分时系统
From: https://www.cnblogs.com/ieas/p/16735928.html