Java并发---synchronized、Lock、Condition的区别
2017-12-12 18:16
435 查看
synchronized是Java关键字, Lock是Java接口。
在Android开发中大多数情况下都使用synchronized,例如常用的StringBuffer、HashTable等线程安全的数据结构;但后台开发更多的用Lock。
1、 synchronized保证任何时刻最多只有一个线程执行被修饰的函数/代码块,不用显示申请锁、释放锁; 缺点是所有等待线程是线性逐个运行的。具体用法参考Java生产者消费者模式
2、 Lock更加灵活, 支持申请锁、释放锁等。
ReentrantLock类实现了Lock接口, 提供了申请锁、释放锁等基本功能;
ReentrantReadWriteLock类实现了ReadWriteLock接口, 提供了读写分离功能,即无写时支持多线程读,只能支持一个线程写。
StampedLock是升级版的读写锁, 写的时候也支持读, 但读时可判断当前是否在写并重读。
下面看Lock接口的定义, 包含申请锁、释放锁等功能。 在申请锁后必须要释放锁, 否则会出现死锁问题。
lock函数是无返回值的阻塞函数, 如果拿不到锁则阻塞等待, 直到获取到锁后继续向下执行。
tryLock是有返回值的非阻塞函数(能立即返回), 成功拿到锁后返回true, 失败时返回false。
tryLock(long time, TimeUnit unit)方法和tryLock()方法类似, 区别是如果拿不到锁会等待一段时间, 超时返回false。
lockInteruptibly函数跟lock函数的区别是在其它线程调用当前线程的interupt方法后, 当前线程如果处于阻塞状态能够继续向下执行(效果类似于拿到了锁)。
Java为了支持高并发的读写操作, 又提供了读写所ReadWriteLock, 支持多读单写, 即无写时可以多读, 写时不能读,最多一个线程写。
Java8对读写锁进行了改进,新增StampedLock类支持乐观锁, 实现读不阻塞写, 在读的时候如果发生了写操作则重新读。下面代码摘自源码注释,区别是在读函数里调用validate方法判断释放有写操作发生并重新获取读锁。
Condition可以替换Object的wait/notify/notifyAll实现线程间通讯,await替换wait,signal替换notify,signalAll替换notifyAll。
PS: Conditon也是继承于Object类的, 但Object类的wait、notify和notifyAll都是用final修饰的, 所以Condition要新增函数。
JDK的示例代码是单个生产者单个消费者模式, 比起Object的wait/notify/notifyAll(随机唤醒读线程或写线程)的优势在于创建了读Condition和写Condition, 从而可以只唤醒读线程或写线程。
改造一下JDK示例代码使用signalAll(唤醒所有await线程但必须拿到唯一的锁后才能继续运行),将仓库容量给为3个, 使用2个写线程和2个读线程实现生产者消费者模式。
public class Main {
static class BoundedBuffer {
final Lock lock = new ReentrantLock(); //锁对象
final Condition notFull = lock.newCondition(); //写线程锁,仓库满时等待
final Condition notEmpty = lock.newCondition(); //读线程锁,仓库空时等待
final Object[] items = new Object[3];//数据仓库,最多容纳3个商品
int putptr; //写索引
int takeptr; //读索引
int count; //items记录数
//写操作,每次生产一个商品
public void put(Object x, String name) throws InterruptedException {
lock.lock(); //锁定
try {
System.out.println(System.currentTimeMillis() + "--- 写线程" + name+"进入. 剩余商品" + count + "个");
// 如果仓库满则等待读锁
while (count == items.length) {
System.out.println(System.currentTimeMillis() + "--- 写线程"+name+"等待");
notFull.await(); //释放锁并等待signal
System.out.println(System.currentTimeMillis() + "--- 写线程"+name+"继续执行");
}
//创建商品
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count; //创建了一个商品
System.out.println(System.currentTimeMillis() + "--- 写线程" + name+"退出,并signal读线程. 剩余商品" + count + "个");
//如果读线程等待则唤醒读线程
notEmpty.signalAll();
} finally {
lock.unlock();//释放锁
}
}
//读操作,每次消费一个商品
public Object take(String name) throws InterruptedException {
lock.lock(); //获取锁
try {
System.out.println(System.currentTimeMillis() + "--- 读线程" + name+"进入. 剩余商品" + count + "个");
//如果仓库空则等待写线程
while (count == 0) {
System.out.println(System.currentTimeMillis() + "--- 读线程"+name+"等待");
notEmpty.await(); //释放锁并等待signal
System.out.println(System.currentTimeMillis() + "--- 读线程"+name+"继续执行");
}
//读取队列,并更新读索引
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count; //消费了一个商品
System.out.println(System.currentTimeMillis() + "--- 读线程"+name+"退出,并signal写线程. 剩余商品"+ count + "个");
//如果写线程等待则唤醒写线程
notFull.signalAll();
return x;
} finally {
lock.unlock();//解除锁定
}
}
}
public static void main(String[] args) {
final BoundedBuffer obj = new BoundedBuffer();
//写线程A
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
obj.put(new Object(), "A");
Thread.sleep(100);
} catch (Exception ex) {
}
}
}
}).start();
//写线程B
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
obj.put(new Object(), "B");
Thread.sleep(50);
} catch (Exception ex) {
}
}
}
}).start();
//读线程1
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
obj.take("1");
Thread.sleep(100);
} catch (Exception ex) {
}
}
}
}).start();
//读线程2
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
obj.take("2");
Thread.sleep(80);
} catch (Exception ex) {
}
}
}
}).start();
}
}运行程序, 任意时刻只能有1个线程拿到锁, 但通过awati/signal可以唤醒读线程或写线程。 即写线程等待时肯定是读线程拿到锁, 读线程等待时肯定是写线程拿到锁。
1513132664275--- 读线程1进入. 剩余商品3个
1513132664275--- 读线程1退出,并signal写线程. 剩余商品2个
1513132664275--- 写线程A继续执行
1513132664275--- 写线程A退出,并signal读线程. 剩余商品3个
1513132664280--- 读线程2进入. 剩余商品3个
1513132664280--- 读线程2退出,并signal写线程. 剩余商品2个
1513132664284--- 写线程B进入. 剩余商品2个
1513132664284--- 写线程B退出,并signal读线程. 剩余商品3个
1513132664337--- 写线程B进入. 剩余商品3个
1513132664337--- 写线程B等待
1513132664364--- 读线程2进入. 剩余商品3个
1513132664364--- 读线程2退出,并signal写线程. 剩余商品2个
1513132664365--- 写线程B继续执行
1513132664365--- 写线程B退出,并signal读线程. 剩余商品3个
1513132664376--- 读线程1进入. 剩余商品3个
1513132664376--- 读线程1退出,并signal写线程. 剩余商品2个
1513132664376--- 写线程A进入. 剩余商品2个
1513132664376--- 写线程A退出,并signal读线程. 剩余商品3个
1513132664419--- 写线程B进入. 剩余商品3个
1513132664419--- 写线程B等待
1513132664448--- 读线程2进入. 剩余商品3个
1513132664448--- 读线程2退出,并signal写线程. 剩余商品2个
1513132664448--- 写线程B继续执行
1513132664448--- 写线程B退出,并signal读线程. 剩余商品3个
在Android开发中大多数情况下都使用synchronized,例如常用的StringBuffer、HashTable等线程安全的数据结构;但后台开发更多的用Lock。
1、 synchronized保证任何时刻最多只有一个线程执行被修饰的函数/代码块,不用显示申请锁、释放锁; 缺点是所有等待线程是线性逐个运行的。具体用法参考Java生产者消费者模式
2、 Lock更加灵活, 支持申请锁、释放锁等。
ReentrantLock类实现了Lock接口, 提供了申请锁、释放锁等基本功能;
ReentrantReadWriteLock类实现了ReadWriteLock接口, 提供了读写分离功能,即无写时支持多线程读,只能支持一个线程写。
StampedLock是升级版的读写锁, 写的时候也支持读, 但读时可判断当前是否在写并重读。
下面看Lock接口的定义, 包含申请锁、释放锁等功能。 在申请锁后必须要释放锁, 否则会出现死锁问题。
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }API文档里介绍了Lock的用法, 先申请锁l.lock(), 得到锁后执行代码逻辑, 最后在finally里释放锁。
lock函数是无返回值的阻塞函数, 如果拿不到锁则阻塞等待, 直到获取到锁后继续向下执行。
Lock l = ...; l.lock(); try { // access the resource protected by this lock }finally { l.unlock(); }
tryLock是有返回值的非阻塞函数(能立即返回), 成功拿到锁后返回true, 失败时返回false。
Lock lock = ...; if (lock.tryLock()) { try { // manipulate protected state }finally { lock.unlock(); } } else { // perform alternative actions }}
tryLock(long time, TimeUnit unit)方法和tryLock()方法类似, 区别是如果拿不到锁会等待一段时间, 超时返回false。
void testLock() throws InterruptedException { Lock lock = new ReentrantLock(); if (lock.tryLock(3, TimeUnit.SECONDS)) { //最多等待3秒 try { // manipulate protected state } finally { lock.unlock(); } } else { // perform alternative actions } }
lockInteruptibly函数跟lock函数的区别是在其它线程调用当前线程的interupt方法后, 当前线程如果处于阻塞状态能够继续向下执行(效果类似于拿到了锁)。
Lock lock = new ReentrantLock(); //如果当前线程阻塞在这里,在其它线程调用当前线程的interupt方法后当前线程继续执行 lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); }ReentrantLock类支持了基本的线程互斥操作, 即任何时刻只有一个线程能拿到锁, 缺点是最多一个线程在运行。
Java为了支持高并发的读写操作, 又提供了读写所ReadWriteLock, 支持多读单写, 即无写时可以多读, 写时不能读,最多一个线程写。
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }一个是读锁,一个是写锁。
public static class Reader implements Runnable { private ReentrantReadWriteLock rwl; public Reader(ReentrantReadWriteLock lock) { this.rwl = lock; } @Override public void run() { while (true) { try { Thread.sleep(100); } catch (Exception ex) { } Lock lock = rwl.readLock(); lock.lock(); try { System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + "正在进行读操作"); Thread.sleep(500); } catch (Exception ex) { } finally { lock.unlock(); } } } } public static class Writer implements Runnable { private ReentrantReadWriteLock rwl; public Writer(ReentrantReadWriteLock lock) { this.rwl = lock; } @Override public void run() { while (true) { try { Thread.sleep(100); } catch (Exception ex) { } Lock lock = rwl.writeLock(); lock.lock(); try { System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + "正在进行写操作"); Thread.sleep(500); } catch (Exception ex) { } finally { lock.unlock(); } } } } public static void main(String[] args) { ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); new Thread(new Writer(rwl), "写线程A").start(); new Thread(new Writer(rwl), "写线程B").start(); new Thread(new Writer(rwl), "写线程C").start(); new Thread(new Writer(rwl), "写线程D").start(); new Thread(new Reader(rwl), "读线程1").start(); new Thread(new Reader(rwl), "读线程2").start(); new Thread(new Reader(rwl), "读线程3").start(); new Thread(new Reader(rwl), "读线程4").start(); }结果:
1513073055136写线程A正在进行写操作 1513073055639写线程B正在进行写操作 1513073056139写线程C正在进行写操作 1513073056641写线程D正在进行写操作 1513073057141读线程1正在进行读操作 1513073057142读线程2正在进行读操作 1513073057142读线程3正在进行读操作 1513073057142读线程4正在进行读操作 1513073057647写线程A正在进行写操作 1513073058149写线程B正在进行写操作 1513073058651写线程C正在进行写操作可以看出任意时刻只有1个线程可以写, 但是可以有多个线程同时读。
Java8对读写锁进行了改进,新增StampedLock类支持乐观锁, 实现读不阻塞写, 在读的时候如果发生了写操作则重新读。下面代码摘自源码注释,区别是在读函数里调用validate方法判断释放有写操作发生并重新获取读锁。
class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; finally { sl.unlockWrite(stamp); } } double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); double currentX = x, currentY = y; if (!sl.validate(stamp)) { stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } void moveIfAtOrigin(double newX, double newY) { // upgrade // Could instead start with optimistic, not read mode long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { long ws = sl.tryConvertToWriteLock(stamp); if (ws != 0L) { stamp = ws; x = newX; y = newY; break; } else { sl.unlockRead(stamp); stamp = sl.writeLock(); } } } finally { sl.unlock(stamp); } } }}
Condition可以替换Object的wait/notify/notifyAll实现线程间通讯,await替换wait,signal替换notify,signalAll替换notifyAll。
PS: Conditon也是继承于Object类的, 但Object类的wait、notify和notifyAll都是用final修饰的, 所以Condition要新增函数。
JDK的示例代码是单个生产者单个消费者模式, 比起Object的wait/notify/notifyAll(随机唤醒读线程或写线程)的优势在于创建了读Condition和写Condition, 从而可以只唤醒读线程或写线程。
改造一下JDK示例代码使用signalAll(唤醒所有await线程但必须拿到唯一的锁后才能继续运行),将仓库容量给为3个, 使用2个写线程和2个读线程实现生产者消费者模式。
public class Main {
static class BoundedBuffer {
final Lock lock = new ReentrantLock(); //锁对象
final Condition notFull = lock.newCondition(); //写线程锁,仓库满时等待
final Condition notEmpty = lock.newCondition(); //读线程锁,仓库空时等待
final Object[] items = new Object[3];//数据仓库,最多容纳3个商品
int putptr; //写索引
int takeptr; //读索引
int count; //items记录数
//写操作,每次生产一个商品
public void put(Object x, String name) throws InterruptedException {
lock.lock(); //锁定
try {
System.out.println(System.currentTimeMillis() + "--- 写线程" + name+"进入. 剩余商品" + count + "个");
// 如果仓库满则等待读锁
while (count == items.length) {
System.out.println(System.currentTimeMillis() + "--- 写线程"+name+"等待");
notFull.await(); //释放锁并等待signal
System.out.println(System.currentTimeMillis() + "--- 写线程"+name+"继续执行");
}
//创建商品
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count; //创建了一个商品
System.out.println(System.currentTimeMillis() + "--- 写线程" + name+"退出,并signal读线程. 剩余商品" + count + "个");
//如果读线程等待则唤醒读线程
notEmpty.signalAll();
} finally {
lock.unlock();//释放锁
}
}
//读操作,每次消费一个商品
public Object take(String name) throws InterruptedException {
lock.lock(); //获取锁
try {
System.out.println(System.currentTimeMillis() + "--- 读线程" + name+"进入. 剩余商品" + count + "个");
//如果仓库空则等待写线程
while (count == 0) {
System.out.println(System.currentTimeMillis() + "--- 读线程"+name+"等待");
notEmpty.await(); //释放锁并等待signal
System.out.println(System.currentTimeMillis() + "--- 读线程"+name+"继续执行");
}
//读取队列,并更新读索引
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count; //消费了一个商品
System.out.println(System.currentTimeMillis() + "--- 读线程"+name+"退出,并signal写线程. 剩余商品"+ count + "个");
//如果写线程等待则唤醒写线程
notFull.signalAll();
return x;
} finally {
lock.unlock();//解除锁定
}
}
}
public static void main(String[] args) {
final BoundedBuffer obj = new BoundedBuffer();
//写线程A
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
obj.put(new Object(), "A");
Thread.sleep(100);
} catch (Exception ex) {
}
}
}
}).start();
//写线程B
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
obj.put(new Object(), "B");
Thread.sleep(50);
} catch (Exception ex) {
}
}
}
}).start();
//读线程1
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
obj.take("1");
Thread.sleep(100);
} catch (Exception ex) {
}
}
}
}).start();
//读线程2
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
obj.take("2");
Thread.sleep(80);
} catch (Exception ex) {
}
}
}
}).start();
}
}运行程序, 任意时刻只能有1个线程拿到锁, 但通过awati/signal可以唤醒读线程或写线程。 即写线程等待时肯定是读线程拿到锁, 读线程等待时肯定是写线程拿到锁。
1513132664275--- 读线程1进入. 剩余商品3个
1513132664275--- 读线程1退出,并signal写线程. 剩余商品2个
1513132664275--- 写线程A继续执行
1513132664275--- 写线程A退出,并signal读线程. 剩余商品3个
1513132664280--- 读线程2进入. 剩余商品3个
1513132664280--- 读线程2退出,并signal写线程. 剩余商品2个
1513132664284--- 写线程B进入. 剩余商品2个
1513132664284--- 写线程B退出,并signal读线程. 剩余商品3个
1513132664337--- 写线程B进入. 剩余商品3个
1513132664337--- 写线程B等待
1513132664364--- 读线程2进入. 剩余商品3个
1513132664364--- 读线程2退出,并signal写线程. 剩余商品2个
1513132664365--- 写线程B继续执行
1513132664365--- 写线程B退出,并signal读线程. 剩余商品3个
1513132664376--- 读线程1进入. 剩余商品3个
1513132664376--- 读线程1退出,并signal写线程. 剩余商品2个
1513132664376--- 写线程A进入. 剩余商品2个
1513132664376--- 写线程A退出,并signal读线程. 剩余商品3个
1513132664419--- 写线程B进入. 剩余商品3个
1513132664419--- 写线程B等待
1513132664448--- 读线程2进入. 剩余商品3个
1513132664448--- 读线程2退出,并signal写线程. 剩余商品2个
1513132664448--- 写线程B继续执行
1513132664448--- 写线程B退出,并signal读线程. 剩余商品3个
相关文章推荐
- java并发之Lock与synchronized的区别
- java并发包中的Condition和Lock 取代Synchronized、wait、notify/notifyAll实现线程的同步与互斥
- 【Java并发系列04】线程锁synchronized和Lock和volatile和Condition
- 【Java之并发】ReentrantLock和synchronized区别
- java并发之Lock与synchronized的区别
- java并发之Lock与synchronized的区别
- Java乐观锁悲观锁、synchronized,重入锁 (ReentrantLock)处理并发(互斥同步、非互斥同步)
- 线程高级篇-Synchronized锁,Lock锁区别和Condition线程并行
- JAVA学习61_Lock与synchronized 的区别
- java中的lock和synchronized区别是什么
- Java中Lock和Synchronized的区别
- 【Java线程】锁机制:synchronized、Lock、Condition
- 黑马程序员-19-java基础-多线程(2)-死锁与线程间通信(synchronized与Lock的区别及各自用法)
- java中synchronized和lock的区别
- JAVA 并发编程-线程同步通信技术(Lock和Condition)(十)
- java synchronized与lock区别 转
- 【Java线程】锁机制:synchronized、Lock、Condition
- Java并发之读写锁Lock和条件阻塞Condition的应用(转载)
- java线程通讯——使用Lock和Condition代替synchronized 和 wait, notify notifyAll()
- java中的lock和synchronized区别