Lock和Synchronized的区别
参考链接 海子博客链接地址
- Lock不是java语言内置的,Lock是一个类,通过该类来实现同步访问,synchronzied是java内置关键字
- synchronized不需要手动去释放,当sync的代码块执行完会自动释放对锁的占用,但是Lock需要手动去释放,否则会造成死锁,手动释放需要在finally关键字里面执行
- Lock可以让等待锁的线程响应中断,但是sync不行,如果线程中断,sync会一直等待下去
- 使用Lock可以判断是不是成功获取锁
- Lock可以进行多个读操作可以提高效率
总结来说,如果竞争不激烈两者性能类似,但是如果线程较多竞争激烈,那么Lock的效率回高于sync
- Lock - ReentrantLock lock方法
package com.troy.concurrent.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockRunner {
List list = new ArrayList();
//注意与方法里面锁的区别
Lock lock = new ReentrantLock();
public static void main(String[] args){
LockRunner runner = new LockRunner();
Thread ta = new Thread(runner.new WorkA(),"WorkA");
Thread tb = new Thread(runner.new WorkB(),"WorkB");
ta.start();
tb.start();
runner.list.forEach(f->System.out.println(f));
}
void conCurrOper(Thread thread){
//如果new锁的操作放在方法内,那么则锁无效,因为每一个线程都会在内存中有自己单独的副本,所以会造成每个单独的线程使用各自副本里面的锁,因此无法看到锁效果
//Lock lock = new ReentrantLock();
lock.lock();
try{
System.out.println(thread.getName()+"线程获取到锁,线程正在处理...");
for(int i=0;i<3;i++){
list.add(thread.getName()+ "~"+i);
}
}catch (Exception e){
System.out.println("程序发生异常");
System.out.println(e.getMessage());
}finally{
lock.unlock();
System.out.println(thread.getName() +"执行结束释放了锁...");
}
}
class WorkA implements Runnable{
public void run() {
conCurrOper(Thread.currentThread());
}
}
class WorkB implements Runnable{
public void run() {
conCurrOper(Thread.currentThread());
}
}
}
输出结果:
WorkA线程获取到锁,线程正在处理...<br>
WorkA执行结束释放了锁...<br>
WorkB线程获取到锁,线程正在处理...<br>
WorkB执行结束释放了锁...<br>
WorkA~0<br>
WorkA~1<br>
WorkA~2<br>
WorkB~0<br>
WorkB~1<br>
WorkB~2<br>
- Lock - ReentrantLock tryLock方法
package com.troy.concurrent.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 通过tryLock()进行一次判断是否可以获取锁
*/
public class LockRunner_tryLock {
List list = new ArrayList();
Lock lock = new ReentrantLock();
public static void main(String[] args) throws Exception{
LockRunner_tryLock runner = new LockRunner_tryLock();
Thread ta = new Thread(runner.new WorkA(),"WorkA");
Thread tb = new Thread(runner.new WorkB(),"WorkB");
ta.start();
tb.start();
Thread.sleep(3000);
runner.list.forEach(f->System.out.println(f));
}
public boolean conCurrOper(Thread thread){
if(lock.tryLock()){
try {
System.out.println(thread.getName() + "线程获取到锁,线程正在处理...");
for(int i=0;i<3;i++){
list.add(thread.getName()+"~"+i);
}
Thread.sleep(500);
}catch (Exception e){
System.out.println("程序发生异常");
System.out.println(e.getMessage());
lock.unlock();
return false;
}finally{
System.out.println(thread.getName() +"执行结束释放了锁...");
lock.unlock();
return true;
}
}else{
System.out.println(thread.getName()+"线程获取锁失败,准备从新尝试本次任务终止...");
return false;
}
}
class WorkA implements Runnable{
public void run() {
int retry = 0;
boolean isSuc = conCurrOper(Thread.currentThread());
while(retry < 3 && !isSuc){
System.out.println(Thread.currentThread().getName() + "正在进行第"+retry+"次重试...");
isSuc = conCurrOper(Thread.currentThread());
retry++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("isSuc = " + isSuc);
}
}
class WorkB implements Runnable{
public void run() {
int retry = 0;
boolean isSuc = conCurrOper(Thread.currentThread());
while(retry < 3 && !isSuc){
System.out.println(Thread.currentThread().getName() + "正在进行第"+retry+"次重试...");
isSuc = conCurrOper(Thread.currentThread());
retry++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("isSuc = " + isSuc);
}
}
}
输出结果:
WorkA线程获取到锁,线程正在处理...<br>
WorkB线程获取锁失败,准备从新尝试本次任务终止...<br>
WorkB正在进行第0次重试...<br>
WorkB线程获取锁失败,准备从新尝试本次任务终止...<br>
WorkA执行结束释放了锁...<br>
WorkB正在进行第1次重试...<br>
isSuc = true<br>
WorkB线程获取到锁,线程正在处理...<br>
WorkB执行结束释放了锁...<br>
isSuc = true<br>
WorkA~0<br>
WorkA~1<br>
WorkA~2<br>
WorkB~0<br>
WorkB~1<br>
WorkB~2<br>
- Lock - Condition
package com.troy.concurrent.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockRunner_Condition {
List list = new ArrayList();
SmallQueue squ;
public static void main(String[] args) throws Exception {
LockRunner_Condition runner = new LockRunner_Condition();
runner.squ = runner.new SmallQueue();
System.out.println("take value from squ");
//如果拿一个空squ,程序会一直block在这里直到有对象put进去squ,因为lock condition的控制
// runner.squ.take();
// runner.squ.put("Hello world");
// runner.squ.put("Hello Java");
// String value = runner.squ.take().toString();
// System.out.println("take value from squ == " +value);
Thread workA = new Thread(runner.new WorkA(),"读取线程_workA");
Thread workB = new Thread(runner.new WorkB(),"写入线程_workB");
workA.start();
workB.start();
}
/**
* 仿照ArrayBlockingQueue的put,take实现
*/
class SmallQueue{
Lock lock = new ReentrantLock();
Condition empty = lock.newCondition();
Condition full = lock.newCondition();
Object[] objs = new Object[2];
int putptr,takeptr,count;
void put(Object obj) throws Exception{
lock.lock();
try{
while(count == objs.length){
System.out.println(Thread.currentThread().getName() +" is waitting cosume item, now queue is full");
full.await();
System.out.println(Thread.currentThread().getName() +" wait over,now has space");
}
objs[putptr]=obj;
if (++putptr == objs.length) putptr = 0;
++count;
empty.signal();
}finally{
lock.unlock();
}
}
Object take() throws Exception{
lock.lock();
try {
while (count == 0)
// 注意这行代码,楼主在添加本行代码后,程序会一直输出这行print从而进入死循环,目前我也不知道什么原因,有明白的大神可以指点一下
//System.out.println(Thread.currentThread().getName() + " is waitting item, queue is empty now...");
empty.await();
System.out.println(Thread.currentThread().getName() + " waitting is end, queue has item now...");
Object x = objs[takeptr];
if (++takeptr == objs.length) takeptr = 0;
--count;
full.signal();
return x;
} finally {
lock.unlock();
}
}
}
class WorkA implements Runnable{
public void run() {
int i = 0;
while(i<4){
try {
System.out.println(Thread.currentThread().getName() + " start to take value from squ...");
StringBuilder value = new StringBuilder(squ.take().toString());
System.out.println(Thread.currentThread().getName() + " take value from squ value is "+value.toString());
} catch (Exception e) {
e.printStackTrace();
}
i++;
}
}
}
class WorkB implements Runnable{
public void run(){
for(int i=0; i<4; i++){
StringBuilder stringBuilder = new StringBuilder("Que-value-");
stringBuilder.append(i);
try {
System.out.println(Thread.currentThread().getName() + " will put down value to squ ");
squ.put(stringBuilder.toString());
System.out.println(Thread.currentThread().getName() + " put down to squ " + stringBuilder.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
输出结果A:
take value from squ<br>
读取线程_workA start to take value from squ...<br>
写入线程_workB will put down value to squ <br>
写入线程_workB put down to squ Que-value-0<br>
写入线程_workB will put down value to squ <br>
写入线程_workB put down to squ Que-value-1<br>
写入线程_workB will put down value to squ <br>
写入线程_workB is waitting cosume item, now queue is full<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-0<br>
读取线程_workA start to take value from squ...<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-1<br>
读取线程_workA start to take value from squ...<br>
写入线程_workB wait over,now has space<br>
写入线程_workB put down to squ Que-value-2<br>
写入线程_workB will put down value to squ <br>
写入线程_workB put down to squ Que-value-3<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-2<br>
读取线程_workA start to take value from squ...<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-3<br>
输出结果B:
读取线程_workA start to take value from squ...<br>
写入线程_workB will put down value to squ <br>
写入线程_workB put down to squ Que-value-0<br>
写入线程_workB will put down value to squ <br>
读取线程_workA waitting is end, queue has item now...<br>
写入线程_workB put down to squ Que-value-1<br>
写入线程_workB will put down value to squ <br>
读取线程_workA take value from squ value is Que-value-0<br>
写入线程_workB put down to squ Que-value-2<br>
读取线程_workA start to take value from squ...<br>
写入线程_workB will put down value to squ <br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-1<br>
写入线程_workB put down to squ Que-value-3<br>
读取线程_workA start to take value from squ...<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-2<br>
读取线程_workA start to take value from squ...<br>
读取线程_workA waitting is end, queue has item now...<br>
读取线程_workA take value from squ value is Que-value-3<br>
细心的朋友会发现会出现A和B两种情况,这是正常的,因为我们是一个多线程同时put和take数据从队列中:
情况A:表示写入的线程先执行到了队列容量,因此触发了队列满时的print提醒“写入线程_workB is waitting cosume item, now queue is full”;
情况B:表示程序在执行时资源分配均衡,写入线程和读取线程近乎平均,因此是有读有写的输出。
- Lock - ReentrantReadWriteLock
在使用Read-Write锁之前我们看下sycn的话程序怎么处理
package com.troy.concurrent.lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockRunner_ReadAndWriter_Sync {
//注意与方法里面锁的区别
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args){
LockRunner_ReadAndWriter_Sync runner = new LockRunner_ReadAndWriter_Sync();
Thread ta = new Thread(runner.new WorkA(),"WorkA");
Thread tb = new Thread(runner.new WorkB(),"WorkB");
ta.start();
tb.start();
}
synchronized void conCurrOper(Thread thread){
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"处理结束...");
System.out.println(thread.getName()+"处理结束...");
}
class WorkA implements Runnable{
public void run() {
conCurrOper(Thread.currentThread());
}
}
class WorkB implements Runnable{
public void run() {
conCurrOper(Thread.currentThread());
}
}
}
输出结果:
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA正在进行读操作<br>
WorkA处理结束...<br>
WorkA处理结束...<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB正在进行读操作<br>
WorkB处理结束...<br>
WorkB处理结束...<br>
使用readLock的结果
package com.troy.concurrent.lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockRunner_ReadAndWriter {
//注意与方法里面锁的区别
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args){
LockRunner_ReadAndWriter runner = new LockRunner_ReadAndWriter();
Thread ta_L = new Thread(runner.new WorkA_Lock(),"WorkA_Lock");
Thread tb_L = new Thread(runner.new WorkB_Lock(),"WorkB_Lock");
ta_L.start();
tb_L.start();
}
void conCurrOper_Lock(Thread thread){
lock.readLock().lock();
try{
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"处理结束...");
}finally {
lock.readLock().unlock();
}
}
class WorkA_Lock implements Runnable{
public void run() { conCurrOper_Lock(Thread.currentThread()); }
}
class WorkB_Lock implements Runnable{
public void run() {
conCurrOper_Lock(Thread.currentThread());
}
}
}
输出结果:
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkA_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock正在进行读操作<br>
WorkB_Lock处理结束...<br>
WorkA_Lock正在进行读操作<br>
WorkA_Lock处理结束...<br>
通过例子可以看到读锁可以大大提高效率,但是有几点要注意:
- 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
- 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
锁的相关介绍
-
可重入锁
如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。public synchronized void method1() { method2(); } public synchronized void method2() { }
上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
-
可中断锁
可中断锁:顾名思义,就是可以相应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁(lockInterruptibly())。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁. -
公平锁
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
ReentrantLock lock = new ReentrantLock(true);
ReentrantReadWriteLock lk = new ReentrantReadWriteLock(true);
如果参数为true表示为公平锁,为fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。<br>
另外在ReentrantLock类中定义了很多方法,比如:<br>
isFair() //判断锁是否是公平锁<br>
isLocked() //判断锁是否被任何线程获取了<br>
isHeldByCurrentThread() //判断锁是否被当前线程获取了<br>
hasQueuedThreads() //判断是否有线程在等待该锁<br>
- 读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
可以通过readLock()获取读锁,通过writeLock()获取写锁。
上面已经演示过了读写锁的使用方法,在此不再赘述。