以下代码均通过使线程睡眠模拟实际业务场景来解释原理
Case1(reentrantlock替代synchronized):
package ReentrantLock;
import java.util.concurrent.TimeUnit;
class ReentrantLock1 {
synchronized void m1(){
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
synchronized void m2(){
System.out.println("m2.....");
}
public static void main(String[] args) {
ReentrantLock1 r1=new ReentrantLock1();
new Thread(r1::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r1::m2).start();
}
}
/*====================output========================
0
1
2
3
4
5
6
7
8
9
m2.....
*/
使用synchronized 编程时,写法如上,上例中由于m1锁定this,只有m2执行完毕的时候,m2才能执行。下面使用ReentrantLock来替代synchronized :
package ReentrantLock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ReentrantLock2 {
//Lock是ReentrantLock实现的接口
Lock lock=new ReentrantLock();
void m1(){
lock.lock(); //相当于synchronized(this)
try {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally { //finally一定得写,用来手工释放锁
lock.unlock();
}
}
void m2(){
lock.lock();
try {
System.out.println("m2....");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLock2 r1=new ReentrantLock2();
//映射方式start
//new Thread(r1::m1).start();
/*lambda表达式写法
new Thread(()->{
r1.m1();
},"t1").start();
*/
//普通写法
new Thread(new Runnable(){
@Override
public void run() {
r1.m1();
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r1::m2).start();
}
}
Lock是ReentrantLock实现的接口,本例的结果和上面使用synchronized 得到的结果相同。reentrantlock是可以用于替代synchronized的锁(手动上锁,手动释放)。
知识点:
- 需要注意的是,必须要手动释放锁(非常重要,ReentrantLock是手工锁)
- 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
Case2(ReentrantLock之trylock):
package ReentrantLock
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ReentrantLock3 {
Lock lock=new ReentrantLock();
void m1(){
lock.lock(); //相当于synchronized(this)
try {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
void m2(){
/*第一种方法:lock.tryLock(),若拿着锁返回true,否则返回false
*
boolean locked=lock.tryLock();
System.out.println("m2..."+locked);
if (locked) lock.unlock();*/
boolean locked=false;
try {
//第二种用法:尝试等n秒,如果还是没有拿到锁,则该干嘛干嘛
locked=lock.tryLock(5,TimeUnit.SECONDS);
System.out.println("m2...."+locked);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked) lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLock3 r1=new ReentrantLock3();
new Thread(r1::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r1::m2).start();
}
}
使用reentrantlock可以进行“尝试锁定”trylock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。所以,reentrantlock比synchronized更加灵活,其能进行等待获取锁。在效率上,两者没有什么区别啦(对synchronized进行底层优化后,以前的synchronized锁的粒度是比较重的),而且,还能指定reentrantlock锁的公平性。
知识点:
- 使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行
- reentrantlock有两种使用方式:可以根据trylock的返回值来判断是否锁定,也可以指定trylock的时间。lock.tryLock()可以根据对返回的布尔值的判断来继续执行,而不会抛出异常。但指定时间时,由于trylock(time)会抛出异常,所以要注意unlock的处理,必须方法finally中
1.第一种方法:lock.tryLock(),若拿着锁返回true,否则返回falseboolean locked=lock.tryLock(); System.out.println("m2..."+locked); if (locked) lock.unlock(); else ....
- 第二种用法:尝试等n秒,如果还是没有拿到锁,则该干嘛干嘛
boolean locked=lock.tryLock(); try { locked=lock.tryLock(5,TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } finally { if (locked) lock.unlock(); }
Case3(ReentrantLock之lockInterruptibly):
package ReentrantLock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLock4 {
public static void main(String[] args) {
Lock lock=new ReentrantLock();
Thread t1=new Thread(()->{
lock.lock();
try {
System.out.println("t1 start");
//t1一直运行,其他等待lock的线程永远等待
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
System.out.println("t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
t1.start();
Thread t2=new Thread(()->{
try {
//如果使用lock.lock();则t2会永远等待,并不能打断t2的执行
//lock.lock();
//lock.lockInterruptibly();可以在等不到资源的情况下把该线程(t2)打断,不再执行
//注意:t2是不可能打断t1的,没这个权利。所谓的打断就是结束等待线程
lock.lockInterruptibly(); //可以对interrupt()做出响应
System.out.println("t2 start");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();//打断线程2的等待
}
}
/*====================output========================
t1 start
t2结束等待
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at ReentrantLock.ReentrantLock4.lambda$main$1(ReentrantLock4.java:40)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at ReentrantLock.ReentrantLock4.lambda$main$1(ReentrantLock4.java:48)
at java.lang.Thread.run(Thread.java:745)
*/
使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断。
上面的例子中,首先new出一个ReentrantLock,t1获得lock锁时,进行永久睡眠。紧接着t2通过lock.lockInterruptibly();尝试着获得锁,由于锁被t1占着,所以t2获得锁失败,就一直等待。这时,调用t2.interrupt();就可以直接把t2线程打断,t2不在等待,而是直接结束线程。
注意:
- lock.lockInterruptibly();可以在等不到资源的情况下把该线程(t2)打断,不再执行
- 注意:t2是不可能打断t1的,没这个权利。所谓的打断就是结束等待线程
Case4(ReentrantLock之公平锁):
package ReentrantLock;
import java.util.concurrent.locks.ReentrantLock;
//本身是从Thread继承的,所以不用再实现runnable接口
public class ReentrantLock5 extends Thread {
//公平锁t1和t2交换执行
private static ReentrantLock lock=new ReentrantLock(true);//参数为true表示为公平锁,默认是非公平的锁。请对比输出结果
@Override
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"获得锁");
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLock5 r1=new ReentrantLock5();
Thread th1=new Thread(r1);
Thread th2=new Thread(r1);
th1.start();
th2.start();
}
}
/*====================output========================
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
。。。。。。
*/
默认的synchronized是非公平锁,并不关心谁等的时间长,由线程调度器来进行调度,ReentrantLock还可以指定公平锁。就是谁等的时间长就让谁来获得这把锁。
使用private static ReentrantLock lock=new ReentrantLock(true);
创建ReentrantLock对象时,参数为true表示为公平锁,默认是非公平的锁。从执行的结果可以看出,t1和t2是交替执行的,如果把公平锁的参数设为false,那么结果将是其中一个线程执行完,另一个再执行。
网友评论