Synchronized
可重入锁:当一个线程得到一个对象的锁后,其他线程不可再获得此锁,但是此线程可以再次请求获得其对象的锁,这样就称之为可重入锁。可重入锁保证了一个线程获得锁之后可以再次请求锁,能够在一定程度上减少程序死锁的几率。
synchronized和ReentrantLock都是可重入锁。
synchronized的解锁过程是由JVM自动控制,Lock需要手动解锁。
通过synchronzed获取对象锁的两种方式:
- 通过执行对象的synchronized方法
- 通过执行对象的synchronized代码块
Lock
Lock是一个接口,其下有各种类型的锁,必须手动释放锁(unlock()方法),相较于synchronized,扩展了许多对锁的操作。
比如synchronized一个线程在获得锁之后没有释放,另一个线程再请求锁就会陷入阻塞状态,但lock则未必,boolean tryLock()可以尝试获取一个锁,如果锁没有被其他线程持有,可以获得锁,否则无法获得锁,但不会陷入阻塞状态。
ReentrantLock(可重入锁)为Lock接口下的一种锁,ReentrantLock即可公平锁又可非公平锁。synchronized为非公平锁。
公平锁能保证等待时间最长的线程获得锁,ReentrantLock中公平锁的说明:
-ReentrantLock方法
- int getHoldCount() //获取持有当前锁的线程数
- boolean hasWaiters(Condition condition) //当前condition是否有等待的线程
- boolean isFair() //是否为公平锁
- boolean isHeldByCurrentThread() //锁是否被当前线程持有
- boolean isLocked() //当前锁是否被任何线程持有
- void lockInterruptibly() //只有当前线程interrupted才会获取锁
- boolean tryLock() //尝试获取锁,如果没有被其他线程持有,获取到锁,否则停止获取锁
- Condition newCondition() //为当前Lock获取一个Condition实例
从这些方法中可以发现ReentrantLock扩展了对锁的操作,使锁操作更加灵活多样。
-ReentrantLock通信
使用Lock加锁时,线程之间的通信使用Condition。
Condition中的await()相当于wait(),singal()相当于notify(),signalAll()相当于notifyAll()。
case1:生产者/消费者
不管是synchronized还是Lock,在构思代码的时候,一定要明确被上锁的是谁。
在生产者/消费者案例案例中,消费者和生产者线程共同操作产品(Product),要获取的对象锁是产品(Product),所以lock/synchronized应该放在Product类中,这样线程执行到lock/synchronized代码,才会获取到lock/synchronized所在对象的锁。
使用ReentrantLock和Condition来实现生产者/消费者案例:
Product.java:
public class Product {
//标志位,flag为true,可以生产,不可以取走
private boolean flag=true;
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void produce() {
lock.lock();
//falg为true,可以生产,false不可以生产
if (!flag) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"生产一件产品");
//取走之后更改标志位
flag=false;
try {
Thread.sleep(100);
//唤醒消费者线程
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
//flag为true,不可以取走
if (flag) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"取走一件产品");
//取走之后更改标志位
flag=true;
try {
Thread.sleep(100);
//唤醒生产者线程
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}
Producer.java:
public class Producer implements Runnable {
private Product product;
public Producer(Product product) {
this.product = product;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
product.produce();
}
}
}
Consumer.java:
public class Consumer implements Runnable {
private Product product;
public Consumer(Product product) {
this.product = product;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
product.consume();
}
}
}
Main.java:
public class Main {
public static void main(String[] args) {
Product product = new Product();
Producer p = new Producer(product);
Consumer c = new Consumer(product);
new Thread(p, "生产者-").start();
new Thread(c, "消费者-").start();
}
}
case2 :多个Condition进行通信
在synchronized中,notify()随机唤醒所有等待线程中的一个,Condition可以对线程进行分类管理,sinal()只会随机唤醒相应Condition中的await()线程。
public class MultCondition {
ReentrantLock lock = new ReentrantLock();
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
public void await(Condition condition,String conditionName) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+" "+conditionName+":wait begin");
condition.await();
System.out.println(Thread.currentThread().getName()+" "+conditionName+":wait end");
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void signal(Condition condition,String conditionName) {
lock.lock();
System.out.println(Thread.currentThread().getName()+" "+conditionName+":notified");
condition.signal();
lock.unlock();
}
public static void main(String[] args) {
MultCondition test = new MultCondition();
new Thread(() -> test.await(test.conditionA,"conditionA"),"Thread-1").start();
new Thread(() -> test.await(test.conditionB,"conditionB"),"Thread-2").start();
new Thread(() -> test.signal(test.conditionB,"conditionB"),"Thread-3").start();
//注释Thread-4,运行程序后手动Terminate
//new Thread(() -> test.signal(test.conditionA,"conditionA"),"Thread-4").start();
}
}
-ReentrantReadWriteLock
ReentrantReadWriteLock实现自ReadWriteLock。ReentrantLock具有完全互斥性,即只能有一个线程执行lock()的内容,而ReentrantReadWriteLock有读锁和写锁,读锁为共享锁,写锁为排它锁,即多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。
sum:synchronized使用起来简单方便,而Lock则是对synchronized扩展了对锁的各种功能,在线程之间不需要复杂通讯,或者程序功能并不复杂情况下,可以选择使用synchronized,反之则使用Lock。