Java并发编程实战笔记二(加锁机制)
2.2 加锁机制
当多个线程对于共享变量进行并发访问时我们需要通过加锁来保证它的线程安全性。
2.2.1 内置锁
Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象作为锁。
简单的来说就是synchronized的两种用法:
- 对象锁:方法(锁对象默认this)和同步代码块
- 类锁:静态方法和锁Class对象
//对象锁
public synchronized void test1(){
}
//默认为this
public void test2(){
synchronized (this) { //手动指定类对象
}
}
//类锁
public static synchronized void test3(){
}
public void test4(){
synchronized (SynchronizedTest.class) {
}
}
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块(无论是正常流程退出还是异常退出)时自动释放锁。获得内置锁的唯一途径就是进入由这个锁保护代码的同步代码块或方法。
Java的内置锁相当于一种互斥体(或互斥锁),这意味着最多只有一个线程能持有这种锁。当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或阻塞,直到线程B释放锁,如果线程B永远不释放,那么线程A将永远地等待下去。
2.2.1 重入
一个线程可以多次获取同一把锁。如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是“线程”。重入的一种实现方法是:为每个锁关联一个获取计数值和一个所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将计下锁的持有者,并且将计数值加1;如果同一个线程再次获得这个锁,计数值将递增,而线程退出同步代码块时,计数器会相应递减。当计数值为0时,这个锁将被释放。
模拟示例:
// 重入锁
public synchronized void reentrantLock() { // count + 1 = 1
synchronized (this) { // count + 1 = 2
} // 代码块执行完毕 count - 1 = 1
} // 方法执行完毕 count - 1 = 0 释放锁
如果内置锁是不可重入的,那么上面这段代码将产生死锁,线程进入方法时获取到当前对象锁,执行代码块时因为这个对象锁已经被持有,从而线程将永远的停顿下去,等待一个永远也无法获得的锁,重入则避免了这种死锁的发生。
重入进一步的提升了加锁行为的封装性,因此简化了面向对象并发代码的开发。
tips:
对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。
当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或耗时任务),一定不要持有锁。
上一篇: 数据库优化之sql优化
下一篇: 红豆冰沙制作的时候需要注意些什么呢