前言:欢迎访问ssm搭建个人博客 ----> 闫兆昌的博客
1 synchronized 和 Lock的区别
a) 存在层次
synchronized是关键字,在jvm层面上,由内置语言实现。
Lock是接口、类。
b) 锁的释放
synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。
Lock异常时不会自动释放锁,必须手动在finally中释放锁。
c) 锁的类型和状态
synchronized是非中断锁(必须等待线程执行完成释放锁)、不可判断(判断是否有锁),非公平(等待的时间不一样)。
Lock是可中断锁(可以手动中断)、可判断(判断是否有锁),公平(两者皆可)。
d) 使用 及 性能
synchronized用于少量代码的同步。
Lock用于大量代码的同步。
Lock锁可以使用读锁提高多线程读效率。
2 synochronied的缺点
a) 不能响应进行中断;
b) 同一时刻无论是读写都只能有一个线程对共享资源进行操作,其他线程只能进行等待。
c) 锁的释放是由虚拟机内部进行操作,不可进行人工操作干预,既有优点,又有缺点。
优点就是不用担心会造成死锁,抛出异常后,会自动释放该锁。
缺点就当有一个线程获取了对应的锁并执行之后,其他线程便只能一直等待,直到线程释放该锁,这样会导致性能偏低
解决:
Lock接口的提出就是为了完善synchronized得不完美,变成了jdk层面实现的接口,可以人工灵活的对共享资源变量的操作和控制,无论读还是写的操作。
3 Lock接口
源码中Lock接口的方法
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
a) lock()
最常用的方法,用来获取锁,如果该锁被其他线程获取之后,将会进入等待的状态。
b) tryLock()
有返回值,用来获取锁,如果获取成功返回true,获取失败返回false,执行后就一定会返回,并不会在那里一直等待。
c) tryLock(long time, TimeUnit unit)
和tryLock类似,只不过在拿不到锁的时候,会等待一段时间。此期间之间还拿不到的话,就会返回false。只要拿到了锁的话,就会返回true。
d) lockInterruptibly()
此方法和以上不同的是可以中断其等待的状态。
例如两个线程同时通过lock.lockInterruptibly()想获得某个锁时,假如A线程获得了该锁,B线程处于等待的状态,这是线程B就可以调用threadB.interrupt()方法来中断线程B的等待状态。
可中断,不必再和synchronized修饰的代码一样,当线程处于等待的时候不必一直等待。
注意:
因为lockInterruptibly()在生命的时候就已经抛出了异常,所以lock.lockInterruptibly()必须在try块中,或者在调用lockInterruptibly()方法外声明抛出InterrupExcption异常
4 ReentrantLock()类
ReentratLock是唯一实现了Lock接口的类,除了Lock接口中的方法,ReentrantLock还提供了更多的方法。
//传入boolean值,true时create一个公平锁,false为非公平锁
ReentrantLock(boolean fair)
//查看有多少线程等待锁
int getQueueLength()
//是否有线程等待抢锁
boolean hasQueuedThreads()
//是否有指定线程等待抢锁
boolean hasQueuedThread(Thread thread)
//当前线程是否抢到锁。返回0代表没有
int getHoldCount()
//查询此锁是否由任何线程持有
boolean isLocked()
//是否为公平锁
boolean isFair()
ReentrantLock中Condition的使用
public interface Condition {
/**
*Condition线程进入阻塞状态,调用signal()或者signalAll()再次唤醒,
*允许中断如果在阻塞时锁持有线程中断,会抛出异常;
*重要一点是:在当前持有Lock的线程中,当外部调用会await()后,ReentrantLock就允许其他线程来抢夺锁当前锁,
*注意:通过创建Condition对象来使线程wait,必须先执行lock.lock方法获得锁
*/
void await() throws InterruptedException;
//Condition线程进入阻塞状态,调用signal()或者signalAll()再次唤醒,不允许中断.如果在阻塞时锁持有线程中断,继续等待唤醒
void awaitUninterruptibly();
//设置阻塞时间,超时继续,超时时间单位为纳秒,其他同await();
//返回时间大于零,表示是被唤醒,等待时间并且可以作为等待时间期望值,小于零表示超时
long awaitNanos(long nanosTimeout) throws InterruptedException;
//类似awaitNanos(long nanosTimeout);返回值:被唤醒true,超时false
boolean await(long time, TimeUnit unit) throws InterruptedException;
//类似await(long time, TimeUnit unit)
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒指定线程
void signal();
//唤醒全部线程
void signalAll();
}
Condition 是Lock的一个条件,可以多次new Condition()获得多个条件.
Condition可用于线程间通信,通过Condition能更精细的控制休眠和唤醒,在粒度和性能上都优于Object的通信方法(wait()、notify() 和 notifyAll());
增加了线程中断、阻塞超时的函数;
使用ReentrantLock.Condition线程通信注意点:
1 使用ReentrantLock.Condition的signal()、await()、signalAll()方法之前必须先进行lock()的操作,finally中unlock()
类似于使用notifyAll()、wait()、notifyAll()等方法前需要有synchronized,否则会抛出IIIegalMonitorStateException异常
2 使用ReentrantLock.Condition的signal()、await()、signalAll()方法不能和Object的notifyAll()、wait()、notifyAll()方法混用,否则也会抛出IIIegalMonitorStateException异常
5 公平锁与非公平锁
公平锁:
是指多个线程竞争同一资源时[等待同一个锁]时,获得资源的顺序时按照申请锁的先后的顺序;保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁。
特点:
线程执行会严格按照顺序执行,等待锁的线程不会饿死,但是整体的效率会降低。
非公平性:
是指多个线程竞争同一资源时,获取资源的顺序是不确定的,一般是抢占式的;非公平锁相对公平锁式增加了获取资源的不确定性,但是整体效率得以提升。
特点:
整体效率高,线程等待的时间片不是确定的;
实现:
ReentrantLock fairLock = new ReentrantLock(true);
ReentrantLock unFairLock = new ReentrantLock();
当构造函数里面传入的是true时,此锁是公平的;传入是false时,此锁是不公平的;
6 可重入锁
可重入性,其实就是锁的分配的机制,就是当一个线程执行到synchronized修饰的test1方法时,test1中又会调用test2方法,此时线程不必再去申请锁,而是可以直接去执行。
class MyClass {
public synchronized void test1() {
test2();
}
public synchronized void test2() {
}
}
synchronized和Lock都具备可重入性。
7 读写锁
读写锁将对一个资源(比如文件)的方法分成了两个锁,一个读锁和一个写锁。
也正是存在读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock就是读写锁,也是一个接口,ReentrantReadWriteLock实现了这个接口,可以通过readLock()获取读锁,writeLock()获取写锁。
注意:
1. 多个读这可以同时进行读。
2. 只允许写者一个进行,也不能同时进行。
如果同时存在,读者必须等写者进行完,再执行,唤醒时也是写者优先(写者优先于读者)
例:
public class Demo{
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
final Demo demo= new Demo();
new Thread(){
public void run() {
demo= (Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
demo= (Thread.currentThread());
};
}.start();
}
public void get(Thread thread) {
rwl.readLock().lock();
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"读操作完毕");
} finally {
rwl.readLock().unlock();
}
}
}
结果 :
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0读操作完毕
Thread-1读操作完毕