Java中,实现线程同步手段有很多,加锁是其中常用的一种,synchronized关键字是最常用的手段之一。而ReentrantLock则可以完全替代其作用,并且可以更灵活的使用锁机制,但同时,对编程水平的要求也稍微高一点,由于编程水平原因,出现bug概率提高。
总而言之,API文档这么描述ReentrantLock:一个可重入的互斥锁 Lock
,它具有与使用 synchronized
方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
两者的区别主要如下:
1:synchronized遇到异常,如果不catch,锁会被自动释放,而ReentrantLock需要手动释放。
编程时请使用如下代码,防止有异常抛出,没有catch,导致异常发生,锁不被释放。
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
示例如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLock02
{
private Lock lock = new ReentrantLock();
private int num = 0 ;
public void count()
{
lock.lock();
try
{
for (int i = 0 ; i < 100000 ; i++)
{
num++;
System.out.println(num);
}
}
finally
{
lock.unlock();
}
}
public static void main(String[] args)
{
TestReentrantLock02 a = new TestReentrantLock02();
new Thread(a::count).start();
new Thread(a::count).start();
new Thread(a::count).start();
}
}
2:synchronized是非公平锁,而ReentrantLock可以构造成公平锁的形式。
synchronized的锁是不公平的,假设现在有两个线程都在竞争a这把锁, 线程1为了获得此锁,等待了24小时,而线程2只等待了1秒,线程调度器也可能让线程2获得此锁,让其先执行。这无疑是不公平的,但是对于程序来说,却是效率提高,因为不需要记录等待时间,也不需要排队获得锁。
ReentrantLock可以在构造时,构造为公平锁:
public ReentrantLock(boolean fair)
创建一个具有给定公平策略的 ReentrantLock。
参数:
fair - 如果此锁应该使用公平的排序策略,则该参数为 true
示例如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestFairLock
{
private Lock unfairLock = new ReentrantLock();
private Lock fairLock = new ReentrantLock(true);
private int num = 0 ;
public void unfairAdd()
{
for (int i = 0 ; i < 100 ; i++ )
{
unfairLock.lock();
try
{
num++;
System.out.println("线程" + Thread.currentThread().getName() + "将num加到了" + num);
}
finally
{
unfairLock.unlock();
}
}
}
public void fairAdd()
{
for (int i = 0 ; i < 200 ; i++ )
{
fairLock.lock();
try
{
num++;
System.out.println("线程" + Thread.currentThread().getName() + "将num加到了" + num);
}
finally
{
fairLock.unlock();
}
}
}
public synchronized void add()
{
for (int i = 0 ; i < 300 ; i++ )
{
num++;
System.out.println("线程" + Thread.currentThread().getName() + "将num加到了" + num);
}
}
public static void main(String[] args) throws InterruptedException
{
TestFairLock test = new TestFairLock();
// 使用synchronized
// new Thread(test::add,"t1").start();
// new Thread(test::add,"t2").start();
// 使用非公平锁
// new Thread(test::unfairAdd,"t1").start();
// new Thread(test::unfairAdd,"t2").start();
// 使用公平锁
new Thread(test::fairAdd,"t1").start();
new Thread(test::fairAdd,"t2").start();
}
}
执行结果片段如下
使用公平锁:前几个没有交替执行的原因是因为两个线程没有同时启动的原因。
线程t1将num加到了1
线程t1将num加到了2
线程t1将num加到了3
线程t1将num加到了4
线程t1将num加到了5
线程t1将num加到了6
线程t1将num加到了7
线程t1将num加到了8
线程t1将num加到了9
线程t1将num加到了10
线程t1将num加到了11
线程t1将num加到了12
线程t1将num加到了13
线程t2将num加到了14
线程t1将num加到了15
线程t2将num加到了16
线程t1将num加到了17
线程t2将num加到了18
线程t1将num加到了19
线程t2将num加到了20 ......
使用cynchronized和ReentrantLock的非公平策略结果一致如下:由于计算量小,线程一次执行机会就执行了所有的代码。
线程t1将num加到了1
线程t1将num加到了2
线程t1将num加到了3
线程t1将num加到了4
线程t1将num加到了5
线程t1将num加到了6
线程t1将num加到了7
线程t1将num加到了8
线程t1将num加到了9
线程t1将num加到了10
线程t1将num加到了11
线程t1将num加到了12
线程t1将num加到了13
线程t1将num加到了14
线程t1将num加到了15
线程t1将num加到了16
线程t1将num加到了17
线程t1将num加到了18
线程t1将num加到了19
线程t1将num加到了20
线程t1将num加到了21
线程t1将num加到了22
线程t1将num加到了23
线程t1将num加到了24
线程t1将num加到了25
线程t1将num加到了26
线程t1将num加到了27
线程t1将num加到了28
线程t1将num加到了29
线程t1将num加到了30
线程t1将num加到了31
线程t1将num加到了32
线程t1将num加到了33
线程t1将num加到了34
线程t1将num加到了35
线程t1将num加到了36
线程t1将num加到了37
线程t1将num加到了38
线程t1将num加到了39
线程t1将num加到了40
线程t1将num加到了41
线程t1将num加到了42
线程t1将num加到了43
线程t1将num加到了44
线程t1将num加到了45
线程t1将num加到了46
线程t1将num加到了47
线程t1将num加到了48
线程t1将num加到了49
线程t1将num加到了50
线程t1将num加到了51
线程t1将num加到了52
线程t1将num加到了53
线程t1将num加到了54
线程t1将num加到了55
线程t1将num加到了56
线程t1将num加到了57
线程t1将num加到了58
线程t1将num加到了59
线程t1将num加到了60
线程t1将num加到了61
线程t1将num加到了62
线程t1将num加到了63
线程t1将num加到了64
线程t1将num加到了65
线程t1将num加到了66
线程t1将num加到了67
线程t1将num加到了68
线程t1将num加到了69
线程t1将num加到了70
线程t1将num加到了71
线程t1将num加到了72
线程t1将num加到了73
线程t1将num加到了74
线程t1将num加到了75
线程t1将num加到了76
线程t1将num加到了77
线程t1将num加到了78
线程t1将num加到了79
线程t1将num加到了80
线程t1将num加到了81
线程t1将num加到了82
线程t1将num加到了83
线程t1将num加到了84
线程t1将num加到了85
线程t1将num加到了86
线程t1将num加到了87
线程t1将num加到了88
线程t1将num加到了89
线程t1将num加到了90
线程t1将num加到了91
线程t1将num加到了92
线程t1将num加到了93
线程t1将num加到了94
线程t1将num加到了95
线程t1将num加到了96
线程t1将num加到了97
线程t1将num加到了98
线程t1将num加到了99
线程t1将num加到了100
线程t2将num加到了101
线程t2将num加到了102
线程t2将num加到了103
线程t2将num加到了104
线程t2将num加到了105
线程t2将num加到了106
线程t2将num加到了107
线程t2将num加到了108
线程t2将num加到了109
线程t2将num加到了110
线程t2将num加到了111
线程t2将num加到了112
线程t2将num加到了113
线程t2将num加到了114
线程t2将num加到了115
线程t2将num加到了116
线程t2将num加到了117
线程t2将num加到了118
线程t2将num加到了119
线程t2将num加到了120
线程t2将num加到了121
线程t2将num加到了122
线程t2将num加到了123
线程t2将num加到了124
线程t2将num加到了125
线程t2将num加到了126
线程t2将num加到了127
线程t2将num加到了128
线程t2将num加到了129
线程t2将num加到了130
线程t2将num加到了131
线程t2将num加到了132
线程t2将num加到了133
线程t2将num加到了134
线程t2将num加到了135
线程t2将num加到了136
线程t2将num加到了137
线程t2将num加到了138
线程t2将num加到了139
线程t2将num加到了140
线程t2将num加到了141
线程t2将num加到了142
线程t2将num加到了143
线程t2将num加到了144
线程t2将num加到了145
线程t2将num加到了146
线程t2将num加到了147
线程t2将num加到了148
线程t2将num加到了149
线程t2将num加到了150
线程t2将num加到了151
线程t2将num加到了152
线程t2将num加到了153
线程t2将num加到了154
线程t2将num加到了155
线程t2将num加到了156
线程t2将num加到了157
线程t2将num加到了158
线程t2将num加到了159
线程t2将num加到了160
线程t2将num加到了161
线程t2将num加到了162
线程t2将num加到了163
线程t2将num加到了164
线程t2将num加到了165
线程t2将num加到了166
线程t2将num加到了167
线程t2将num加到了168
线程t2将num加到了169
线程t2将num加到了170
线程t2将num加到了171
线程t2将num加到了172
线程t2将num加到了173
线程t2将num加到了174
线程t2将num加到了175
线程t2将num加到了176
线程t2将num加到了177
线程t2将num加到了178
线程t2将num加到了179
线程t2将num加到了180
线程t2将num加到了181
线程t2将num加到了182
线程t2将num加到了183
线程t2将num加到了184
线程t2将num加到了185
线程t2将num加到了186
线程t2将num加到了187
线程t2将num加到了188
线程t2将num加到了189
线程t2将num加到了190
线程t2将num加到了191
线程t2将num加到了192
线程t2将num加到了193
线程t2将num加到了194
线程t2将num加到了195
线程t2将num加到了196
线程t2将num加到了197
线程t2将num加到了198
线程t2将num加到了199
线程t2将num加到了200
3:synchronized多个线程竞争一个锁,会一直尝试获取该锁,直到拿到为止,而ReentrantLock可以使用tryLock(), tryLock(long time,TimeUnit unit)方法尝试去获取锁,如果获取不到,可以执行其他逻辑,更加灵活。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TryLock
{
private Lock lock = new ReentrantLock();
public void m1()
{
lock.lock();
try
{
System.out.println("m1拿到锁了");
TimeUnit.SECONDS.sleep(3);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
System.out.println("m1释放锁了");
}
}
public void m2()
{
boolean locked;
locked = lock.tryLock();
if (locked)
{
try
{
System.out.println("m2拿到锁了,m2 执行了");
}
finally
{
lock.unlock();
}
}
else
System.out.println("m2尝试拿到锁失败了。。");
try
{
long startTime = System.currentTimeMillis();
locked = lock.tryLock(4,TimeUnit.SECONDS);
if (locked)
{
long endTime = System.currentTimeMillis();
System.out.println("m2在尝试了" +(endTime - startTime) + "ms 之后拿到了锁,执行了此方法");
}
else
{
long endTime = System.currentTimeMillis();
System.out.println("m2在尝试了" +(endTime - startTime) + "ms 之后依旧没有拿到了锁,执行了此方法");
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
if (locked)
{
lock.unlock();
System.out.println("m2释放了锁");
}
}
}
public static void main(String[] args)
{
TryLock tryLock = new TryLock();
new Thread(tryLock::m1).start();
new Thread(tryLock::m2).start();
}
4:使用3的tryLock()方法是由方法内部设置的超时机制,如果需要由外部设置超时机制,由外部打断尝试获得锁的过程,则需要使用lockInterruptibly()方法获得锁,此方法会抛出一个interruptedException。当此线程在尝试的过程中,被外部打断,就会抛出此异常。用来处理其他业务。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestInterrupted
{
private Lock lock = new ReentrantLock();
public void m1()
{
lock.lock();
try
{
System.out.println("m1拿到锁了");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
System.out.println("m1释放锁了");
}
}
public void m2()
{
System.out.println("m2正在尝试拿锁");
lock.lock();
try
{
System.out.println("m2拿到锁了");
}
finally
{
lock.unlock();
System.out.println("m2释放锁了");
}
}
public void m3()
{
try
{
System.out.println("m3正在尝试拿锁");
lock.lockInterruptibly();
try
{
System.out.println("m3拿到锁了");
}
finally
{
lock.unlock();
System.out.println("m3释放锁了");
}
}
//如果尝试拿锁的过程中被打断了,那一定没有拿到锁,所以不需要finally释放锁
catch (InterruptedException e)
{
System.out.println("m3因为长时间拿不到,被外部线程打断了");
}
}
public static void main(String[] args) throws InterruptedException
{
TestInterrupted testInterrupted = new TestInterrupted();
Thread t1 = new Thread(testInterrupted::m1);
Thread t2 = new Thread(testInterrupted::m2);
Thread t3 = new Thread(testInterrupted::m3);
t1.start();
Thread.sleep(1000);
t2.start();
t3.start();
//t2是不能被打断的
t2.interrupt();
Thread.sleep(2000);
//t3可以被中断
t3.interrupt();
}
}