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 是一个线程执行到一半,等其他线程完了再继续这个线程的代码;
ii. 依次打印案例
  • 相比于 CyclicBarrier,此时控制权在 TheadA;
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();//相当于wait()
        } 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();//相当于notify
    }
}

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 是等 nn个线程到了才执行下面的线程;
  • 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);//相当于设定了间隔,在1s的时间间隔内,最多一个并发线程
            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即可,结合 sleep(5000) 方法,表示在5s内只允许两个并发线程:
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结束!
 */
v. exchange() 方法实现多个对象间的交换
import java.util.HashMap;
import java.util.concurrent.Exchanger;

public class Test {
    public static void main(String[] args) {
        //交换:2个线程之间进行数据交换,可以用两个exchanger
        Exchanger<String> exchanger1 = new Exchanger<>();//用于男票和女票之间的交换
        Exchanger<String> exchanger2 = new Exchanger<>();//用于男票和基友之间的交换

        //交换:在3个或3个以上的线程之间交换数据,可以用HashMap存入线程对象-exchanger
        HashMap<String, Exchanger<String>> map = new HashMap<>();
        map.put("男票-基友", exchanger2);
        map.put("男票-女票", exchanger1);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String name = Thread.currentThread().getName();
                    String receiver = "女票";
                    String key = name + "-" + receiver;
                    Exchanger<String> exchanger = map.get(key);
                    String ret = exchanger.exchange("一杯热水");
                    System.out.println(Thread.currentThread().getName() + ":" + ret);//ret换成了交换对象的ret

                    String ret2 = exchanger2.exchange("一个娃娃");
                    System.out.println(Thread.currentThread().getName() + ":" + ret2);//ret2换成了交换对象的ret

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "男票").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String ret = exchanger1.exchange("一杯熔浆");
                    System.out.println(Thread.currentThread().getName() + ":" + ret);//ret换成了交换对象的ret
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "女票").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String ret = exchanger2.exchange("一杯恒河水");
                    System.out.println(Thread.currentThread().getName() + ":" + ret);//ret换成了交换对象的ret2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "基友").start();
    }
}
/*
输出
男票:一杯熔浆
女票:一杯热水
男票:一杯恒河水
基友:一个娃娃
 */
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值