Java并发容器和同步工具类

同步容器类

早期的同步容器类Vector、Hashtable和Collections.synchronizedXXX创建的同步容器,封装所有public方法,以保证线程安全。

问题:迭代操作期间可能抛ArrayIndexOutOfBoundsException或ConcurrentModificationException

示例代码:

//遍历vector时,其他线程修改vector,可能抛ArrayIndexOutOfBoundsException

for (int i = 0; i < vector.size(); i++) {

    //操作vector.get(i);

}

List<Person> personList = Collections.synchronizedList(new ArrayList<Person>());

//遍历personList时,其他线程修改personList,导致计数器变化,再调用next或hasNext时可能会抛ConcurrentModificationException

for (Person person : personList) {

    //操作person

}

所以,需要在遍历操作期间持有容器的锁,可能会导致并发性和吞吐量降低。

注:容器的toString、hashCode、equals、containsAll、removeAll、retainAll等也隐含执行遍历操作


并发容器

Java 5.0中增加了ConcurrentHashMap和CopyOnWriteArrayList以代替同步Map和List,还增加了Queue用来临时保存一组等待处理的元素,提供了几种实现ConcurrentLinkedQueue、PriorityQueue;还增加了Queue的扩展类BlockingQueue阻塞队列

Java6也增加了ConcurrentSkipListMap和ConcurrentSkipListSet,分别替代SortedMap和SortedSet

ConcurrentHashMap(Java)使用了分段锁(Lock Striping)以使读取操作线程和写入操作线程可以并发的访问Map,它提供的迭代器不会抛出ConcurrentModificationException;在JDK1.8中,ConcurrentHashMap的实现原理摒弃了这种设计,而是选择了与HashMap类似的数组+链表+红黑树的方式实现,而加锁则采用CAS和synchronized实现。

示例代码:

public class ConcurrentContainer {
    private static final ConcurrentHashMap conHashMap = new ConcurrentHashMap();
    private static CountDownLatch latch = new CountDownLatch(1);

