
一.synchronized的缺点
synchronized是java中的一个关键字,也就是说是Java言语内置的特征。那末为何会涌现Lock呢?
如果一个代码块被synchronized润饰了,当一个线程猎取了对应的锁,并实行该代码块时,其他线程便只能一向守候,守候猎取锁的线程开释锁,而这里猎取锁的线程开释锁只会有两种状况:
1)猎取锁的线程实行完了该代码块,然后线程开释对锁的占领;
2)线程实行发作异常,此时JVM会让线程自动开释锁。
引荐:java基础教程
那末如果这个猎取锁的线程由于要守候IO或许其他缘由(比方挪用sleep要领)被壅塞了,然则又没有开释锁,其他线程便只能干巴巴地守候,试想一下,这何等影响递次实行效力。
因而就须要有一种机制可以不让守候的线程一向无限期地守候下去(比方只守候肯定的时候或许可以响应中断),经由历程Lock就可以办到。
再举个例子:当有多个线程读写文件时,读操纵和写操纵会发作冲突征象,写操纵和写操纵会发作冲突征象,然则读操纵和读操纵不会发作冲突征象。
然则采纳synchronized关键字来完成同步的话,就会致使一个问题:
如果多个线程都只是举行读操纵,所以当一个线程在举行读操纵时,其他线程只能守候没法举行读操纵。
因而就须要一种机制来使得多个线程都只是举行读操纵时,线程之间不会发作冲突,经由历程Lock就可以办到。
别的,经由历程Lock可以晓得线程有无胜利猎取到锁。这个是synchronized没法办到的。
总结一下,也就是说Lock供应了比synchronized更多的功用。然则要注重以下几点:
1)Lock不是Java言语内置的,synchronized是Java言语的关键字,因而是内置特征。Lock是一个类,经由历程这个类可以完成同步接见;
2)Lock和synchronized有一点异常大的差别,采纳synchronized不须要用户去手动开释锁,当synchronized要领或许synchronized代码块实行完以后,体系会自动让线程开释对锁的占用;而Lock则必须要用户去手动开释锁,如果没有主动开释锁,就有大概致使涌现死锁征象。
二.java.util.concurrent.locks包下经常运用的类
下面我们就来讨论一下java.util.concurrent.locks包中经常运用的类和接口。
1.Lock
起首要申明的就是Lock,经由历程检察Lock的源码可知,Lock是一个接口:
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
下面来逐一报告Lock接口中每一个要领的运用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来猎取锁的。unLock()要领是用来开释锁的。newCondition()这个要领临时不在此报告,会在背面的线程合作一文中报告。
在Lock中声清晰明了四个要领来猎取锁,那末这四个要领有何区分呢?
起首lock()要领是寻常运用得最多的一个要领,就是用来猎取锁。如果锁已被其他线程猎取,则举行守候。
由于在前面讲到如果采纳Lock,必需主动去开释锁,而且在发作异常时,不会自动开释锁。因而平常来讲,运用Lock必需在try{}catch{}块中举行,而且将开释锁的操纵放在finally块中举行,以保证锁肯定被被开释,防备死锁的发作。平常运用Lock来举行同步的话,是以下面这类情势去运用的:
Lock lock = ...; lock.lock(); try{ //处置惩罚使命 }catch(Exception ex){ }finally{ lock.unlock(); //开释锁 }
tryLock()要领是有返回值的,它示意用来尝试猎取锁,如果猎取胜利,则返回true,如果猎取失利(即锁已被其他线程猎取),则返回false,也就说这个要领无论怎样都邑马上返回。在拿不到锁时不会一向在那守候。
tryLock(long time, TimeUnit unit)要领和tryLock()要领是相似的,只不过区分在于这个要领在拿不到锁时会守候肯定的时候,在时候限期以内如果还拿不到锁,就返回false。如果如果一开始拿到锁或许在守候期间内拿到了锁,则返回true。
所以,平常状况下经由历程tryLock来猎取锁时是如许运用的:
Lock lock = ...; if(lock.tryLock()) { try{ //处置惩罚使命 }catch(Exception ex){ }finally{ lock.unlock(); //开释锁 } }else { //如果不能猎取锁,则直接做其他事变 }
lockInterruptibly()要领比较特别,当经由历程这个要领去猎取锁时,如果线程正在守候猎取锁,则这个线程可以响应中断,即中断线程的守候状况。
也就是说,当两个线程同时经由历程lock.lockInterruptibly()想猎取某个锁时,倘使此时线程A猎取到了锁,而线程B只要在守候,那末对线程B挪用threadB.interrupt()要领可以中断线程B的守候历程。
由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必需放在try块中或许在挪用lockInterruptibly()的要领外声明抛出InterruptedException。
因而lockInterruptibly()平常的运用情势以下:
public void method() throws InterruptedException { lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); } }
注重,当一个线程猎取了锁以后,是不会被interrupt()要领中断的。由于自身在前面的文章中讲过零丁挪用interrupt()要领不能中断正在运转历程当中的线程,只能中断壅塞历程当中的线程。
因而当经由历程lockInterruptibly()要领猎取某个锁时,如果不能猎取到,只要举行守候的状况下,是可以响应中断的。
而用synchronized润饰的话,当一个线程处于守候某个锁的状况,是没法被中断的,只要一向守候下去。
2.ReentrantLock
ReentrantLock,意义是“可重入锁”,关于可重入锁的观点鄙人一节报告。ReentrantLock是唯一完成了Lock接口的类,而且ReentrantLock供应了更多的要领。下面经由历程一些实例看细致看一下怎样运用ReentrantLock。
例子1,lock()的准确运用要领
public class Test { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public static void main(String[] args) { final Test test = new Test(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); } public void insert(Thread thread) { Lock lock = new ReentrantLock(); //注重这个处所 lock.lock(); try { System.out.println(thread.getName()+"得到了锁"); for(int i=0;i<5;i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception }finally { System.out.println(thread.getName()+"开释了锁"); lock.unlock(); } } }
输出结果:
Thread-0得到了锁
Thread-1得到了锁
Thread-0开释了锁
Thread-1开释了锁
在insert要领中的lock变量是局部变量,每一个线程实行该要领时都邑保留一个副本,那末天经地义每一个线程实行到lock.lock()处猎取的是差别的锁,所以就不会发作冲突。
晓得了缘由改起来就比较轻易了,只须要将lock声明为类的属性即可。
public class Test { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Lock lock = new ReentrantLock(); //注重这个处所 public static void main(String[] args) { final Test test = new Test(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); } public void insert(Thread thread) { lock.lock(); try { System.out.println(thread.getName()+"得到了锁"); for(int i=0;i<5;i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception }finally { System.out.println(thread.getName()+"开释了锁"); lock.unlock(); } } }
如许就是准确地运用Lock的要领了。
例子2,tryLock()的运用要领
public class Test { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Lock lock = new ReentrantLock(); //注重这个处所 public static void main(String[] args) { final Test test = new Test(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); } public void insert(Thread thread) { if(lock.tryLock()) { try { System.out.println(thread.getName()+"得到了锁"); for(int i=0;i<5;i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception }finally { System.out.println(thread.getName()+"开释了锁"); lock.unlock(); } } else { System.out.println(thread.getName()+"猎取锁失利"); } } }
输出结果:
Thread-0得到了锁
Thread-1猎取锁失利
Thread-0开释了锁
例子3,lockInterruptibly()响应中断的运用要领:
public class Test { private Lock lock = new ReentrantLock(); public static void main(String[] args) { Test test = new Test(); MyThread thread1 = new MyThread(test); MyThread thread2 = new MyThread(test); thread1.start(); thread2.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread2.interrupt(); } public void insert(Thread thread) throws InterruptedException{ lock.lockInterruptibly(); //注重,如果须要准确中断守候锁的线程,必需将猎取锁放在表面,然后将InterruptedException抛出 try { System.out.println(thread.getName()+"得到了锁"); long startTime = System.currentTimeMillis(); for( ; ;) { if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) break; //插进去数据 } } finally { System.out.println(Thread.currentThread().getName()+"实行finally"); lock.unlock(); System.out.println(thread.getName()+"开释了锁"); } } } class MyThread extends Thread { private Test test = null; public MyThread(Test test) { this.test = test; } @Override public void run() { try { test.insert(Thread.currentThread()); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"被中断"); } } }
运转以后,发明thread2可以被准确中断。
3.ReadWriteLock
ReadWriteLock也是一个接口,在它内里只定义了两个要领:
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading. */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing. */ Lock writeLock(); }
一个用来猎取读锁,一个用来猎取写锁。也就是说将文件的读写操纵离开,分红2个锁来分派给线程,从而使得多个线程可以同时举行读操纵。下面的ReentrantReadWriteLock完成了ReadWriteLock接口。
4.ReentrantReadWriteLock
ReentrantReadWriteLock内里供应了许多雄厚的要领,不过最主要的有两个要领:readLock()和writeLock()用来猎取读锁和写锁。
下面经由历程几个例子来看一下ReentrantReadWriteLock细致用法。
如果有多个线程要同时举行读操纵的话,先看一下synchronized到达的结果:
public class Test { private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { final Test test = new Test(); new Thread(){ public void run() { test.get(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.get(Thread.currentThread()); }; }.start(); } public synchronized void get(Thread thread) { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在举行读操纵"); } System.out.println(thread.getName()+"读操纵终了"); } }
这段递次的输出结果会是,直到thread1实行完读操纵以后,才会打印thread2实行读操纵的信息。
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0读操纵终了
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1读操纵终了
而改成用读写锁的话:
public class Test { private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { final Test test = new Test(); new Thread(){ public void run() { test.get(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.get(Thread.currentThread()); }; }.start(); } public void get(Thread thread) { rwl.readLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在举行读操纵"); } System.out.println(thread.getName()+"读操纵终了"); } finally { rwl.readLock().unlock(); } } }
此时打印的结果为:
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0正在举行读操纵
Thread-1正在举行读操纵
Thread-0读操纵终了
Thread-1读操纵终了
申明thread1和thread2在同时举行读操纵。
如许就大大提升了读操纵的效力。
不过要注重的是,如果有一个线程已占用了读锁,则此时其他线程如果要要求写锁,则要求写锁的线程会一向守候开释读锁。
如果有一个线程已占用了写锁,则此时其他线程如果要求写锁或许读锁,则要求的线程会一向守候开释写锁。
关于ReentrantReadWriteLock类中的其他要领感兴趣的朋侪可以自行查阅API文档。
5.Lock和synchronized的挑选
总结来讲,Lock和synchronized有以下几点差别:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的言语完成;
2)synchronized在发作异常时,会自动开释线程占领的锁,因而不会致使死锁征象发作;而Lock在发作异常时,如果没有主动经由历程unLock()去开释锁,则极大概形成死锁征象,因而运用Lock时须要在finally块中开释锁;
3)Lock可以让守候锁的线程响应中断,而synchronized却不可,运用synchronized时,守候的线程会一向守候下去,不可以响应中断;
4)经由历程Lock可以晓得有无胜利猎取锁,而synchronized却没法办到。
5)Lock可以进步多个线程举行读操纵的效力。
在机能上来讲,如果合作资本不猛烈,二者的机能是差不多的,而当合作资本异常猛烈时(即有大批线程同时合作),此时Lock的机能要远远优于synchronized。所以说,在细致运用时要根据恰当状况挑选。
三.锁的相干观点引见
在前面引见了Lock的基础运用,这一节来引见一下与锁相干的几个观点。
1.可重入锁
如果锁具有可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来现实上表清晰明了锁的分派机制:基于线程的分派,而不是基于要领挪用的分派。举个简朴的例子,当一个线程实行到某个synchronized要领时,比方说method1,而在method1中会挪用别的一个synchronized要领method2,此时线程没必要从新去要求锁,而是可以直接实行要领method2。
看下面这段代码就邃晓了:
class MyClass { public synchronized void method1() { method2(); } public synchronized void method2() { } }
上述代码中的两个要领method1和method2都用synchronized润饰了,如果某一时候,线程A实行到了method1,此时线程A猎取了这个对象的锁,而由于method2也是synchronized要领,如果synchronized不具有可重入性,此时线程A须要从新要求锁。然则这就会形成一个问题,由于线程A已持有了该对象的锁,而又在要求猎取该对象的锁,如许就会线程A一向守候永久不会猎取到的锁。
而由于synchronized和Lock都具有可重入性,所以不会发作上述征象。
2.可中断锁
可中断锁:望文生义,就是可以响应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在实行锁中的代码,另一线程B正在守候猎取该锁,大概由于守候时候太长,线程B不想守候了,想先处置惩罚其他事变,我们可以让它中断本身或许在别的线程中中断它,这类就是可中断锁。
在前面演示lockInterruptibly()的用法时已表现了Lock的可中断性。
3.平正锁
平正锁即只管以要求锁的递次来猎取锁。比方同是有多个线程在守候一个锁,当这个锁被开释时,守候时候最久的线程(最早要求的线程)会取得该所,这类就是平正锁。
非平正锁即没法保证锁的猎取是根据要求锁的递次举行的。如许就大概致使某个或许一些线程永久猎取不到锁。
在Java中,synchronized就黑白平正锁,它没法保证守候的线程猎取锁的递次。
而关于ReentrantLock和ReentrantReadWriteLock,它默许状况下黑白平正锁,然则可以设置为平正锁。
看一下这2个类的源代码就清晰了:
在ReentrantLock中定义了2个静态内部类,一个是NotFairSync,一个是FairSync,离别用来完成非平正锁和平正锁。
我们可以在建立ReentrantLock对象时,经由历程以下体式格局来设置锁的平正性:
ReentrantLock lock = new ReentrantLock(true);
如果参数为true示意为平正锁,为fasle为非平正锁。默许状况下,如果运用无参组织器,则黑白平正锁。
别的在ReentrantLock类中定义了许多要领,比方:
isFair() //推断锁是不是是平正锁
isLocked() //推断锁是不是被任何线程猎取了
isHeldByCurrentThread() //推断锁是不是被当前线程猎取了
hasQueuedThreads() //推断是不是有线程在守候该锁
在ReentrantReadWriteLock中也有相似的要领,一样也可以设置为平正锁和非平正锁。不过要记着,ReentrantReadWriteLock并未完成Lock接口,它完成的是ReadWriteLock接口。
4.读写锁
读写锁将对一个资本(比方文件)的接见分红了2个锁,一个读锁和一个写锁。
正由于有了读写锁,才使得多个线程之间的读操纵不会发作冲突。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock完成了这个接口。
可以经由历程readLock()猎取读锁,经由历程writeLock()猎取写锁。
上面已演示过了读写锁的运用要领,在此不再赘述。
原文地点:http://www.cnblogs.com/dolphin0520/p/3923167.html
以上就是java并发编程详解的细致内容,更多请关注ki4网别的相干文章!