Java 5 以后,Java在内置关键字sychronized的基础上又增长了一个新的处理锁的方式,Lock类。html
因为在Java线程间通讯:volatile与sychronized中,咱们已经详细的了解了synchronized,因此咱们如今主要介绍一下Lock,以及将Lock与synchronized进行一下对比。java
synchronized修饰的代码只有获取锁的线程才可以执行,其余线程只能等待该线程释放锁。一个线程释放锁的状况有如下方式:编程
咱们在Java多线程的生命周期,实现与调度中谈过,锁会由于等待I/O,sleep()方法等缘由被阻塞而不释放锁,此时若是线程还处于用synchronized修饰的代码区域里,那么其余线程只能等待,这样就影响了效率。所以Java提供了Lock来实现另外一个机制,即不让线程无限期的等待下去。安全
思考一个情景,当多线程读写文件时,读操做和写操做会发生冲突,写操做和写操做会发生冲突,但读操做和读操做不会有冲突。若是使用synchronized来修饰的话,就极可能形成多个读操做没法同时进行的可能(若是只用synchronized修饰写方法,那么可能形成读写冲突,若是同时修饰了读写方法,则会有读读干扰)。此时就须要用到Lock,换言之Lock比synchronized提供了更多的功能。多线程
使用Lock须要注意如下两点:并发
Lock类自己是一个接口,其方法以下:ide
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 = ...; lock.lock(); try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 }
public void method() throws InterruptedException { lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); } }
Lock lock = ...; if(lock.tryLock()) { try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 } }else { //若是不能获取锁,则直接作其余事情 }
ReentrantLock译为“可重入锁”,咱们在Java多线程:synchronized的可重入性中已经明白了什么是可重入以及理解了synchronized的可重入性。ReentrantLock是惟一实现Lock接口的类。优化
考虑到如下情景,一个仅出售双人票的演唱会进行门票出售,有三个售票口同时进行售票,买票须要100ms时间,每张票出票须要100ms时间。该如何设计这个情景?this
package com.cielo.LockTest; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static java.lang.Thread.sleep; /** * Created by 63289 on 2017/4/10. */ class SoldTicket implements Runnable { Lock lock = new ReentrantLock();//使用可重入锁 private volatile Integer ticket;//保证从主内存获取 SoldTicket(Integer ticket) { this.ticket = ticket;//提供票数 } private void sold() { lock.lock();//锁定操做放在try代码块外 try { if (ticket <= 0) return;//当ticket==2时可能有多个线程进入sold方法,一个线程运行后另外两个线程须要退出。 sleep(200);//买票0.1s,出票0.1s --ticket; System.out.println("The first ticket is sold by "+Thread.currentThread().getId()+", "+ticket+" tickets leave.");//获取线程id来识别出票站。 sleep(100);//出票0.1s --ticket; System.out.println("The second ticket is sold by "+Thread.currentThread().getId()+", "+ticket+" tickets leave."); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } @Override public void run() { while (ticket > 0) { sold(); } } } public class LockTest { public static void main(String[] args) { SoldTicket soldTicket = new SoldTicket(20); new Thread(soldTicket).start(); new Thread(soldTicket).start(); new Thread(soldTicket).start(); } }
上面这段代码结果以下:
The first ticket is sold by 11, 19 tickets leave. The second ticket is sold by 11, 18 tickets leave. The first ticket is sold by 13, 17 tickets leave. The second ticket is sold by 13, 16 tickets leave. The first ticket is sold by 13, 15 tickets leave. The second ticket is sold by 13, 14 tickets leave. The first ticket is sold by 12, 13 tickets leave. The second ticket is sold by 12, 12 tickets leave. The first ticket is sold by 11, 11 tickets leave. The second ticket is sold by 11, 10 tickets leave. The first ticket is sold by 11, 9 tickets leave. The second ticket is sold by 11, 8 tickets leave. The first ticket is sold by 13, 7 tickets leave. The second ticket is sold by 13, 6 tickets leave. The first ticket is sold by 13, 5 tickets leave. The second ticket is sold by 13, 4 tickets leave. The first ticket is sold by 13, 3 tickets leave. The second ticket is sold by 13, 2 tickets leave. The first ticket is sold by 13, 1 tickets leave. The second ticket is sold by 13, 0 tickets leave.
若是咱们不对售票操做进行锁定,则会有如下几个问题:
显然,本题的情景用synchronized也能够很容易的实现,实际上Lock有别于synchronized的主要点是lockInterruptibly()和tryLock()这两个能够对锁进行控制的方法。
回到开头synchronized缺陷的介绍,实际上,Lock接口的重要衍生接口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(); }
便是它只提供了readLock()和writeLock()两个操做,这两个操做均返回一个Lock类的实例。两个操做一个获取读锁,一个获取写锁,将读写分开进行操做。ReadWriteLock将读写的锁分开,可让多个读操做并行,这就大大提升了效率。使用ReadWriteLock时,用读锁去控制读操做,写锁控制写操做,进而实现了一个能够在以下的大量读少许写且读者优先的情景运行的锁。
ReentrantReadWriteLock是ReadWriteLock的惟一实例。同时提供了不少操做方法。ReentratReadWriteLock接口实现的读锁写锁进入有以下要求:
private SomeClass someClass;//资源 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//建立锁 private final Lock readLock = readWriteLock.readLock();//读锁 private final Lock writeLock = readWriteLock.writeLock();//写锁 //读方法 readLock.lock(); try { result = someClass.someMethod(); } catch (Exception e) { e.printStackTrace(); } finally { readLock.unlock(); } //写方法,产生新的SomeClass实例tempSomeClass writeLock.lock(); try{ this.someClass = tempSomeClass;//更新 }catch (Exception e) { e.printStackTrace(); } finally{ writeLock.unlock(); }
公平锁即当多个线程等待的一个资源的锁释放时,线程不是随机的获取资源而是等待时间最久的线程获取资源(FIFO)。Java中,synchronized是一个非公平锁,没法保证锁的获取顺序。ReentrantLock和ReentrantReadWriteLock默认也是非公平锁,但能够设置成公平锁。咱们前面的实例中初始化ReentrantLock和ReentrantReadWriteLock时都是无参数的。实际上,它们提供一个默认的boolean变量fair,为true则为公平锁,为false则为非公平锁,默认为false。所以,当咱们想将其实现为公平锁时,仅须要初始化时赋值true。即:
Lock lock = new ReentrantLock(true);
考虑前面卖票的实例,若是改成公平锁(尽管这和情景无关),则结果输出很是整齐以下:
The first ticket is sold by 11, 19 tickets leave. The second ticket is sold by 11, 18 tickets leave. The first ticket is sold by 12, 17 tickets leave. The second ticket is sold by 12, 16 tickets leave. The first ticket is sold by 13, 15 tickets leave. The second ticket is sold by 13, 14 tickets leave. The first ticket is sold by 11, 13 tickets leave. The second ticket is sold by 11, 12 tickets leave. The first ticket is sold by 12, 11 tickets leave. The second ticket is sold by 12, 10 tickets leave. The first ticket is sold by 13, 9 tickets leave. The second ticket is sold by 13, 8 tickets leave. The first ticket is sold by 11, 7 tickets leave. The second ticket is sold by 11, 6 tickets leave. The first ticket is sold by 12, 5 tickets leave. The second ticket is sold by 12, 4 tickets leave. The first ticket is sold by 13, 3 tickets leave. The second ticket is sold by 13, 2 tickets leave. The first ticket is sold by 11, 1 tickets leave. The second ticket is sold by 11, 0 tickets leave.