写在前面
建议学习Java并发编程前,先学习一下jvm,这样可以理解的更清楚些。
一、共享带来的问题
1.上下文切换——分析
Java体现
两个线程对初始值为0的静态变量一个做自增,一个做自减,各做5000次,结果是0么?
static int counter = 0;
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(()->{
for(int i=0;i<5000;i++){
counter++;
}
},"t1");
Thread t2 = new Thread(()->{
for(int i=0;i<5000;i++){
counter--;
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",counter);
}
问题分析
例如对于i++而言(i为静态变量),实际会产生如下的JVM字节码指令:
getstatic i //获取静态变量i的值
iconst_1 //准备常量1
iadd //自增
putstatic i //将修改后的值存入静态变量i
而对于i–也是类似:
getstatic i //获取静态变量i的值
iconst_1 //准备常量1
isub //自减
putstatic i //将修改后的值存入静态变量i
- 1为-1的情况
- 为1的情况
2.临界区与竞态条件
临界区Critical Section
- 一个程序运行多个线程本身是没有问题的
- 问题出在多个线程访问共享资源
- 多个线程读共享资源其实也没有问题
- 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
- 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
例如,下面代码中的临界区
static int counter = 0;
static void increment()
//临界区
{
counter++;
}
static void decrement()
//临界区
{
counter--;
}
竞态条件Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。
二、synchronized解决方案
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案:synchronized,Lock
- 非阻塞式的解决方案:原子变量
本节先讲解使用synchronized来解决,其余方法将在后续讲到。加了synchronized的Java代码如下:
static int counter = 0;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(()->{
for(int i=0;i<5000;i++){
synchronized(lock){
counter++;
}
}
},"t1");
Thread t2 = new Thread(()->{
for(int i=0;i<5000;i++){
synchronized(lock){
counter--;
}
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",counter);
}