阻塞式的解决方案synchronized
语法:
synchronized(对象) // 线程1, 线程2(blocked)
{
临界区
}
解决
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);
}
类比
如何理解这个synchronized(对象){}
,可以把这个对象想象成一个room(房间),有唯一的入口,一次只能进入一个人:
1、这时候有两个人,小明,小红(t1,t2)。当线程 t1 执行到 synchronized(room)
时就好比 小明(t1) 进入了这个房间,并锁住了门拿走了钥匙,在门内执行count++
代码。
2、这时候如果 小红(t2) 也运行到了 synchronized(room)
时,它发现门被锁住了,只能在门外等待,发生了上下文切换,阻塞住了。
3、这中间即使 t1 的 cpu 时间片不幸用完,被踢出了门外(不要错误理解为锁住了对象就能一直执行下去哦),这门还是锁住的,小明(t1) 仍拿着钥匙,小红(t2) 线程还在阻塞状态进不来,只有下次轮到 小明(t1) 自己再次获得时间片时才能开门进入。
4、当小明(t1) 执行完 synchronized{}
块内的代码,这时候才会从 obj 房间出来并解开门上的锁,唤醒 小红(t2) 线程把钥匙给他。小红(t2) 线程这时才可以进入 obj 房间,锁住了门拿上钥匙,执行它的 count--
代码
synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。(这是啥啊?)
- 如果把 synchronized(obj) 放在 for 循环的外面,如何理解?-- 原子性1
- 如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎样运作?-- 锁对象
- 如果 t1 synchronized(obj) 而 t2 没有加会怎么样?如何理解?-- 锁对象
方法上的 synchronized
synchronized还可以写到方法上,也同样能锁住对象,不能锁住方法。
class Test{
public synchronized void test() {
}
}
//等价于
class Test{
public void test() {
synchronized(this) {
}
}
}
synchronized()加在静态方法上,相当于锁住类对象,而不是锁住this对象
class Test{
public synchronized static void test() {
}
}
//等价于
class Test{
public static void test() {
synchronized(Test.class) {
}
}
}
不加 synchronized 的方法
不加 synchronzied 的方法就好比不遵守规则的人,不去老实排队(好比翻窗户进去的)
对synchronized有兴趣的同学可以去做做线程八锁
程序的原子性指:整个程序中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。 ↩︎