疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java进阶多线程核心技术总结2


 

 
1.1使用ReentrantLock实现同步:测试1
  使用下面代码获取ReenTrantLock对象lock
 
private Lock lock = new ReentrantLock();
  调用lock.lock()方法可以对代码进行加锁,调用lock.unlock()方法对代码进行解锁。
 
  注:需要进行加锁的代码放在lock()与unlock()方法中。
 
 
 
1.2使用ReentrantLock实现同步:测试2
  调用了lock()方法代码的线程会持有“对象监视器”,其他线程只有等待锁被释放时再次争抢,效果跟使用synchronized关键字一样,线程间还是顺序执行的。
 
 
 
1.3使用Condition实现等待/同步错误用法与解决
  类ReentrantLock实现等待/同步功能,需要借助于condition对象。
 
  Condition类具有很好的灵活性,可以实现多路通知功能:在一个Lock对象中创建多个Condition(对象监视器)实例,线程对象可以注册在指定的Condition中,从而有选择性地进行线程通知,在调度线程上更灵活。
 
  在使用notify()/notifyAll()方法进行通知时,被通知的线程是由JVM随机选择的,但使用ReentrantLock结合Condition类是可以实现前面介绍过的“选择性通知”,这个功能是常用,且在Condition中是默认的。
 
  通过下面代码获取Condition实例
 
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
  然后使用condition.await()来进行等待操作。
 
  注:在进行await()前,必须调用lock.lock()来获取监视器对象,否则的话就会爆出异常:java.lang.IllegalMonitorStateException(无监视器异常)。
 
 
 
 
 
1.4正确使用Condition实现等待/通知
  Object类中的wait()方法相当于Condition类中的await()方法。
 
  Object类中的wait(long timeout)方法相当于Condition类中的await(long time, TimeUnit unit)方法。
 
  Object类中的notify()方法相当于Condition类中的single()方法。
 
  Object类中的notifyAll()方法相当于Condition类中的singleAll()方法。
 
 
 
 
 
1.5使用多个Condition实现通知部分线程:错误用法
  一个Condition对象可以通过singleAll()方法唤醒所有调用lock.newCondition().await()的线程。
 
  但是若想实现只唤醒部分线程,只能使用多个Condition对象了,这也就是Condition对象可以唤醒部分指定线程,提高程序运行效率。
 
 
 
 
 
1.6使用多个Condition实现通知部分线程:正确用法
  若想实现Condition对象唤醒部分指定线程。一定要是调用等待的Condition对象与调用唤醒的Condition保持是一个Condition对象。
 
 
 
 
 
1.7实现生产者/消费者模式:一对一交替打印
复制代码
public class Service {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
 
    private boolean flag = false;
 
    public void set() {
        try {
            lock.lock();
            while (flag == true) {
                condition.await();
            }
            System.out.println("我是set");
            flag = true;
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
          lock.unlock();
        }
    }
 
    public void get() {
        try {
            lock.lock();
            while (flag == false) {
                condition.await();
            }
            System.out.println("我是get");
            flag = false;
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
复制代码
  一个线程调用set(),一个线程调用get()就可以实现一对一交替打印。
 
 
 
1.8实现生产者/消费者模式:多对多打印
  service代码还是同1.7,执行时开启多条线程即可,但是会遇到“假死”问题,可以通过将signal()改为signalAll()来解决。
 
 
 
1.9公平锁与非公平锁
  锁Lock分为“公平锁”与“非公平锁”。
 
  公平锁:标识线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。
 
  非公平锁:一种线程抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方法可能会导致某些线程一直拿不到锁,自然不公平。
 
  创建的方式就是:
 
private Lock lock = new ReentrantLock(Boolean flag);
  flag为true就是公平锁,flag为false就是非公平锁。
 
 
 
1.10方法getHoldCount()、getQueueLength()和getWaitQueueLength()的测试
  1)int getHoldCount()                       查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
 
  2)int getQueueLength()                  返回正等待获取此锁定线程估计数。
 
      比如有5个线程,一个线程先执行await()方法,那么在调用getQueueLength()方法后返回值是4,说明有4个线程同时在等待lock的释放。
 
  3)int getWaitQueueLength(Condition condition)        返回等待与此锁定相关的给定条件Condition的线程估计数。
 
      比如有5个线程,每个线程都执行了同一个condition对象的await()方法,则调用getWaitQueueLength()返回的值就是5。
 
 
 
   注:以上都是ReentrantLock类的API
 
 
 
1.11方法hasQueuedThread()、hasQueuedThreads()、hasWaiters()的测试
   1)boolean hasQueuedThread(Thread thread)          查询指定的线程是否正在等待获取此锁定。  
 
  2)boolean hasQueuedThreads()                  查询是否有线程正在等待获取此锁定。
 
  3)boolean hasWaiters(Condition condition)             查询是否有线程正在等待与此锁定有关的condition条件。
 
  
 
  注:以上都是ReentrantLock类的API
 
 
 
