Java 并发包


并发包

  • 在 JDK 的并发包里提供了几个非常有用的并发容器和并发工具类,供我们在多线程开发中进行使用;

1. CopyOnWriteArrayList

a. ArrayList

  • ArrayList 的线程不安全,最终结果可能会抛异常,或者最终集合大小是不正确的:
import java.util.ArrayList;
import java.util.List;

class MyThread extends Thread {
    public static List<Integer> list = new ArrayList<>();//线程不安全的

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        System.out.println("添加完毕!");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("最终集合的长度:" + MyThread.list.size());
    }
}
/*
输出
添加完毕!
最终集合的长度:10004
 */

b. CopyOnWriteArrayList

  • CopyOnWriteArrayList 是线程安全的,结果始终是正确的;
import java.util.concurrent.CopyOnWriteArrayList;

class MyThread extends Thread {
    //public static List<Integer> list = new ArrayList<>();//线程不安全的
    //改用:线程安全的List集合:
    public static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        System.out.println("添加完毕!");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("最终集合的长度:" + MyThread.list.size());
    }
}
/*
输出
添加完毕!
添加完毕!
最终集合的长度:20000
 */

2. CopyOnWriteArraySet

a. HashSet

  • HashSet 仍然是线程不安全的, 最终结果可能会抛异常,也可能最终的长度是错误的:
import java.util.HashSet;
import java.util.Set;

class MyThread extends Thread {
    public static Set<Integer> set = new HashSet<>();//线程不安全的

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            set.add(i);
        }
        System.out.println("添加完毕!");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        t1.start();
        //主线程也添加10000个
        for (int i = 10000; i < 20000; i++) {
            MyThread.set.add(i);

        }
        Thread.sleep(1000 * 3);
        System.out.println("最终集合的长度:" + MyThread.set.size());
    }
}
/*
输出
添加完毕!
最终集合的长度:19564
 */

b. CopyOnWriteArraySet

  • CopyOnWriteArraySet是线程安全的,可以看到结果总是正确的:
import java.util.concurrent.CopyOnWriteArraySet;

class MyThread extends Thread {
    //public static Set<Integer> set = new HashSet<>();//线程不安全的
    //改用:线程安全的Set集合:
    public static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            set.add(i);
        }
        System.out.println("添加完毕!");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        t1.start();
        //主线程也添加10000个
        for (int i = 10000; i < 20000; i++) {
            MyThread.set.add(i);

        }
        Thread.sleep(1000 * 3);
        System.out.println("最终集合的长度:" + MyThread.set.size());
    }
}
/*
输出
添加完毕!
最终集合的长度:20000
 */

3. ConcurrentHashMap

a. HashMap

  • HashMap 是线程不安全的,运行结果可能会出现异常、或者结果不准确:
import java.util.HashMap;
import java.util.Map;

class MyThread extends Thread {
    public static Map<Integer, Integer> map = new HashMap<>();

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            map.put(i, i);
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        t1.start();

        for (int i = 10000; i < 20000; i++) {
            MyThread.map.put(i, i);

        }
        Thread.sleep(1000 * 2);

        System.out.println("map最终大小:" + MyThread.map.size());
    }
}
/*
输出
map最终大小:19131
 */

b. Hashtable

  • Hashtable 是 JDK 1.1 提供的一个早期的线程安全的类,它把 remove() put() get() 做成了同步方法,保证了自身线程安全性,所以虽然是线程安全的,但效率低;
public synchronized V put(K key, V value) 
public synchronized V get(Object key)
  • 下例中,我们加入了"计时",能看到结果是正确的,但耗时较长:
import java.util.Hashtable;
import java.util.Map;

class MyThread extends Thread {
    public static Map<Integer, Integer> map = new Hashtable<>();

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            map.put(i, i);
        }
        long end = System.currentTimeMillis();
        System.out.println((end - start) + " 毫秒");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();//开启1000个线程
        }

        Thread.sleep(1000 * 20);//由于每个线程执行时间稍长,所以这里多停顿一会

        System.out.println("map的最终大小:" + MyThread.map.size());

    }
}
/*
部分输出
...
5046 毫秒
5046 毫秒
map的最终大小:100000
 */

c. ConcurrentHashMap

  • 改用 ConcurrentHashMap,可以看到效率提高了很多:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

class MyThread extends Thread {
    public static Map<Integer, Integer> map = new ConcurrentHashMap<>();

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            map.put(i, i);
        }
        long end = System.currentTimeMillis();
        System.out.println((end - start) + " 毫秒");
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();
        }

        Thread.sleep(1000 * 20);

        System.out.println("map的最终大小:" + MyThread.map.size());
    }
}
/*
部分输出
...
10068 毫秒
10092 毫秒
map的最终大小:100000
 */

