赞
踩
同步控制是并发程序必不可少的重要手段。
重入锁可以完全替代关键字synchronized。重入锁使用java.util.concurrent.locks.ReentrantLock类来实现。
- public class ReenterLock implements Runnable {
- public static ReentrantLock lock = new ReentrantLock();
- public static int i=0;
- @Override
- public void run() {
- for (int j = 0; j < 100000; j++) {
- lock.lock();
- try {
- i++;
- }finally {
- lock.unlock();
- }
- }
- }
- }
与关键字synchronized相比,重入锁有着显示的操作过程。必须手动指示何时加锁,何时释放锁。因此,重入锁对逻辑控制的灵活性要远远优于synchronized。
重入锁是可以反复进入的,一个线程可以多次获得同一把锁,但是要记得释放相同次数。
重入锁的高级功能:
(1)中断响应
对于关键字synchronized来说,如果一个线程在等待锁,那么结果只有两种情况,要么获得这把锁继续执行,要么保持等待。而重入锁则使得线程在等待锁的时候可以被中断。这种情况对于处理死锁是有一定帮助的。
- public class IntLock implements Runnable {
- public static ReentrantLock lock1 = new ReentrantLock();
- public static ReentrantLock lock2 = new ReentrantLock();
- int lock;
-
- public IntLock(int lock) {
- this.lock = lock;
- }
-
- @Override
- public void run() {
- try {
- if (lock == 1) {
- lock1.lockInterruptibly();//这个请求锁的方法可以对中断进行相应
- Thread.sleep(500);
- lock2.lockInterruptibly();
- } else {
- lock2.lockInterruptibly();
- Thread.sleep(500);
- lock1.lockInterruptibly();
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }finally {
- if (lock1.isHeldByCurrentThread())//查询当前线程是否保持该锁
- lock1.unlock();
- if (lock2.isHeldByCurrentThread())
- lock2.unlock();
- System.out.println(Thread.currentThread().getId()+"线程退出");
- }
- }
-
- public static void main(String[] args) throws InterruptedException {
- IntLock r1 = new IntLock(1);
- IntLock r2 = new IntLock(2);
- Thread t1 = new Thread(r1);
- Thread t2 = new Thread(r2);
- t1.start();t2.start();
- Thread.sleep(1000);
- t2.interrupt();//中断t2
- }
- }
线程t1和t2启动后,由于互相请求对方持有的锁造成死锁。如果不中断t2,那么会无限期等待下去。中断t2后,t2会放弃对lock1的申请,同时释放已获得的lock2,从而使得t1获得lock2顺利执行下去。
中断后两个线程都会退出,但真正完成工作的只有t1。
(2)锁申请等待限时
除了等待外部通知以外,要避免死锁还有另一种方法:限时等待。可以使用tryLock() 方法进行一次限时的等待。
- public class TimeLock implements Runnable {
- public static ReentrantLock lock = new ReentrantLock();
- @Override
- public void run() {
- try {
- if (lock.tryLock(5, TimeUnit.SECONDS)) {
- Thread.sleep(6000);
- } else {
- System.out.println("get Lock failed");
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }finally {
- if (lock.isHeldByCurrentThread()) {
- lock.unlock();
- }
- }
- }
-
- public static void main(String[] args) {
- TimeLock t1 = new TimeLock();
- Thread thread1 = new Thread(t1);
- Thread thread2 = new Thread(t1);
- thread1.start();
- thread2.start();
- }
- }
tryLock() 方法接受两个参数,等待时长和计时单位。由于占用锁的线程会持有锁长达6秒,故另一个线程无法在5秒之内获得锁,请求失败。
ReentrantLock.tryLock()不带参数时,线程会尝试获得锁,申请成功返回true,申请失败则会立即返回false,不在进行等待。
(3)公平锁
使用synchronized关键字进行锁控制时,产生的锁是非公平的,就是说会在等待队列中随机挑选一个获得锁。
重入锁允许对公平性进行设置,构造函数如下:
public ReentrantLock(boolean fair)
当参数为true时,表示锁是公平的。公平锁按照时间先后顺序,保证不会产生饥饿现象。但是实现成本较高,性能比较地下,默认状况下为非公平的。
与Object.wait() 和notify() 作用大致相同。
- public class ReenterLockCondition implements Runnable {
- public static ReentrantLock lock = new ReentrantLock();
- //通过newCondition方法生成一个与当前重入锁绑定的Condition实例。
- public static Condition condition = lock.newCondition();
-
- @Override
- public void run() {
-
- try {
- lock.lock();
- //await方法会使当前线程等待在Condition上,同时释放当前锁
- condition.await();
- System.out.println("Thread is going on");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }finally {
- lock.unlock();
- }
- }
-
- public static void main(String[] args) throws InterruptedException {
- ReenterLockCondition reenterLockCondition = new ReenterLockCondition();
- Thread thread = new Thread(reenterLockCondition);
- thread.start();
- Thread.sleep(2000);
- //在signal方法调用时,要求线程先获得相关的锁
- lock.lock();
- //调用signal方法,系统会从当前Condition对象的等待队列中唤醒一个线程
- //同理,signalAll方法会唤醒所有线程
- condition.signal();
- //在signal方法调用后,需要释放相关的锁,让给被唤醒的线程
- //若没有释放锁,线程thread无法继续执行
- lock.unlock();
- }
- }
信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量可以指定多个线程,同时访问一个资源。
- //信号量的构造函数,构造时必须制定准入数,即同时能申请多少个许可
- public Semaphore(int permits)
- public Semaphore(int permits,boolean fair) //第二个参数可以指定是否公平
-
- //信号量的方法
- public void acquire() //尝试获得一个准入许可
- public void acquireUninterruptibly() //不响应中断
- public boolean tryAcquire() //尝试获得一个许可,成功返回true,失败返回false,不等待
- public boolean tryAcquire(long timeout, TimeUnit unit) //等待限时
- public void release() //用于访问结束后释放一个许可
一个简单的例子:
- public class SemapDemo implements Runnable {
- //声明一个包含5个许可的信号量
- final Semaphore semp = new Semaphore(5);
-
- @Override
- public void run() {
- try {
- //申请信号量
- semp.acquire();
- Thread.sleep(2000);
- System.out.println(Thread.currentThread().getId()+":done!");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }finally {
- //离开前要释放信号量
- semp.release();
- }
- }
-
- public static void main(String[] args) {
- ExecutorService exec = Executors.newFixedThreadPool(20);
- final SemapDemo demo = new SemapDemo();
- //同时开启20个线程,但每次只能有5个线程进行输出
- for (int i = 0; i < 20; i++) {
- exec.submit(demo);
- }
- }
- }
读写分离锁能有效地帮助减少锁竞争,提升系统性能。
一般来说,读与读之间不互斥,可以并行操作;读与写、写与写之间仍会互相阻塞。
- public class ReadWriteLockDemo {
- // private static Lock lock = new ReentrantLock();
- private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
- private static Lock readLock = readWriteLock.readLock(); //读取锁
- private static Lock writeLock = readWriteLock.writeLock(); //写入锁
- private int value;
-
- public Object handleRead(Lock lock) throws InterruptedException {
- try {
- lock.lock(); //模拟读操作
- Thread.sleep(1000); //读操作的耗时越多,读写锁的优势就越明显
- return value;
- }finally {
- lock.unlock();
- }
- }
-
-
- public void handleWrite(Lock lock, int index) throws InterruptedException {
- try {
- lock.lock(); //模拟写操作
- Thread.sleep(1000); //写操作的耗时越多,读写锁的优势就越明显
- value = index;
- }finally {
- lock.unlock();
- }
- }
-
- public static void main(String[] args) {
- final ReadWriteLockDemo demo = new ReadWriteLockDemo();
- Runnable readRunable=new Runnable() {
- @Override
- public void run() {
- try {
- demo.handleRead(readLock);
- // demo.handleRead(lock);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
-
- Runnable writeRunable =new Runnable() {
- @Override
- public void run() {
- try {
- demo.handleWrite(writeLock,new Random().nextInt());
- // demo.handleWrite(lock, new Random().nextInt());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
-
- for (int i = 0; i < 18; i++) {
- new Thread(readRunable).start();
- }
- for (int i = 18; i < 20; i++) {
- new Thread(writeRunable).start();
- }
- }
- }
以上程序使用读写锁时2秒多就能执行完, 因为读操作是并行的,而使用重入锁时需要20多秒(读操作也会阻塞)。
通常用来控制线程等待,可以让某一个线程等待直到倒计数器结束再开始执行。
- public class CountDownLatchDemo implements Runnable {
- //创建一个倒计数器的实例,参数为计数器的计数个数
- //参数为10表示需要10个线程完成任务后等待在CountDownLatch上的线程才能继续执行
- static final CountDownLatch end = new CountDownLatch(10);
- static final CountDownLatchDemo demo = new CountDownLatchDemo();
-
- @Override
- public void run() {
- try {
- //模拟线程执行时间
- Thread.sleep(new Random().nextInt(10)*1000);
- System.out.println("check complete");
- //一个线程已经完成了任务,倒计数器减一
- end.countDown();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- public static void main(String[] args) throws InterruptedException {
- ExecutorService exec = Executors.newFixedThreadPool(10);
- for (int i = 0; i < 10; i++) {
- exec.submit(demo);
- }
- //要求主线程等待所有检查任务全部完成后,才能继续执行
- end.await();
- System.out.println("go!");
- exec.shutdown();
- }
- }
类似CountDownLatch,但功能更强大。比如,把计数器设置为10,那么在凑齐第一批10个线程后,一起开始执行任务,直到任务完成,然后计数器就会归零,接着凑齐下一批10个线程。
可以在线程内任意位置让线程阻塞。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。