1.12方法isFair()、isHeldByCurrentThread()和isLocked()的测试
  1)boolean isFair()            判断是不是公平锁
 
  2)boolean isHeldByCurrentThread()    查询当前线程是否保持此锁定
 
  3)boolean isLocked()            查询此锁定是否由任意线程保持
 
 
 
  注:以上都是ReentrantLock类的API
 
 
 
1.13方法lockInterruptibly()、tryLock()和tryLock(Long timeout, TimeUnit unit)的测试
  1)void lockInterruptibly()              如果当前线程未被中断(不处于中断状态),则获得锁定,如果已经被检测中断(处于中断状态)则出现异常。
 
            爆出java.lang.InterruptedException异常。
 
  2)boolean tryLcok()                仅在调用时锁定未被另一个线程保持的情况下,才获得该锁定。
 
  3)boolean tryLock(long timeout, TimeUnit unit)    如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获得该锁定。
 
 
 
  注:以上都是ReentrantLock类的API
 
 
 
1.14方法awaitUninterruptibly()的使用
  此方法为condition的方法await()的替代。可以避免在挂起的时候遇到打断状态(interrupt)而爆出异常。
 
 1.15方法awaitUntil()的使用
  在指定时间后唤醒自己。参数为long
 
  在线程等待期间也可以被其他线程唤醒。
 
  此方法是condition的API
 
 
 
 1.16使用Condition实现顺序执行
  就是利用多个Condition实现顺序执行。
 
复制代码
public class Main {
    volatile private static int flag = 1;
 
    private static ReentrantLock lock = new ReentrantLock();
    final private static Condition c1 = lock.newCondition();
    final private static Condition c2 = lock.newCondition();
    final private static Condition c3 = lock.newCondition();
 
    public static void main(String[] args) throws Exception{
        Thread threadA = new Thread() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (flag != 1) {
                        c1.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.println("ThreadA" + (i + 1));
                    }
                    flag = 2;
                    c2.signalAll();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        };
        Thread threadB = new Thread() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (flag != 2) {
                        c2.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.println("ThreadB" + (i + 1));
                    }
                    flag = 3;
                    c3.signalAll();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        };
        Thread threadC = new Thread() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    while (flag != 3) {
                        c3.await();
                    }
                    for (int i = 0; i < 3; i++) {
                        System.out.println("ThreadC" + (i + 1));
                    }
                    flag = 1;
                    c1.signalAll();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        };
 
        Thread[] a = new Thread[5];
        Thread[] b = new Thread[5];
        Thread[] c = new Thread[5];
        for (int i = 0; i < 5; i++) {
            a[i] = new Thread(threadA);
            b[i] = new Thread(threadB);
            c[i] = new Thread(threadC);
            a[i].start();
            b[i].start();
            c[i].start();
        }
    }
}
复制代码
第二节使用ReentrantReadWriteLock类
2.1读读写写
2.2写写互斥
2.3读写互斥
2.4写读互斥
  实际上ReentrantLock虽然能够保证实例变量的线程安全性,但是效率比较低。
 
  为了在一些不需要操作实例变量的方法中,使用一些效率较高的锁来实现提升运行效率,即使用ReentrantReadWriteLock读写锁。
 
  读写锁表示有两个锁,一个是读操作相关的锁,也成为共享锁;
 
            另一个是写操作相关的锁,也叫排他锁。
 
            也就是说读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程进行写操作时,多个进行读操作的线程都可以获取读锁,而进行写入操作的Thread只有在获取写锁后才能进
 
            行写入操作。
 
  注:多个线程可以同时进行读操作,但同一时刻只允许一个Thread进行写入操作。  
 
 
 
2.1读读共享
  可以通过如下代码获取读写锁
 
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  lock.readLock().lock()
 
  就可以获得读锁,并进行加锁。
 
  lock.readLock().unlock()
 
  释放读锁。
 
  注:读锁是共享锁。
 
 
 
2.2写写互斥  
  lock.writeLock().lock()
 
  就可以获得写锁,并进行加锁。
 
  lock.writeLock().unlock()
 
  释放写锁。
 
  注:写锁是互斥锁。
 
 
 
2.3读写互斥 
2.4写读互斥
  读锁与写锁是互斥的,也就是说不管是读锁先加锁还是写锁先加锁,只要两者相遇都是互斥的,必须等待其中一个释放线程后才能够再获得锁。
 
  “读写”、“写读”、“写写”都是互斥的(因为写锁时互斥锁,所以只要出现写锁,就一定是互斥的)
 
  “读读”是共享的,非互斥的。
 
 
 
本章总结:
  本章用Lock来替换掉了synchronized关键字。
 
  Lock很重要,重要到什么程度?很多并发包的源码都是通过Lock实现的。学好了Lock,有助于你理解你进一步进阶去看一些并发的源码。