class ThreadProblem{
static int counter = 0;
public static void testThread(){
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();
try {
t1.join();
t2.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(counter);
}
}
以上的结果可能是整数、负数、零。为什么呢?因为java中对静态变量的自增自减并不是原子操作,要彻底理解,必须从字节码进行分析。
例如对于i++而言(i为静态变量),实际会产生如下的JVM字节码指令:
getstatic i //获取静态变量i的值
iconst_1 //准备变量1
1add //自增
putstatic //把修改后的值存入静态变量i
在多线程下这8行代码(8行代码指i++,i--的JVM字节码)可能交错运行:
static int counter = 0;
static void increment(){
//临界区
counter++;
}
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。
2.1 应用之互斥
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
本篇文章使用阻塞式的解决方案:synchronized,来解决竞态条件的发生,即俗称的【对象锁】,它采用互斥的方式让同一个时刻至多只有线程能持有【对象锁】,其他线程再想获取这个【对象锁】就会被阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心上下文切换。
注意:
虽然java中互斥和同步都可以采用synchronized关键字来完成,但他们还是有区别的:
- 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
- 同步是由于线程执行的先后顺序不同,需要一个线程等待其他线程运行到某个点
synchronized(对象){//对象可以使任意的,但要保证操作同一个【共享资源】使用同一个对象
临界区
}
class ThreadSynchronized{
static int counter = 0;
static Object room = new Object();
public static void testSynchronized(){
Thread t1 = new Thread(()-> {
for (int i = 0; i < 5000; i++) {
synchronized(room){
counter++;
}
}
},"t1");
Thread t2 = new Thread(()-> {
for (int i = 0; i < 5000; i++) {
synchronized(room) {
counter--;
}
}
},"t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(counter);
}
}
做一个类比:
synchronized实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
把需要保护的共享变量放入一个类
class Room{
private int value=0;
public void increment(){
synchronized (this){
value++;
}
}
public void decrement(){
synchronized (this){
value--;
}
}
public int get(){
synchronized (this){
return value;
}
}
}
public synchronized void decrement(){
value--;
}
相当于,锁对象是this对象
public void decrement(){
synchronized (this){
value--;
}
}
public synchronized static void decrement(){
value--;
}
相当于,锁对象是类对象
public void decrement(){
synchronized (Room.class){
value--;
}
}