i. HashTable 与 ConcurrentHashMap

  • Hashtable 容器使用 synchronized 来保证线程安全,但在线程竞争激烈的情况下 Hashtable 的效率非常低下。因为当一个线程访问 Hashtable 的同步方法,其他线程也访问 Hashtable 的同步方法时,会进入阻塞状态。如线程1使用 put 进行元素添加,线程2不但不能使用 put 方法添加元素,也不能使用 get 方法来获取元素,所以竞争越激烈效率越低:

Hashtable

  • ConcurrentHashMap 高效的原因:CAS + 局部(synchronized)锁定:
    ConcurrentHashMap

4. CountDownLatch

  • CountDownLatch 允许一个或多个线程等待其他线程完成操作;
  • 例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行;

a. 构造方法

  • public CountDownLatch(int count) 初始化一个指定计数器的 CountDownLatch 对象;

b. 重要方法

public void await() throws InterruptedException// 让当前线程等待
public void countDown()	// 计数器进行减1
  • CountDownLatch 中 count down 是倒数的意思,latch 则是门闩的含义,整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉;
  • CountDownLatch 是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用 countDown() 方法让计数器-1,当计数器到达0时,调用 CountDownLatch;
  • await() 方法的线程阻塞状态解除,继续执行;

c. 应用

i. 应用场景

  • CountDownLatch 和 wait() 相比:一般 CountDownLatch 用于一个线程等待多个线程结束时或指定多个线程按顺序进行;

ii. 依次打印案例

import java.util.concurrent.CountDownLatch;

class ThreadA extends Thread {
    private CountDownLatch down;

    public ThreadA(CountDownLatch down) {
        this.down = down;
    }