    public static void readMap() {
        Thread rThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                    //每隔1秒遍历一次
                    Map.Entry entry;
                    while (true) {
                        System.out.println("***********************");
                        for (Object enObj : conHashMap.entrySet()) {
                            entry = (Map.Entry) enObj;
                            System.out.println("[Key=" + entry.getKey() + "]-[Value=" + entry.getValue() + "]");
                        }
                        System.out.println("***********************");
                        System.out.println("");
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        rThread.start();
    }

    public static void writeMap() {
        Thread wThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                    //每隔10秒遍历一次
                    Random ran = new Random(1);
                    while (true) {
                        conHashMap.put("key" + (int) (ran.nextFloat() * 1000), Math.abs(ran.nextInt() * 1000));
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        wThread.start();
    }


    public static void main(String[] args) {
        readMap();
        writeMap();
        latch.countDown();
    }
}

CopyOnWriteArrayList/CopyOnWriteArraySet,在每次修改时,都会创建并重新发布一个新的容器副本,从而实现可变性,它提供的迭代器不会抛出ConcurrentModificationException。每当修改容器时都会复制底层数组,需要一定的开销,因此,仅当迭代操作远多于修改操作时,才 应该使用“写入时复制”容器。

示例代码:

public class ConListContainer {
    private static final CopyOnWriteArrayList cowList = new CopyOnWriteArrayList();
    private static CountDownLatch latch = new CountDownLatch(1);

    public static void readAndWriteList() throws InterruptedException {
        Thread rThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                    //每隔1秒遍历一次
                    while (true) {
                        System.out.println("***********************");
                        System.out.println("cowList is: " + cowList);
                        System.out.println("***********************");
                        System.out.println("");
                        Thread.sleep(1000);
                        if (cowList.size() > 10)
                            break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread wThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                    //每隔10秒遍历一次
                    Random ran = new Random(1);
                    while (true) {
                        cowList.add(ran.nextInt());
                        Thread.sleep(500);
                        if (cowList.size() > 10)
                            break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        rThread.start();
        wThread.start();
        latch.countDown();
        rThread.join();
        wThread.join();
        System.out.println("Finally, cowList is " + cowList);
    }

    public static void main(String[] args) throws InterruptedException {
        readAndWriteList();
    }
}

阻塞队列

阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法。如果队列已经满了,那么put方法将阻塞直到有空间可用;如果队列为空,那么take方法将会阻塞知道有元素可用。BlockingQueue的实现包括ArrayBlockingQueue(基于数组,可做有界队列)、LinkedBlockingQueue(基于链表,可做有界队列和无界队列)、SynchronousQueue(队列中最多只能有一个元素,使用上可选公平模式和非公平模式)等。LinkedBlockingQueue、ArrayBlockingQueue是FIFO队列,分别类似LinkedList和ArrayList,但比同步List有更好的并发性。BlockingQueue适用于生产者-消费者模式(所有消费者共享一个工作队列)的问题。

示例代码:

public class BlockingQueueTest {
    private static final BlockingQueue blockingQueue = new ArrayBlockingQueue(20);

    private static void testBlockingQueue() throws InterruptedException {
        Thread pThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Random rand = new Random(1);
                int temp;
                try {
                    while (blockingQueue.size() < 20) {
                        temp = (int) (1000 * rand.nextFloat());
                        System.out.println("pThread 放入: " + temp);
                        blockingQueue.put(temp);
                        System.out.println("pThread读到blockingQueue当前有" + blockingQueue.size() + "个元素");
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
            }
        });

        Thread cThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Object obj;
                try {
                    //只要有就一直取
                    while ((obj = blockingQueue.take()) != null) {
                        //take方法会一直阻塞知道有元素可拿
                        Thread.sleep(3000);
                        System.out.println("cThread拿到了:" + obj);
                        System.out.println("cThread读到blockingQueue当前有" + blockingQueue.size() + "个元素");
                        if (0 == blockingQueue.size()){
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        pThread.start();
        cThread.start();
        pThread.join();
        cThread.join();
        System.out.println("最后,blockingQueue当前有: " + blockingQueue.size() + "个元素");
    }

    public static void main(String[] args) throws InterruptedException {
        testBlockingQueue();
    }
}

Deque是一个双端队列,支持FIFO、FILO,实现了在队列头和队列尾的高效插入和移除,具体实现包括ArrayDeque(非线程安全)和LinkedBlockingDeque(线程安全)。Deque适用于工作密取(Work Stealing)模式(每个消费者都有各自的双端队列)的问题,提供了更高的可伸缩性。

示例代码:

public class BlockingDequeTest {
    private static final BlockingDeque blockingDeque = new LinkedBlockingDeque();
    private static final CountDownLatch latch = new CountDownLatch(1);
    private static final AtomicLong ATOM_INT = new AtomicLong(100L);
    private static final String ALL_LETTERS = "abcdefghijklmnopqrstuvwxyz";

    private static void testBlockingDeque() throws InterruptedException {
        Thread lrThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                    while (true) {
                        Object obj = blockingDeque.pollFirst();
                        if (obj != null) {
                            System.out.println("lrThread gets: [" + obj + "] from Deque");
                            if (ATOM_INT.decrementAndGet() < 0L) {
                                System.out.println("lrThread: tired of this stupid game, BYE!");
                                break;
                            }
                        }
                        Thread.sleep(40);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        Thread lwThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                    String str;
                    for (; ; ) {
                        str = genRandomStr();
                        blockingDeque.offerFirst(str);
                        System.out.println("lwThread writes: [" + str + "] to Deque");
                        if (ATOM_INT.longValue() < 0L) {
                            System.out.println("lwThread: I've got a bad feeling about this...");
                            break;
                        }
                        Thread.sleep(200);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread rrThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                    while (true) {
                        Object obj = blockingDeque.pollLast();
                        if (null != obj) {
                            System.out.println("rrThread gets: [" + obj + "] from Deque");
                            if (ATOM_INT.decrementAndGet() < 0L) {
                                System.out.println("rrThread: OUT!");
                                break;
                            }
                        }
                        Thread.sleep(30);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread rwThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                    String str;
                    for (; ; ) {
                        str = genRandomStr();
                        blockingDeque.addLast(str);
                        System.out.println("lwThread writes: [" + str + "] to Deque");
                        if (ATOM_INT.longValue() < 0L) {
                            System.out.println("rwThread: Ew!");
                            break;
                        }
                        Thread.sleep(150);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        lrThread.start();
        lwThread.start();
        rrThread.start();
        rwThread.start();
        latch.countDown();
        lrThread.join();
        lwThread.join();
        rrThread.join();
        rwThread.join();
        System.out.println("Finally, blockingDeque is: " + blockingDeque);
    }

    private static String genRandomStr() {
        Random ran = new Random();
        String tempStr = "";
        int idx1;
        for (int i = 0; i < 3; i++) {
            idx1 = Math.abs((int) (26 * ran.nextFloat()));
            tempStr += ALL_LETTERS.substring(idx1, idx1 + 1);
        }
        return tempStr + Math.abs((int) (10 * ran.nextFloat()));
    }

    public static void main(String[] args) throws InterruptedException {
        testBlockingDeque();
    }
}

阻塞方法与中断方法

线程可能会阻塞或暂停执行,原因有多种:等待I/O操作结束(BLOCKED),等待获得一个锁(WAITING),等待从Thread.sleep方法中醒来(TIMED_WAITING),或是等待另一个线程的结算结果。当代码中调用了一个将抛出InterruptedException异常的方法时,通常可以传递InterruptedException给调用者或通过调用当前线程上的interrupt方法恢复中断。如:

public class TaskRunnable implements Runnable {
    BlockingQueue<Task> queue;

	 @Override
     public void run() {
        try {
            processTask(queue.take());
        } catch (InterruptedException e) {
            //恢复被中断的状态
            Thread.currentThread().interrupt();
        }
     }
}

同步工具类

闭锁(CountDownLatch)

闭锁是一种同步工具类,可以延迟线程的进度直到其到达中止状态。底层是基于 AQS(AbstractQueuedSynchronizer)实现,可以比join方法对线程有更灵活的控制。

示例代码:

public class TestHarness {
    public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
        final CountDownLatch startGate = new CountDownLatch(1);
        final CountDownLatch endGate = new CountDownLatch(nThreads);

        for (int i = 0; i < nThreads; i++) {
            Thread t = new Thread() {
                @Override
                public void run() {
                    try {
                        startGate.await();
                        try {
                            task.run();
                        } finally {
                            endGate.countDown();
                        }
                    } catch (InterruptedException e) {
                    }
                }
            };
        }
        long start = System.nanoTime();
        startGate.countDown();
        endGate.await();
        long end = System.nanoTime();
        return end - start;
    }
}

FutureTask也可以用作闭锁

FutureTask是一个可取消的异步计算,实现了Runnable和Future接口,通常用来包装一个Callable对象,可以异步执行,并将计算结果返回调用主线程。

示例代码:

public class FutureTaskTest {
    private static AtomicInteger atomicInteger = new AtomicInteger(1);
    private static CountDownLatch countDownLatch = new CountDownLatch(3);

    private static void testFutureTask() throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>(10);

        for (int i = 0; i < 10; i++) {
            FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    countDownLatch.await();
                    Random ran = new Random();
                    int res = 0;
                    for (int j = 0; j < 10; j++) {
                        res += Math.abs(ran.nextInt() / 100);
                    }
                    int sleepSec = atomicInteger.getAndIncrement();
                    System.out.println("Thread-" + Thread.currentThread().getId() + " will run for " + sleepSec + " seconds");
                    Thread.sleep(1000 * sleepSec);
                    System.out.println("Thread-" + Thread.currentThread().getId() + " returns " + res);
                    return res;
                }
            });
            executor.submit(futureTask);
            taskList.add(futureTask);
        }

        countDownLatch.countDown();//3
        countDownLatch.countDown();//2
        countDownLatch.countDown();//1

        int totRes = 0;
        for (FutureTask<Integer> task : taskList) {
            totRes += task.get().intValue();
        }
        System.out.println("Final result: " + totRes);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        testFutureTask();
    }
}

信号量(Semaphore)

计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。Semaphore可以用于实现资源池。

示例代码:

/**
 * Running 24/7
 */
public class PublicToilet {
    private static final int SPOTS = 5;
    private static final Random RANDOM = new Random();
    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
    private static final Semaphore SEMAPHORE = new Semaphore(SPOTS, false); // Not a fair WC
    private static final AtomicInteger USE_TIMES = new AtomicInteger(0);

    private void startService() {
        for (int i = 0; i < 20; i++) {
            WcGoer goer = new WcGoer();
            goer.setErgentLevel((short) (RANDOM.nextInt() % 2));
            Thread thread = new Thread(new WcGoer());
            thread.start();
        }
    }

    private class WcGoer implements Runnable {
        private short ergentLevel;

        public short getErgentLevel() {
            return ergentLevel;
        }

        public void setErgentLevel(short ergentLevel) {
            this.ergentLevel = ergentLevel;
        }

        @Override
        public void run() {
            try {
                int useTime;
                if (ergentLevel == 0) {
                    SEMAPHORE.acquire();
                } else {
                    while (!SEMAPHORE.tryAcquire()) {
                        System.out.println(FORMAT.format(new Date()) + ":" + Thread.currentThread().getName() + " keep waiting");
                        Thread.sleep(1000 * 3);
                    }
                }
                System.out.println(FORMAT.format(new Date()) + ":" + Thread.currentThread().getName() + " enters WC" +
                        ((ergentLevel == 0) ? "" : " in a hurry"));
                useTime = Math.abs(RANDOM.nextInt() % 11);
                System.out.println(FORMAT.format(new Date()) + ":" + Thread.currentThread().getName() + " will use for " + useTime + " seconds");
                Thread.sleep(1000 * useTime);
                System.out.println(FORMAT.format(new Date()) + ":" + Thread.currentThread().getName() + " exits WC");
                SEMAPHORE.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static void enterMaintenance() throws InterruptedException {
        //occupy all spots
        while (!SEMAPHORE.tryAcquire(SPOTS)) {
        }
        System.exit(0);
    }

    public static void main(String[] args) {
        PublicToilet pt = new PublicToilet();
        pt.startService();
    }

}

栅栏(Barrier)

栅栏(Barrier)类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

CyclicBarrier可以使一定数量的参与方反复地在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列互相独立的子问题。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有线程都被释放,而栅栏将被重置以便下次使用。

另一种形式的栅栏是Exchanger,它是一种两方(Two-Party)栅栏,各方在栅栏位置上交还数据。

示例代码:

public class BankRobbing {
    private static Random random = new Random();
    private static String CHICKEN_OUT = "";
    private CyclicBarrier TOUGH_TANK = new CyclicBarrier(5, new Runnable() {
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(3L);
                System.out.println("Take a deep breath, guys..");
                TimeUnit.SECONDS.sleep(3L);
                System.out.println("Ok, let's go make some money!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    });

    private class BankRobber extends Thread {
        private String name;

        public BankRobber(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            int time;
            System.out.println("Criminal " + name + " setting off for TOUGH_TANK");
            try {
                time = Math.abs(random.nextInt() % 20);
                TimeUnit.SECONDS.sleep(time);
                System.out.println("Criminal " + name + " reaches TOUGH_TANK");
                if (time < 5 && "".equals(CHICKEN_OUT)) {
                    CHICKEN_OUT = name;
                    TOUGH_TANK.await(5, TimeUnit.SECONDS);
                } else {
                    TOUGH_TANK.await();
                }
                System.out.println("Go,go,go!!!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                System.out.println(CHICKEN_OUT + " chickens out, " + name + " flees...");
                //e.printStackTrace();
            } catch (TimeoutException e) {
                System.out.println(name + " can't bear the waiting......");
                //e.printStackTrace();
            }
        }
    }

    public void doRobBank() {
        String[] robbers = {"Carl", "Coughlin", "Ben", "Mike", "Douglas"};
        for (int i = 0; i < robbers.length; i++) {
            new BankRobber(robbers[i]).start();
        }
    }

    public static void main(String[] args) {
        BankRobbing bankRobbing = new BankRobbing();
        bankRobbing.doRobBank();
    }
}

示例代码:

public class MethBooth {
    private Exchanger<Object> exchanger;

    public MethBooth(Exchanger<Object> exchanger) {
        this.exchanger = exchanger;
    }

    private class DrugDealer implements Runnable {
        private String infiniteDrug;
        private int money;

        public DrugDealer(String infiniteDrug, int money) {
            this.infiniteDrug = infiniteDrug;
            this.money = money;
        }

        @Override
        public void run() {
            Object object;
            System.out.println("DrugDealer: Walking to rendezvous");
            try {
                for (; ; ) {
                    TimeUnit.SECONDS.sleep(5);
                    object = exchanger.exchange(infiniteDrug);
                    if (object != null && "NARC".equals(object.toString())) {
                        System.out.println("DrugDealer:COPS! Running away......");
                        Thread.currentThread().interrupt();
                    } else {
                        Integer drugMoney = (Integer) object;
                        System.out.println("DrugDealer: Getting $" + drugMoney);
                        money += drugMoney;
                    }
                }
            } catch (InterruptedException e) {
                System.out.println("DrugDealer: I'm gone, collecting money $" + money + " in total.");
                e.printStackTrace();
            }
        }
    }

    private class DrugUser implements Runnable {
        private int money;

        public DrugUser(int money) {
            this.money = money;
        }

        @Override
        public void run() {
            System.out.println("DrugUser: Driving to rendezvous");
            Object object;
            try {
                while (money > 0) {
                    TimeUnit.SECONDS.sleep(5);
                    object = exchanger.exchange(5);
                    money -= 5;
                    System.out.println("DrugUser: Giving $5, getting " + object + ", left " + money);
                }
                object = exchanger.exchange("NARC");
                System.out.println("DrugUser: Giving NARC, getting " + object);
                object = exchanger.exchange(100, 3, TimeUnit.SECONDS);
                System.out.println("DrugUser: Giving $100, getting " + object);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
                System.out.println("DrugUser: DrugDealer is gone...");
            }
        }
    }

    public void drugDealing() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(new Thread(new DrugDealer("BLUE CHERRY", 0)));
        executorService.submit(new Thread(new DrugUser(10)));
        executorService.shutdown();
    }

    public static void main(String[] args) {
        MethBooth methBooth = new MethBooth(new Exchanger<Object>());
        methBooth.drugDealing();
    }
}

参考:《Java并发编程实战》;

Java并发包concurrent——ConcurrentHashMap

ArrayList和CopyOnWriteArrayList解读 java 并发队列 BlockingQueue

阻塞队列之六:LinkedBlockingDeque

Java 并发编程系列之闭锁(CountDownLatch)

Java并发编程笔记之 CountDownLatch闭锁的源码分析

Java进阶之FutureTask的用法及解析

Java多线程之Semaphore的使用(五)

Java并发33:Semaphore基本方法与应用场景实例

JUC回顾之-CyclicBarrier底层实现和原理

多线程编程的常用类(CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger)

发布了11 篇原创文章 · 获赞 1 · 访问量 1637

猜你喜欢

转载自blog.csdn.net/u011578173/article/details/93306631
今日推荐