锁
ReentrantLock
在Java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增加ReentranLock类也可以达到同样的效果,并且在扩展功能上更加强大,比如具有嗅探锁定、多路分支通知等,而且在使用上也比synchronized更加灵活。从运行结果来看,当前线程打印完毕之后将锁进行释放,其它线程才可以继续 打印。线程打印的数组是分级打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。调用lock方法的线程就会持有对象锁,其它的线程只能等待锁被释放(调用unlock方法)才可以再次争抢锁。效果和synchronized关键字一样,线程之间还是按照顺序执行
public class TestReentrantLock {
private Lock lock;
public TestReentrantLock(Lock lock) {
this.lock = lock;
}
public void printNumber() {
try {
lock.lock();
for (int i = 0; i <= 6; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
TestReentrantLock reentrantLock = new TestReentrantLock(lock);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
reentrantLock.printNumber();
}).start();
}
}
}
ReentrantLock实现wait和notifty
ReentrantLock 实现了 Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性,ReentrantLock也可以实现等待/通知模式的功能,但需要借助Condition对象。Condition类是在JDK5中出现的技术。synchronized就相当于整个Lock对象中只有一个单一Condition对象,所有的线程都是注册在它一个对象的身上。线程开始notifyAll时需要通知所有正在等待的线程,没有选择权,会出现相当大效率问题。
public class TestReentrantLock {
public static void main(String[] args) throws InterruptedException {
ReentrantLockService service = new ReentrantLockService();
Thread thread = new AwaitThread(service);
thread.start();
TimeUnit.SECONDS.sleep(3);
service.signal();
}
}
class ReentrantLockService {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void await() {
lock.lock();
System.out.println(Thread.currentThread().getName() + "begin await:" + System.currentTimeMillis());
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
public void signal() {
lock.lock();
System.out.println(Thread.currentThread().getName() + "begin signal:" + System.currentTimeMillis());
condition.signal();
lock.unlock();
}
}
class AwaitThread extends Thread {
private ReentrantLockService service;
public AwaitThread(ReentrantLockService service) {
this.service = service;
}
@Override
public void run() {
service.await();
}
}
Condition接口来实现多路通知
在一个Lock对象里面可以创建多个Condition实例(Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,使用其 newCondition() 方法),线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调试线程上更加灵活。在使用notify/notifyAll方法进行通知时,被通知线程是由JVM随机选择的,但使用ReentrantLock结合Condition类可以实现选择性通知,这个功能是非常重要的,而且在Condition在中是默认提供的,单个Lock可能与多个Condition对象关联
public class TestMultiReentrantLock {
public static void main(String[] args) throws InterruptedException {
MultiReentrantLockService service = new MultiReentrantLockService();
new Thread((service::await1)).start();
new Thread((service::await2)).start();
TimeUnit.SECONDS.sleep(5);
service.signal();
}
}
class MultiReentrantLockService {
private ReentrantLock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();//单个Lock可能与多个Condition对象关联
private Condition condition2 = lock.newCondition();//单个Lock可能与多个Condition对象关联
public void await1() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " begin await1...");
condition1.await();
System.out.println(Thread.currentThread().getName() + " signal await1...");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
public void await2() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " begin await2...");
condition2.await();
System.out.println(Thread.currentThread().getName() + " signal await2...");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
public void signal() {
try {
lock.lock();
condition1.signalAll();
System.out.println(Thread.currentThread().getName() + " begin signal...");
} catch (Exception e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
PS:
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll
面试题:开启3个线程,这三个线程的Name分别为A、B、C,每个线程将自己的Name在屏幕上打印5遍
public class TestReentrantLock {
public static void main(String[] args) {
Print print = new Print();
new Thread(()->{
for (int i = 0; i < 5; i++) {
print.printA();
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
print.printB();
}
}, "B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
print.printC();
}
}, "C").start();
}
}
class Print {
private String flag = "A";
final ReentrantLock lock = new ReentrantLock();
final Condition conditionA = lock.newCondition();
final Condition conditionB = lock.newCondition();
final Condition conditionC = lock.newCondition();
public void printA() {
try {
lock.lock();
if (!"A".equals(flag)) {
conditionA.await();
}
System.out.print(Thread.currentThread().getName());
flag = "B";
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
public void printB() {
try {
lock.lock();
if (!"B".equals(flag)) {
conditionB.await();
}
System.out.print(Thread.currentThread().getName());
flag = "C";
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
public void printC() {
try {
lock.lock();
if (!"C".equals(flag)) {
conditionC.await();
}
System.out.print(Thread.currentThread().getName());
flag = "A";
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
tryLock和TryLock(time, uint)
public class TestTryLock {
public static void main(String[] args) {
TryLockService tryLockService = new TryLockService();
Thread A = new Thread(tryLockService::tryLock);
A.setName("A");
A.start();
Thread B = new Thread(() -> tryLockService.tryLockTime(4L));
B.setName("B");
B.start();
}
}
class TryLockService {
final ReentrantLock lock = new ReentrantLock();
public void tryLock() {
System.out.println(Thread.currentThread().getName() + " start.....");
if (lock.tryLock()) {
System.out.println(Thread.currentThread().getName() + " 获取到锁!");
}
else {
System.out.println(Thread.currentThread().getName() + " 未获取到锁!");
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
System.out.println(Thread.currentThread().getName() + " 释放锁!!");
lock.unlock();
}
}
public void tryLockTime(Long time) {
try {
System.out.println(Thread.currentThread().getName() + " start.....");
if (lock.tryLock(time, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + " 获取到锁!");
}
else {
System.out.println(Thread.currentThread().getName() + " 未获取到锁!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
System.out.println(Thread.currentThread().getName() + " 释放锁!!");
lock.unlock();
}
}
}
公平锁和非公平锁
在java的锁机制中,公平和非公平的参考物是什么,简单的来说,如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。
那如何能保证每个线程都能拿到锁呢,队列FIFO是一个完美的解决方案,也就是先进先出,java的ReenTrantLock也就是用队列实现的公平锁和非公平锁。在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。
public class TestReentrantLock {
public static void main(String[] args) {
TestReentrantLockService t1 = new TestReentrantLockService();
for (int i = 0; i < 50; i++) {
new Thread(t1).start();
}
}
}
class TestReentrantLockService extends Thread {
final ReentrantLock lock = new ReentrantLock(true); //true公平锁,false非公平锁
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁:" + i);
} catch (Exception e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
}
ReadWriteLock读写锁(写加锁,读不需要)
并发读写操作保证安全的常规解决办法是在读写操作上加入互斥锁,这种情况下并发读写效率会打折扣,因为大多数情况下我们对同一数据读操作的频率会高于写操作,而线程与线程间的并发读操作是不涉及并发安全问题的,所以没有必要给读操作加互斥锁,只要保证读写、写写并发操作上锁是互斥的就行。
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的,ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。 ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。
上面基本都废话,主要核心:
“读-读” 不互斥
“读-写” 互斥
“写-写” 互斥
public class TestReadWriteLock {
public static void main(String[] args) {
ReadWriteLockService service = new ReadWriteLockService();
for (int i = 0; i < 20; i++) {
char c = (char) ('A' + i);
new Thread(() -> {
service.read();
}, "A").start();
new Thread(() -> {
service.write();
}, "B").start();
}
}
}
class ReadWriteLockService {
private String value = "";
final ReadWriteLock lock = new ReentrantReadWriteLock();
final Lock readLock = lock.readLock();
final Lock writeLock = lock.writeLock();
public void read() {
try {
readLock.lock();
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " read..." + value);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
}
public void write() {
try {
writeLock.lock();
TimeUnit.SECONDS.sleep(1);
value = System.currentTimeMillis() + "";
System.out.println(Thread.currentThread().getName() + " write..." + value);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
}
}
LockSupport读写锁(写加锁,读不需要)
- LockSupport 不需要加锁就可以实现阻塞和唤醒
- 如果一个线程处理等待状态,连续调用了两次park方法。线程永远无法被唤醒
- unpark 和 park 方法不需要考虑调用先后顺序
public class TestLockSupport {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
if (i == 3) {
LockSupport.park();
}
System.out.println(i);
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A");
thread.start();
LockSupport.unpark(thread);
}
}