Java多线程与高并发五(常用并发工具类)

为了实现各种线程的操作,比如线程阻塞、阻塞恢复,JDK提供了大量模型来操纵线程运行。《Java多线程与高并发五(常用并发工具类)》给大家伙儿分享下JDK中常见的并发工具类。

说一道面试题

实现一个容器,提供add,size方法,两个线程,线程一添加十个元素到容器中,线程二监控容器中元素的个数,当线程一添加到容器的元素个数到5时,线程二给出提示并结束。

解法一:wait/notify和synchronized的组合

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;

/**
 * 实现一个容器,提供add,size方法
 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束
 *
 * @author zab
 * @date 2019-10-20 21:54
 */
public class ThreadCommunicationTest1 {

    private volatile MyContainer1 myContainer1 = new MyContainer1();

    public void f1() {
        synchronized (this) {

            for (int i = 1; i <= 10; i++) {
                myContainer1.add("test");
                System.out.println("add" + i);
                if(i == 5){
                    this.notify();
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }

    public void f2() {
        synchronized (this) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("size等于5啦!!!");
            this.notify();
        }
    }


    public static void main(String[] args) {
        ThreadCommunicationTest1 t = new ThreadCommunicationTest1();
        new Thread(t::f2).start();
        new Thread(t::f1).start();
    }
}

class MyContainer1 {
    List list = new LinkedList<>();

    public void add(String s) {
        list.add(s);
    }

    public int size() {
        return list.size();
    }

}

多次运行结果如下:

 Java多线程与高并发五(常用并发工具类)_第1张图片

 wait/notify的解法比较常规,大概逻辑是,f2方法启动的线程先运行,由于f2方法一来就加锁等待,释放锁,f1方法的线程获得锁,循环输出1、2、3、4、5,当输出5时,叫醒f2,同时自己wait,释放锁,f2得以执行,输出"size等于5啦!!!",输出完毕过后叫醒正在wait的f1,f1得以执行下面的输出。

方法二:lock、condition组合

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现一个容器,提供add,size方法
 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束
 *
 * @author zab
 * @date 2019-10-20 21:54
 */
public class ThreadCommunicationTest2 {

    private volatile MyContainer2 myContainer2 = new MyContainer2();
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();


    public void f1() {

        lock.lock();
        for (int i = 1; i <= 10; i++) {
            System.out.println("add" + i);
            if (i == 5) {
                c2.signal();
                try {
                    c1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        lock.unlock();

    }

    public void f2() {
        lock.lock();
        try {
            c2.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("size等于5啦!!!");
        c1.signal();
        lock.unlock();
    }


    public static void main(String[] args) {
        ThreadCommunicationTest2 t = new ThreadCommunicationTest2();
        new Thread(t::f2).start();
        new Thread(t::f1).start();
    }
}

class MyContainer2 {
    List list = new LinkedList<>();

    public void add(String s) {
        list.add(s);
    }

    public int size() {
        return list.size();
    }

}

 道理同方法一,实现不同。

方法三:LockSupport的park和unpark组合,也是相对简单的用法,不需要锁

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现一个容器,提供add,size方法
 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束
 *
 * @author zab
 * @date 2019-10-20 21:54
 */
public class ThreadCommunicationTest3 {

    private volatile MyContainer3 myContainer3 = new MyContainer3();
    Thread t1 = null;
    Thread t2 = null;

    public void f1() {

        for (int i = 1; i <= 10; i++) {
            myContainer3.add("test");
            System.out.println("add" + i);
            if (i == 5) {
                LockSupport.unpark(t2);
                LockSupport.park();
            }
        }

    }

    public void f2() {
        LockSupport.park();
        System.out.println("size等于5啦!!!");
        LockSupport.unpark(t1);
    }


    public static void main(String[] args) {
        ThreadCommunicationTest3 t = new ThreadCommunicationTest3();
        t.t2 = new Thread(t::f2);
        t.t1 = new Thread(t::f1);
        t.t2.start();
        t.t1.start();
    }
}

class MyContainer3 {
    List list = new LinkedList<>();

    public void add(String s) {
        list.add(s);
    }

    public int size() {
        return list.size();
    }

}

方法四:CountDownLatch倒数门栓的await和countDown组合

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.LockSupport;

/**
 * 实现一个容器,提供add,size方法
 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束
 *
 * @author zab
 * @date 2019-10-20 21:54
 */
public class ThreadCommunicationTest4 {

    private volatile MyContainer4 myContainer4 = new MyContainer4();
    CountDownLatch latch = new CountDownLatch(5);
    CountDownLatch latch1 = new CountDownLatch(1);

    public void f1() {

        for (int i = 1; i <= 10; i++) {
            myContainer4.add("test");
            System.out.println("add" + i);
            latch.countDown();
            if (i == 5) {
                try {
                    latch1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public void f2() {
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("size等于5啦!!!");
        latch1.countDown();
    }


    public static void main(String[] args) {
        ThreadCommunicationTest4 t = new ThreadCommunicationTest4();
        new Thread(t::f2).start();
        new Thread(t::f1).start();
    }
}

class MyContainer4 {
    List list = new LinkedList<>();

    public void add(String s) {
        list.add(s);
    }

    public int size() {
        return list.size();
    }

}

 门栓定义的时候,传入一个值,每调用一次countDown方法,就减少一,当减少到0,门栓对应的await方法就由阻塞状态释放。

方法五:信号量Semaphore

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;

/**
 * 实现一个容器,提供add,size方法
 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束
 *
 * @author zab
 * @date 2019-10-20 21:54
 */
public class ThreadCommunicationTest {

    private volatile MyContainer myContainer = new MyContainer();

    Semaphore semaphore1 = new Semaphore(0);
    Semaphore semaphore2 = new Semaphore(0);

    public void f1() {
        for (int i = 1; i <= 10; i++) {
            myContainer.add("test");
            System.out.println("add" + i);
            if (i == 5) {
                semaphore1.release();
                try {
                    semaphore2.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void f2() {
        try {
            semaphore1.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("size等于5啦!!!");
        semaphore2.release();
    }



    public static void main(String[] args) {
        ThreadCommunicationTest t = new ThreadCommunicationTest();
        new Thread(t::f2).start();
        new Thread(t::f1).start();
    }
}

class MyContainer {
    List list = new LinkedList<>();

    public void add(String s) {
        list.add(s);
    }

    public int size() {
        return list.size();
    }

}

信号量的用法也很简单,定义信号量时会初始化一个数量,可以理解为一块令牌,每当release的时候,就相当于给阻塞的acquire方法发一个令牌,acquire方法才由阻塞状态变为可通行状态。案例中,初始化为0,意味着只有别人release的时候,acquire才能获得一块令牌通行;当初始化为1的时候,acquire方法可以直接通行,但是初始化的值相应减少1,如果要再次调用acquire方法,就会阻塞,需要等到别的线程给令牌(调用release方法),acquire方法才能执行。

在上面的面试题中,我们了解了LockSupport、CountDownLatch、Semaphore等并发工具类的用法,下面介绍几种特殊用途的并发工具类。

循环屏障CyclicBarrier

说再多都不一个简单的demo来的直接:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
 * 
* @author zab
* @date 2019-11-09 18:44
*/
public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(20,()->System.out.println("满二十人,发车"));
        for (int i = 0; i < 100; i++) {
           new Thread(new Runnable(){
                @Override
                public void run() {
                    try {
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

模拟一个场景,我们去公司上班,会在某个地方等公司班车,班车司机会先后到达乘车点,装满一车人就会开往公司。这个类似于CyclicBarrier的作用,一个新起的线程(一个人)过来遇到await方法,就会阻塞(坐车上等着),但是阻塞到循环屏障定义的次数(理解为装满人了)后,就放行所有线程(发车了),并且调用定义的方法。输出的结果是:

Java多线程与高并发五(常用并发工具类)_第2张图片

还可以看到,这个await是循环使用的,一百次的调用可以发车五次。

读写锁ReadWriteLock

读写锁就是读不加锁,写加锁,看示例:

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁
 *
 * @author zab
 * @date 2019-11-10 11:46
 */
public class ReadWriteLockTest {
    Lock readLock;
    Lock writeLock;
    Lock commonLock;
    static Semaphore readSemaphore = new Semaphore(0);
    static Semaphore writeSemaphore = new Semaphore(0);
    static Semaphore writeSemaphore1 = new Semaphore(0);
    static Semaphore readSemaphore1 = new Semaphore(0);
    ThreadPoolExecutor threadPoolExecutor;

    Map map = new HashMap<>();

    public ReadWriteLockTest(Lock readLock, Lock writeLock) {
        this.readLock = readLock;
        this.writeLock = writeLock;
    }

    public static void main(String[] args) throws Exception {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        ReentrantLock commonLock = new ReentrantLock();
        //ReadWriteLockTest test = new ReadWriteLockTest(lock.readLock(),lock.writeLock());
        ReadWriteLockTest test = new ReadWriteLockTest(commonLock, commonLock);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            new Thread(test.new WriteOperation()).start();
        }
        writeSemaphore.release(100);
        writeSemaphore1.acquire(100);
        long end = System.currentTimeMillis();

        System.out.println("lock锁写操作耗时:" + (end - start));


        long start1 = System.currentTimeMillis();

        for (int i = 0; i < 100; i++) {
            new Thread(test.new ReadOperation()).start();
        }
        readSemaphore.release(100);
        readSemaphore1.acquire(100);
        long end1 = System.currentTimeMillis();
        System.out.println("lock锁读操作耗时:" + (end1 - start1));

    }

    class WriteOperation implements Runnable {

        @Override
        public void run() {
            writeLock.lock();
            try {
                writeSemaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            writeSemaphore1.release();
            writeLock.unlock();
        }
    }

    class ReadOperation implements Runnable {
        @Override
        public void run() {
            readLock.lock();
            try {
                readSemaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            readSemaphore1.release();
            readLock.unlock();
        }

    }
}

我们用四个信号量精准控制读写操作的进行,记录了读写操作的时间,假设一次写操作耗时10ms,读操作耗时100ms,分别用sleep模拟读写逻辑,同时对读写操作都加锁,锁的初始化可为普通ReentrantLock,可为ReentrantWriteReadLock,对比其效率:

Java多线程与高并发五(常用并发工具类)_第3张图片

Java多线程与高并发五(常用并发工具类)_第4张图片 

可以看到ReentrantLock读操作耗时10秒左右,而ReentrantWriteReadLock的读操作耗时0.1秒左右。读性能可见一斑。

交换器Exchanger

交换器用于两个线程之间交换数据,示例代码:

import java.util.concurrent.Exchanger;

/**
 * 交换器
 *
 * @author zab
 * @date 2019-11-10 12:00
 */
public class ExchangerTest {
    String s1 = "s1";
    String s2 = "s2";
    Exchanger exchanger = new Exchanger<>();

    public void f1() {
        try {
            s1 = exchanger.exchange(s1);
            System.out.println("f1----" + s1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void f2() {
        try {
            Thread.sleep(1000);
            s2 = exchanger.exchange(s2);
            System.out.println("f2----" + s2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ExchangerTest et = new ExchangerTest();
        new Thread(et::f2).start();
        new Thread(et::f1).start();
    }
}

这段代码表示f1和f2之间把字符串s1和s2交换一下,输出结果:

Java多线程与高并发五(常用并发工具类)_第5张图片

exchange方法工作的时候,必须两个线程同时到达才会交换数据,输出上图结果之前,其实有一秒的阻塞。

 

你可能感兴趣的:(Java并发编程)