    @Override
    public void run() {
        System.out.println("A");
        try {
            down.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}

class ThreadB extends Thread {
    private CountDownLatch down;

    public ThreadB(CountDownLatch down) {
        this.down = down;
    }

    @Override
    public void run() {
        System.out.println("B");
        down.countDown();
    }
}

public class Test {
    public static void main(String[] args) {
        CountDownLatch down = new CountDownLatch(1);//创建1个计数器
        new ThreadA(down).start();
        new ThreadB(down).start();
    }
}
/*
输出
A
B
C
 */

5. CyclicBarrier

  • CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行;
  • 例如:公司召集5名员工开会,等5名员工都到了,会议开始。我们创建5个员工线程,1个开会线程,几乎同时启动,使用 CyclicBarrier 保证5名员工线程全部执行后,再执行开会线程;

a. 构造方法

  • public CyclicBarrier(int parties, Runnable barrierAction) 用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景;

b. 重要方法

public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞

c. 应用

i. 应用场景

  • CyclicBarrier 和 wait() 相比:一般 CyclicBarrier 用于一个线程等待多个线程结束时;
  • CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。例如,使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作;

ii. 员工开会案例

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

class PersonThread extends Thread {
    private CyclicBarrier cbRef;

    public PersonThread(CyclicBarrier cbRef) {
        this.cbRef = cbRef;
    }

    @Override
    public void run() {
        try {
            Thread.sleep((int) (Math.random() * 1000));
            System.out.println(Thread.currentThread().getName() + " 到了! ");
            cbRef.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

class MeetingThread extends Thread {
    @Override
    public void run() {
        System.out.println("好了,人都到了,开始开会......");
    }
}

public class Test {
    public static void main(String[] args) {
        CyclicBarrier cbRef = new CyclicBarrier(5, new MeetingThread());//等待5个线程执行完毕,再执行MeetingThread
        PersonThread p1 = new PersonThread(cbRef);
        PersonThread p2 = new PersonThread(cbRef);
        PersonThread p3 = new PersonThread(cbRef);
        PersonThread p4 = new PersonThread(cbRef);
        PersonThread p5 = new PersonThread(cbRef);
        p1.start();
        p2.start();
        p3.start();
        p4.start();
        p5.start();
    }
}
/*
输出
Thread-4 到了! 
Thread-5 到了! 
Thread-1 到了! 
Thread-2 到了! 
Thread-3 到了! 
好了,人都到了,开始开会......
 */

6. Semaphore

  • Semaphore(旗语)的主要作用是控制线程的并发数量;
  • Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目;

a. 构造方法

public Semaphore(int permits)//permits 表示许可线程的数量
public Semaphore(int permits, boolean fair)//fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程

b. 重要方法

public void acquire() throws InterruptedException//表示获取许可
public void release()//release() 表示释放许可

c. 应用

i. 应用场景

  • synchronized 可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行,而 Semaphore 可以设置同时允许几个线程执行;

ii. 同时允许1个线程执行

import java.util.concurrent.Semaphore;

class Service {
    private Semaphore semaphore = new Semaphore(1);//1表示许可的意思,表示最多允许1个线程执行acquire()和release()之间的内容

    public void testMethod() {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()
                    + " 进入 时间=" + System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()
                    + "   结束 时间=" + System.currentTimeMillis());
            semaphore.release();
            //acquire()和release()方法之间的代码为"同步代码"
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        //启动5个线程
        for (int i = 1; i <= 5; i++) {
            ThreadA a = new ThreadA(service);
            a.setName("线程 " + i);
            a.start();//5个线程会同时执行Service的testMethod方法,而某个时间段只能有1个线程执行
        }
    }
}
/*
输出
线程 1 进入 时间=1583589945404
线程 1   结束 时间=1583589946412
线程 3 进入 时间=1583589946413
线程 3   结束 时间=1583589947413
线程 5 进入 时间=1583589947413
线程 5   结束 时间=1583589948414
线程 2 进入 时间=1583589948414
线程 2   结束 时间=1583589949414
线程 4 进入 时间=1583589949414
线程 4   结束 时间=1583589950414
 */

iii. 同时允许2个线程同时执行

  • 只修改Service类,将 new Semaphore(1) 改为2即可:
import java.util.concurrent.Semaphore;

class Service {
    private Semaphore semaphore = new Semaphore(2);//2表示许可的意思,表示最多允许2个线程执行acquire()和release()之间的内容

    public void testMethod() {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()
                    + " 进入 时间=" + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName()
                    + "   结束 时间=" + System.currentTimeMillis());
            semaphore.release();
            //acquire()和release()方法之间的代码为"同步代码"
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

public class Test {
    public static void main(String[] args) {
        Service service = new Service();
        //启动5个线程
        for (int i = 1; i <= 5; i++) {
            ThreadA a = new ThreadA(service);
            a.setName("线程 " + i);
            a.start();//5个线程会同时执行Service的testMethod方法,而某个时间段只能有1个线程执行
        }
    }
}
/*
输出
线程 1 进入 时间=1583590081732
线程 2 进入 时间=1583590081732
线程 1   结束 时间=1583590086747
线程 2   结束 时间=1583590086747
线程 3 进入 时间=1583590086747
线程 5 进入 时间=1583590086747
线程 3   结束 时间=1583590091747
线程 5   结束 时间=1583590091747
线程 4 进入 时间=1583590091748
线程 4   结束 时间=1583590096749
 */

7. Exchanger

  • Exchanger(交换者)是一个用于线程间协作的工具类,用于进行线程间的数据交换;
  • 这两个线程通过 exchange() 方法交换数据,如果第一个线程先执行exchange() 方法,它会一直等待第二个线程也执行 exchange() 方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方;

a. 构造方法

public Exchanger()

b. 重要方法

public V exchange(V x)

c. 应用

i. 使用场景

  • 可以做数据校对工作;
  • 比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,并对两个文件数据进行校对,看看是否录入一致;

ii. exchange() 方法的阻塞特性

import java.util.concurrent.Exchanger;

class ThreadA extends Thread {
    private Exchanger<String> exchanger;

    public ThreadA(Exchanger<String> exchanger) {
        super();
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        try {
            System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值...");
            System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("礼物A"));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<String>();
        ThreadA a = new ThreadA(exchanger);
        a.start();
    }
}
/*
输出
线程A欲传递值'礼物A'给线程B,并等待线程B的值...
 */

iii. exchange() 方法执行交换

import java.util.concurrent.Exchanger;

class ThreadA extends Thread {
    private Exchanger<String> exchanger;

    public ThreadA(Exchanger<String> exchanger) {
        super();
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        try {
            System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值...");
            System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("礼物A"));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadB extends Thread {
    private Exchanger<String> exchanger;

    public ThreadB(Exchanger<String> exchanger) {
        super();
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        try {
            System.out.println("线程B欲传递值'礼物B'给线程A,并等待线程A的值...");
            System.out.println("在线程B中得到线程A的值=" + exchanger.exchange("礼物B"));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Exchanger<String> exchanger = new Exchanger<String>();
        ThreadA a = new ThreadA(exchanger);
        ThreadB b = new ThreadB(exchanger);
        a.start();
        b.start();
    }
}
/*
输出
线程A欲传递值'礼物A'给线程B,并等待线程B的值...
线程B欲传递值'礼物B'给线程A,并等待线程A的值...
在线程A中得到线程B的值=礼物B
在线程B中得到线程A的值=礼物A
 */

iv. exchange() 方法的超时

import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

class ThreadA extends Thread {
    private Exchanger<String> exchanger;
    public ThreadA(Exchanger<String> exchanger) {
        super();
        this.exchanger = exchanger;
    }
    @Override
    public void run() {
        try {
            System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值,只等5秒...");
            System.out.println("在线程A中得到线程B的值 =" + exchanger.exchange("礼物A",5, TimeUnit.SECONDS));
            System.out.println("线程A结束!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            System.out.println("5秒钟没等到线程B的值,线程A结束!");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<String>();
        ThreadA a = new ThreadA(exchanger);
        a.start();
    }
}
/*
输出
线程A欲传递值'礼物A'给线程B,并等待线程B的值,只等5秒...
5秒钟没等到线程B的值,线程A结束!
 */
发布了185 篇原创文章 · 获赞 181 · 访问量 5347

猜你喜欢

转载自blog.csdn.net/Regino/article/details/104722287