案例分析
创建两个线程 t1 、t2 ,t1 线程负责变量 count 自增 5000 次,t2 线程负责变量 count 自减 5000 次
public class AddAndSub {
static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
count++;
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
count--;
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
由于线程执行的顺序不受控制,可能会发生如下情况:
假设此时 count 值为 0 ,t1 线程读取到 count 的值,进行自增后变成 1 ,还没来得及写入 count ,t2 线程就读取了 count 原来的值 0 ,进行自减后,将 -1 写入 count ,然后 t1 线程将刚刚自增得到的结果 1 写入 count ,最终导致了分别进行一次自增自减后,count 的值从 0 变成了 1
概念介绍
临界资源
一次仅允许一个进程使用的共享资源,如物理设备中的打印机、输入机和进程之间共享的变量、数据
临界区(Critical Section)
每个进程中访问临界资源的代码段,如上述案例中的 count++
竟态条件(Race Condition)
两个或者多个进程竞争使用临界资源,这些进程可能因为执行先后顺序的不受控制而出现问题,如上述案例中的 t1 和 t2 线程执行顺序不受控制,导致结果错误
synchronized 实现互斥
static Object lock = new Object();
synchronized (lock) {
count++;
}
synchronized (lock) {
count--;
}
使用 synchronized 关键字对 lock 对象加锁,当其中一个线程对 count 进行自增或自减操作时,另外的线程由于获取不到 lock 锁,被阻塞,保证了临界区内代码的原子性
封装共享资源
public class AddAndSubOptimize {
public static void main(String[] args) throws InterruptedException {
Resource resource = new Resource();
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
resource.increment();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
resource.decrement();
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(resource.getCount());
}
}
class Resource {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public void decrement() {
synchronized (this) {
count--;
}
}
public int getCount() {
synchronized (this) {
return count;
}
}
}
通过 Resource 类封装了共享资源(变量)count ,对变量操作的互斥逻辑在类的内部实现,外部只需要调用相应的方法即可