线程-三

135 阅读4分钟

[TOC]

Lock

ReentrantLock

在JDK1.5的版本中新加入了很多特性。其中就有ReentrantLockReentrantLocksynchronized更灵活。功能也更强大。学习线程的过程中也是不可避免的。
new 一个ReentrantLock 返回一个Lock接口对象。
通过lock加锁。unlock解锁。在lock与unlock之间的代码就是同步区域。

使用Condition实现等待/通知

关键字synchronizedwaitnotify方法相结合可以实现等待通知模式,类ReentrantLock也可以实现同样的功能,但是要借助于Condition对象,它有更好的灵活性,比如实现多路通知功能,也就是说在一个lock对象创建多个Condition实例,线程对象可以注册在指定的Condition中,从而可以有选择的进行线程通知,在调度线程上更灵活。
在notify中,被通知的线程是JVM随机选择的。但使用ReentrantLock结合Condition可以实现选择性通知。
代码如下

public class ThreadCondition {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        new Thread(()->{
            service.await();
        }).start();
        Thread.sleep(1000);
        service.signal();
    }
}
class Service {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void await(){
        try{
            lock.lock();
            System.out.println("A");
            condition.await();
            System.out.println("A");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void signal(){
        try {
            lock.lock();
            System.out.println("解锁");
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}

其中Conditionawait等于Objectwaitsignal等于notify
signalAll等于notifyAll

使用Condition实现选择性通知。
public class ThreadConditionSelect {
    public static void main(String[] args) throws InterruptedException {
        ThreadConditionSelectService service1 = new ThreadConditionSelectService();
        new Thread(() -> {
            service1.awaitA();
        }).start();
        new Thread(() -> {
            service1.awaitB();
        }).start();
        Thread.sleep(500);
        service1.signalAll_A();
    }
}

class ThreadConditionSelectService {
    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println("A:wait");
            conditionA.await();
            System.out.println("A:run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println("B:wait");
            conditionB.await();
            System.out.println("B:run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_A() {
        try {
            lock.lock();
            System.out.println("A:signalAll");
            conditionA.signalAll();
            System.out.println("A:signalAll");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_B() {
        try {
            lock.lock();
            System.out.println("B:signalAll");
            conditionB.signalAll();
            System.out.println("B:signalAll");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

在这代码中最终只有A被唤醒。
通过这种方式我们可以唤醒指定种类的线程。

公平锁与非公平锁

锁lock分为公平锁与非公平锁。公平锁表示线程获取锁的顺序是安装线程加锁的顺序来分配的,即FIFO先进先出的顺序。而非公平锁则是抢占。随机获取锁。所以可能会有一些线程一直拿不到锁。
通过创建ReentrantLock的构造函数,传入Boolean值设置锁的类型。
bd0c3c3058122d3cde548384342f72e6.png
通过源码,我们可以知道,在不传入值的情况下默认是非公平锁。

public class ThreadGPLock {
    public static void main(String[] args) {
        ThreadGPLockService gpLockService = new ThreadGPLockService();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":运行了");
                gpLockService.await();
            }).start();
        }
    }
}

class ThreadGPLockService {
    private Lock lock = new ReentrantLock(true);
    public void await() {
        try { 
        lock.lock();
  System.out.println(Thread.currentThread().getName() + ":获得锁");
        } finally {
            lock.unlock();
        }
    }
}

通过结果我们可以看出,当锁是公平锁时,谁先等待那么谁就先获取到锁。而非公平锁则是相互竞争。

ReentrantLock类的方法

getHoldCount()的作用时查询当前线程锁保持锁定的个数。也就是调用lock()方法的次数。
getQueueLength()是返回正在等待获取锁的线程数,表示没有调用await的线程。
getWaitQueueLength()getQueueLength()相反,它表示已经调用了await方法的线程数。
hasQueuedThread(Thread)的作用是查询指定线程释放在等待获取此锁定。
HasQueuedThreads()的作用是查询是否有线程正在等待获取此锁定。
hasWaiters(Condition condition)查询是否有线程正在等待与此锁定有关的condition条件。

lock接口中的方法。

lockInterruptibly()的作用是如果当前线程未被中断,则获取锁定。如果已经被中断则出现异常0

tryLock()的作用是如果锁没被其他线程获取就获得锁。

tryLock(long time, TimeUnit unit)在给定时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。

ReentrantReadWriteLock

ReentrantLock类具有完全排他的效果,在同一时间下只有一个线程在执行lock方法后面的任务,虽然保证了安全性,但是效率相对低下。我们可以使用ReentrantReadWriteLock读写锁,在某些不需要操作实例变量中,完全可以使用读写锁来提升代码运行效率。
读写锁有两个锁,一个是读相关的锁,叫做共享锁,另一个是写操作相关的锁,也称排他锁。写锁与任何锁都互斥。
同一个时刻读锁允许多个线程获取锁,而写锁只允许一个线程获取锁

public class ThreadReadWriteLock {
    public static void main(String[] args) {
        Service service = new Service();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                service.read();
            }).start();
        }
    }
    static class Service{
        private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
        public void read(){
            try {
                lock.readLock().lock();
                System.out.println("获取读锁:"+Thread.currentThread().getName());
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        }
    }
}

结果如下:

获取读锁:Thread-0
获取读锁:Thread-3
获取读锁:Thread-5
获取读锁:Thread-2
获取读锁:Thread-1
获取读锁:Thread-6
获取读锁:Thread-4
获取读锁:Thread-8
获取读锁:Thread-7
获取读锁:Thread-9

通过lock.readLock().lock()加一个读锁。
通过lock.writeLock().lock()加一个写锁
如果这个锁是一个ReentrantLock锁,则同一时刻只能允许一个线程运行,所以如果有10个线程。每个线程睡眠0.5秒,十个线程则需要睡5秒。在使用读锁之后,所有线程都可以同时运行。
可见效率要高很多。
写线程与ReentrantLock基本上一致。