1.有了synchronized,为什么还用Lock?Lock的应用场景
- 解决获取锁的等待问题
如果使用synchronized,线程A要想释放锁,要么线程A执行完毕,要么线程A执行发生异常才能释放锁。
当线程A执行遇到阻塞等情况,线程B要想获取这个锁,必须一直等到线程A释放锁后才能获取锁并执行线程B的程序。
而使用用Lock的tryLock(Long time)方法,可以使线程只等待一定的时间,不会一直等待下去。
- 读写锁的特别应用
我们知道,在读文件或者读数据库时,是不需要同步等待的,但是写则需要同步等待。
如果用synchronized,则不管读还是写都会同步等待。
而使用Lock的子类ReentrantReadWriteLock,则可实现读不同步,写同步的操作。
2.使用Lock要注意的地方
- synchronized是java的关键字,是基于JVM层面的;而Lock是java的类,是jdk层面的。
- synchronized系统会自动释放锁,但Lock不会自动释放,需要手工释放锁,切记切记,否则会造成死锁,只有重启系统了。
3.Lock的方法
public interface Lock {
//获取锁,没有获取锁,则一直等待,这个和synchronized类似
void lock();
//获取可中断的锁
void lockInterruptibly() throws InterruptedException;
//获取锁并直接返回结果,true代表获取到锁,false代表未获取到锁
boolean tryLock();
//获取锁,一定时间内获取,并返回结果
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//释放锁
void unlock();
//释放等待与唤醒线程,获取的Condition对象实例可以调用await()与signalAll()方法,就相当于synchronized的wait()与notifyAll()方法,这两个方法不明白的可以看我的线程四之线程的方法了解
Condition newCondition();
}
4.lock()方法,获取锁,没有获取锁,则一直等待,这个和synchronized类似
看下面的例子:
/**
* Created by
* Date : 2018/7/17 14:52
*/
public class Main {
private Lock lock=new ReentrantLock();
public static void main(String[] args){
final Main m=new Main();
for(int i=1;i<=3;i++){
new Thread(String.valueOf(i)){
public void run(){
m.getLock();
}
}.start();
}
}
public void getLock(){
lock.lock();
try{
System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}finally{
System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
}
}
执行结果:
线程1获取锁
线程1释放锁
线程2获取锁
线程2释放锁
线程3获取锁
线程3释放锁
其中要注意:private Lock lock=new ReentrantLock();这个不能放在方法中,如果放在方法中,每个线程则各自都会有自己独立的锁,就不会互斥了。
放在方法中的代码如下:
/**
* Created by
* Date : 2018/7/17 14:52
*/
public class Main {
public static void main(String[] args){
final Main m=new Main();
for(int i=1;i<=3;i++){
new Thread(String.valueOf(i)){
public void run(){
m.getLock();
}
}.start();
}
}
public void getLock(){
Lock lock=new ReentrantLock();
lock.lock();
try{
System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}finally{
System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
}
}
执行结果:
线程1获取锁
线程2获取锁
线程3获取锁
线程1释放锁
线程2释放锁
线程3释放锁
还有一个要注意的是:lock.lock()方法不能放在try中,因为如果是lock.lock()本身的方法抛异常,则跳到catch中,再跳到finally中,执行释放锁,还会抛出异常。
5.tryLock()方法,无论是否获取到锁,都会返回结果,不进行等待,其中tryLock(long time,TimeUnit timeUnit)方法是在一定时间内获取
举例如下:
/**
* Created by
* Date : 2018/7/17 14:52
*/
public class Main {
private Lock lock=new ReentrantLock();
public static void main(String[] args){
final Main m=new Main();
for(int i=1;i<=3;i++){
new Thread(String.valueOf(i)){
public void run(){
m.getTryLock();
}
}.start();
}
}
public void getTryLock(){
if(lock.tryLock()){
try{
System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}finally{
System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
}else{
System.out.println("线程"+Thread.currentThread().getName()+"未获取到锁");
}
}
}
执行结果:
线程1获取锁
线程2未获取到锁
线程3未获取到锁
线程1释放锁
从以上结果就可以看出,线程2和3获取不到锁就直接返回false。
再看看tryLock(long time,TimeUnit timeUnit)的使用方法:
/**
* Created by
* Date : 2018/7/17 14:52
*/
public class Main {
private Lock lock=new ReentrantLock();
public static void main(String[] args){
final Main m=new Main();
for(int i=1;i<=3;i++){
new Thread(String.valueOf(i)){
public void run(){
try{
m.getTryLock();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}.start();
}
}
public void getTryLock()throws InterruptedException{
if(lock.tryLock(2,TimeUnit.SECONDS)){
try{
System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}finally{
System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
}else{
System.out.println("线程"+Thread.currentThread().getName()+"未获取到锁");
}
}
}
执行结果:
线程2获取锁
线程2释放锁
线程3获取锁
线程1未获取到锁
线程3释放锁
我们通过结果看到,tryLock方法等待了两秒后,线程3获取到了锁,但线程1未获取到就返回了。
6.lockInterruptibly(),讲到这个方法时,我要吐槽一下,百度上查询这个方法,查出来的都是千篇一律的lock()与lockInterruptibly()的区别,好多文章都是一模一样的,讲的都是不明不白,没经过实践,真不知道直接复制过来有啥用。
用这个方法最主要是方便中断线程,但中断是有条件的,必须是阻塞情况下或者用sleep()等情况下调用interrupt()方法才会中断,否则不会中断,继续执行。其中要注意的是中断是使用线程的interrupt()方法。看下面的例子:
/**
* Created by
* Date : 2018/7/17 14:52
*/
public class Main {
private Lock lock=new ReentrantLock();
public static void main(String[] args)throws InterruptedException{
final Main m=new Main();
Thread t1=new Thread("t1"){
public void run(){
try{
m.getInterruptLock();
}catch (InterruptedException e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
}
public void getInterruptLock()throws InterruptedException{
lock.lockInterruptibly();
try{
System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
//耗时操作
long startTime = System.currentTimeMillis();
for( ; ; ) {
if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) {
break;
}
}
}catch (Exception e){
e.printStackTrace();
}finally{
System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
}
}
执行结果:
线程t1获取锁
这个程序在一直执行,直到循环结束释放锁。虽然调用了t1.interrupt();方法,但是线程并没有被中断,还在继续执行中。
如果我们将耗时操作换为sleep()方法,那么这个程序就会被中断,示例如下:
/**
* Created by
* Date : 2018/7/17 14:52
*/
public class Main {
private Lock lock=new ReentrantLock();
public static void main(String[] args)throws InterruptedException{
final Main m=new Main();
Thread t1=new Thread("t1"){
public void run(){
try{
m.getInterruptLock();
}catch (InterruptedException e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
}
public void getInterruptLock()throws InterruptedException{
lock.lockInterruptibly();
try{
System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
//耗时操作
TimeUnit.SECONDS.sleep(5);
}catch (Exception e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
e.printStackTrace();
}finally{
System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
}
}
执行结果:
线程t1获取锁
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:360)
at threadTest.lock.Main.getInterruptLock(Main.java:35)
at threadTest.lock.Main$1.run(Main.java:18)
线程t1释放锁
从结果来看,因为调用了sleep被中断了,所以打印了中断异常,注意,这儿的异常是被getInterruptLock()方法中的try-catch捕获的。
我们再来看看等待锁被中断的情况:
/**
* Created by WangZhiXin
* Date : 2018/7/17 14:52
*/
public class Main {
private Lock lock=new ReentrantLock();
public static void main(String[] args)throws InterruptedException{
final Main m=new Main();
Thread t1=new Thread("t1"){
public void run(){
try{
m.getInterruptLock();
}catch (InterruptedException e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
}
}
};
Thread t2=new Thread("t2"){
public void run(){
try{
m.getInterruptLock();
}catch (InterruptedException e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
e.printStackTrace();
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(1);
t2.start();
TimeUnit.SECONDS.sleep(2);
t2.interrupt();
}
public void getInterruptLock()throws InterruptedException{
lock.lockInterruptibly();
try{
System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
//耗时操作
long startTime = System.currentTimeMillis();
for( ; ; ) {
if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) {
break;
}
}
}catch (Exception e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
e.printStackTrace();
}finally{
System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
}
}
执行结果:
线程t1获取锁
线程t2中断锁
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
at threadTest.lock.Main.getInterruptLock(Main.java:44)
at threadTest.lock.Main$2.run(Main.java:28)
从结果来看,线程t1获取了锁,t2则一直等待t1释放锁,但是在等待的过程中被中断了,便抛出了中断异常。
网上还有一个坑人的地方,就是网上好多示例把lock.lockInterrupt()方法放在了try-catch中,这个其实做法很不规范,看下面的例子:
/**
* Created by
* Date : 2018/7/17 14:52
*/
public class Main {
private Lock lock=new ReentrantLock();
public static void main(String[] args)throws InterruptedException{
final Main m=new Main();
Thread t1=new Thread("t1"){
public void run(){
try{
m.getInterruptLock();
}catch (InterruptedException e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
}
}
};
Thread t2=new Thread("t2"){
public void run(){
try{
m.getInterruptLock();
}catch (InterruptedException e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(1);
t2.start();
TimeUnit.SECONDS.sleep(2);
t2.interrupt();
}
public void getInterruptLock()throws InterruptedException{
try{
lock.lockInterruptibly();
System.out.println("线程"+Thread.currentThread().getName()+"获取锁");
//耗时操作
long startTime = System.currentTimeMillis();
for( ; ; ) {
if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) {
break;
}
}
}catch (Exception e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
}finally{
System.out.println("线程"+Thread.currentThread().getName()+"释放锁");
lock.unlock();
}
}
}
执行结果:
线程t1获取锁
线程t2中断锁
线程t2释放锁
Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at threadTest.lock.Main.getInterruptLock(Main.java:57)
at threadTest.lock.Main$2.run(Main.java:28)
从结果看到没,抛出了IllegalMonitorStateException异常,而不是中断异常,为什么会抛出这个异常?是因为线程t2在调用lock.lockInterruptibly()时,锁一直被t1持有,所以一直在等待锁,而这时候t2又中断了线程,所以就会抛出中断的异常,但是最后有个finally执行lock.unlock();因为t2并未获取到锁,这儿却又调用了释放锁,所以抛出了IllegalMonitorStateException异常。
7.ReentrantReadWriteLock,这个类是用于读写的类,读异步,写同步,直接看例子:
/**
* Created by WangZhiXin
* Date : 2018/7/18 16:46
*/
public class ReadAndWrite {
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public static void main(String[] args){
final ReadAndWrite rw=new ReadAndWrite();
for(int i=1;i<=10;i++){
new Thread(String.valueOf(i)){
public void run(){
rw.read();
}
}.start();
}
for(int i=11;i<=12;i++){
new Thread(String.valueOf(i)){
public void run(){
rw.write();
}
}.start();
}
}
public void read(){
rwLock.readLock().lock(); // 在外面获取锁
try {
long start = System.currentTimeMillis();
System.out.println("线程" + Thread.currentThread().getName() + "开始读操作...");
while (System.currentTimeMillis() - start <= 1) {
}
System.out.println("线程" + Thread.currentThread().getName() + "读操作完毕...");
} catch (Exception e){
e.printStackTrace();
} finally {
rwLock.readLock().unlock();
}
}
public void write(){
rwLock.writeLock().lock(); // 在外面获取锁
try {
long start = System.currentTimeMillis();
System.out.println("线程" + Thread.currentThread().getName() + "开始写操作...");
while (System.currentTimeMillis() - start <= 1) {
}
System.out.println("线程" + Thread.currentThread().getName() + "写操作完毕...");
} catch (Exception e){
e.printStackTrace();
} finally {
rwLock.writeLock().unlock();
}
}
}
执行结果:
线程1开始读操作...
线程1读操作完毕...
线程3开始读操作...
线程4开始读操作...
线程4读操作完毕...
线程5开始读操作...
线程3读操作完毕...
线程7开始读操作...
线程7读操作完毕...
线程5读操作完毕...
线程9开始读操作...
线程9读操作完毕...
线程11开始写操作...
线程11写操作完毕...
线程8开始读操作...
线程8读操作完毕...
线程12开始写操作...
线程12写操作完毕...
线程2开始读操作...
线程2读操作完毕...
线程6开始读操作...
线程6读操作完毕...
线程10开始读操作...
线程10读操作完毕...
从结果可以看出,有的读操作还没读完,就已经执行了另一个读操作;但是写操作是必须执行完才会再执行其它的读和写操作。
8.公平锁与非公平锁
故名思义:
公平锁就是按执行顺序获取锁,用new ReentrantLock(true)表示。
非公平锁就是无序的竞争锁,用new ReentrantLock(false)或new ReentrantLock()表示。
直接上代码:
/**
* Created by WangZhiXin
* Date : 2018/7/17 14:52
*/
public class Main {
private Lock lock=new ReentrantLock(true);
public static void main(String[] args)throws InterruptedException{
final Main m=new Main();
for(int i=1;i<10;i++){
new Thread(String.valueOf(i)){
public void run(){
System.out.println("线程"+Thread.currentThread().getName()+"执行");
m.test();
}
}.start();
}
}
public void test(){
lock.lock();
try{
System.out.println("线程"+Thread.currentThread().getName()+"拿到锁");
}catch (Exception e){
System.out.println("线程"+Thread.currentThread().getName()+"中断锁");
}finally{
lock.unlock();
}
}
}
执行结果:
线程4执行
线程9执行
线程4拿到锁
线程9拿到锁
线程5执行
线程5拿到锁
线程1执行
线程1拿到锁
线程8执行
线程8拿到锁
线程2执行
线程2拿到锁
线程6执行
线程6拿到锁
线程3执行
线程3拿到锁
线程7执行
线程7拿到锁
从结果看到没,都是按照线程的执行顺序获得锁的。谁先执行,谁就先拿到锁。
再看看非公平锁的情况:仅仅是将private Lock lock=new ReentrantLock(true);换成private Lock lock=new ReentrantLock();
执行结果为:
线程2执行
线程2拿到锁
线程3执行
线程3拿到锁
线程1执行
线程1拿到锁
线程7执行
线程7拿到锁
线程8执行
线程4执行
线程8拿到锁
线程9执行
线程9拿到锁
线程5执行
线程6执行
线程5拿到锁
线程4拿到锁
线程6拿到锁
看看结果,尤其是看线程4的结果,线程4在线程9之前执行的,但是线程4却在线程9之后才获取锁。
9.newCondition(),其实这个往简单了看,你就当它和synchronized的wait()与notifyAll()一样就行
看下面的例子:
/**
* Created by
* Date : 2018/7/19 9:32
*/
public class LockCondition {
private Lock lock=new ReentrantLock();//创建lock锁
private Condition newCondition = lock.newCondition();//创建条件对象
public static void main(String[] args)throws InterruptedException{
LockCondition lc=new LockCondition();
lc.await();
TimeUnit.SECONDS.sleep(2);
System.out.println("开始唤醒线程");
lc.testNotify();
}
public void await(){
for(int i=1;i<=5;i++){
new Thread(String.valueOf(i)){
public void run(){
lock.lock();
System.out.println("线程"+Thread.currentThread().getName()+"执行线程");
try{
newCondition.await();//释放锁并等待
}catch (InterruptedException e){
e.printStackTrace();
}finally {
System.out.println("线程"+Thread.currentThread().getName()+"结束线程");
lock.unlock();
}
}
}.start();
}
}
public void testNotify(){
lock.lock();
try{
newCondition.signalAll();//唤醒锁
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
执行结果:
线程1执行线程
线程2执行线程
线程3执行线程
线程4执行线程
线程5执行线程
开始唤醒线程
线程1结束线程
线程2结束线程
线程3结束线程
线程4结束线程
线程5结束线程
从结果上来看,是不是和wait()与notifyAll()是一样的道理。
如果当newCondition.signalAll()改为newCondition.signal(),则和notify()是一样的,会随机取一个线程唤醒,执行结果如下:
线程1执行线程
线程2执行线程
线程5执行线程
线程4执行线程
线程3执行线程
开始唤醒线程
线程1结束线程