4.2 synchronized 解决方案
- 1、应用之互斥
- 2、synchronized
- 3、思考
- 4、面向对象改进
1、应用之互斥
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案:synchronized,Lock
- 非阻塞式的解决方案:原子变量
本次课使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心多线程操作共享变量
2、synchronized
语法
synchronized(对象) // 线程1, 线程2(blocked)
{
临界区
}
利用synchronized 解决上一章的counter问题
public class TestCounterSync {
static int counter = 0;
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
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();
t1.join();
t2.join();
log.debug("{}",counter);
}
}
使用图来解释为什么synchronize解决了共享问题
3、思考
synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
为了加深理解,请思考下面的问题:
如果把 synchronized(obj) 放在 for 循环的外面,如何理解?
整个循环的 counter ++ 操作时不可分割的,依然可以保证原子性
如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎样运作?
由于使用的锁对象是不同的对象,并不能保证代码块的原子性
如果 t1 synchronized(obj) 而 t2 没有加会怎么样?如何理解?
没有使用锁的线程依然可以进入到操作共享变量的代码块中,不能保证原子性
4、面向对象改进
把需要保护的共享变量放入一个类
class Room {
int value = 0;
public void increment() {
synchronized (this) {
value++;
}
}
public void decrement() {
synchronized (this) {
value--;
}
}
public int get() {
synchronized (this) {
return value;
}
}
}
public class TestCounterOop {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
room.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
room.decrement();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("count: {}" , room.get());
}
}