day12【线程安全解决synchronized 并发包ConcurrentHashMap,CountDownLatch,CyclicBarrier,Semaphore,Exchanger】

第一章 synchronized关键字

1.1 多行代码的原子性问题

讲解

  • 之前的AtomicInteger类只能保证"变量"的原子性操作,而对多行代码进行"原子性"操作,使用AtomicInteger类就不能达到效果了。

  • 我们通过一个案例,演示线程的安全问题:

    电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。

    我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟。

模拟票:

public class MyRunnable implements Runnable {
    int tickets = 100;// 4个窗口共同卖的票 共享变量

    @Override
    public void run() {
        // 实现卖票的操作
        // 死循环卖票
        while (true){
            // 当票卖完了,就结束
            if (tickets < 1){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":正在出售第"+tickets+"张票");
            tickets--;
        }
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        /*
            多行代码的原子性问题:
                通过案例演示该问题: 电影院4个窗口卖票,卖的是同一份票,这份票总共有100张票
                分析:
                    1. 电影院4个窗口 相当于 4条线程
                    2. 电影院4个窗口 卖票的操作是一样 相当于每条线程的任务是一样的
         */
        // 电影院4个窗口  去卖票
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr,"窗口1");
        Thread t2 = new Thread(mr,"窗口2");
        Thread t3 = new Thread(mr,"窗口3");
        Thread t4 = new Thread(mr,"窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

程序执行后,结果会出现的问题
在这里插入图片描述
发现程序出现了两个问题:

  1. 相同的票数,比如100这张票被卖了四回。
  2. 不存在的票,比如0票与-1票,-2票,是不存在的。

这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

卖票案例问题分析:
在这里插入图片描述

1.2 synchronized关键字概述

  • synchronized关键字:表示“同步”的。它可以对“多行代码”进行“同步”——将多行代码当成是一个完整的整体,一个线程如果进入到这个代码块中,会全部执行完毕,执行结束后,其它线程才会执行。这样可以保证这多行的代码作为完整的整体,被一个线程完整的执行完毕。

  • synchronized被称为“重量级的锁”方式,也是“悲观锁”——效率比较低。

  • synchronized有几种使用方式:
    a).同步代码块

    b).同步方法【常用】

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

根据案例简述:

窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

1.3 同步代码块

  • 同步代码块synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){
     需要同步操作的代码
}

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。
  2. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

使用同步代码块解决代码:

public class MyRunnable implements Runnable {
    int tickets = 100;// 4个窗口共同卖的票 共享变量

    @Override
    public void run() {
        // 实现卖票的操作
        // 死循环卖票
        while (true) {
            // 当票卖完了,就结束
            // 加锁
            synchronized (this) {// mr钥匙
                if (tickets < 1) {
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
                tickets--;
            }

            // 释放锁
        }
    }
}

public class Test {
    //static Object lock  = new Object();
    public static void main(String[] args) {
        /*
            解决多行代码的原子性问题:
                同步代码块:
                    概述:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
                    格式:
                        synchronized(锁对象){

                        }
                    锁对象:
                        1.锁对象可以是任意类的对象
                        2.多条线程想要实现同步,那么锁对象必须一致
         */
        // 电影院4个窗口  去卖票
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr,"窗口1");
        Thread t2 = new Thread(mr,"窗口2");
        Thread t3 = new Thread(mr,"窗口3");
        Thread t4 = new Thread(mr,"窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        /*
            发现程序出现了两个问题:
                1. 相同的票数,比如100这张票被卖了四回。
                2. 不存在的票,比如0票与-1票,-2票,是不存在的。
         */
    }
}

当使用了同步代码块后,上述的线程的安全问题,解决了。

1.4 同步方法

讲解

  • 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式:

public synchronized void method(){
   	可能会产生线程安全问题的代码
}

同步锁是谁?

​ 对于非static方法,同步锁就是this。

​ 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

使用同步方法代码如下:

public class MyRunnable implements Runnable {
    int tickets = 100;// 4个窗口共同卖的票 共享变量

    @Override
    public  void run() {
        // 实现卖票的操作
        // 死循环卖票
        while (true) {
            // 当票卖完了,就结束
            // 加锁
            if (sellTickets()) break;

            // 释放锁
        }
    }

    private  synchronized boolean sellTickets() {
        if (tickets < 1) {
            return true;
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
        tickets--;
        return false;
    }
}
public class Test {
    //static Object lock  = new Object();
    public static void main(String[] args) {
        /*
            解决多行代码的原子性问题:
                同步方法:
                    概述:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
                    格式:
                        修饰符 synchronized 返回值类型 方法名(参数列表){}
                    锁对象:
                        1.非静态同步方法: 锁对象是this
                        2.静态同步方法: 锁对象是该方法所在类的字节码文件对象  类名.class

                    线程A中使用的是同步代码块,线程B中使用的是同步方法,线程A和线程B要实现同步,
                    那么线程A中的同步代码块锁对象和线程B中的同步方法的锁对象要一致,否则锁不住
         */
        // 电影院4个窗口  去卖票
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr,"窗口1");
        Thread t2 = new Thread(mr,"窗口2");
        Thread t3 = new Thread(mr,"窗口3");
        Thread t4 = new Thread(mr,"窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        /*
            发现程序出现了两个问题:
                1. 相同的票数,比如100这张票被卖了四回。
                2. 不存在的票,比如0票与-1票,-2票,是不存在的。
         */
    }
}

1.5 Lock锁

讲解

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock():加同步锁。
  • public void unlock():释放同步锁。

使用如下:

public class MyRunnable implements Runnable {
    int tickets = 100;// 4个窗口共同卖的票 共享变量
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 实现卖票的操作
        // 死循环卖票
        while (true) {
            // 当票卖完了,就结束
            // 加锁
            lock.lock();
            if (tickets < 1) {// 窗口1 0
                lock.unlock();
                break;// 结束卖票
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
            tickets--;
            // 释放锁
            lock.unlock();
        }
    }
}

第二章 并发包

2.1 CopyOnWriteArrayList

ArrayList的线程不安全:

  1. 定义线程类:
public 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("添加完毕!");
    }
}

  1. 定义测试类:
public class Demo {
    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());
    }
}

最终结果可能会抛异常,或者最终集合大小是不正确的。

CopyOnWriteArrayList是线程安全的:

  1. 定义线程类:
public 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("添加完毕!");
    }
}

  1. 测试类:
public class Demo {
    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());
    }
}

结果始终是正确的。

2.2 CopyOnWriteArraySet

HashSet仍然是线程不安全的:

  1. 线程类:
public 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("添加完毕!");
    }
}

  1. 测试类:
public class Demo {
    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());
    }
}

最终结果可能会抛异常,也可能最终的长度是错误的!!

CopyOnWriteArraySet是线程安全的:

  1. 线程类:
public 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("添加完毕!");
    }
}

  1. 测试类:
public class Demo {
    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());
    }
}

可以看到结果总是正确的!!

2.3 ConcurrentHashMap

HashMap是线程不安全的。

  1. 线程类:
public class MyRunnable implements Runnable {
    // HashMap线程不安全
    HashMap<Integer,Integer> map = new HashMap<>();// 2条线程共享的map集合
     // Hashtable线程安全
    //Hashtable<Integer,Integer> map = new  Hashtable<>();// 2条线程共享的map集合
    // ConcurrentHashMap线程安全
    //ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();// 2条线程共享的map集合
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            map.put(i,i);
        }
        System.out.println("添加完毕");
    }
}
  1. 测试类:
public class Test {
    public static void main(String[] args) throws InterruptedException {
        /*
            ConcurrentHashMap:
                - 演示HashMap线程不安全
                - 演示Hashtable线程安全
                - 演示ConcurrentHashMap线程安全

            案例: 线程1对HashMap集合添加1000个键值对,线程2也对HashMap集合添加1000个键值对
         */
        // 创建MyRunnable任务对象
        MyRunnable mr = new MyRunnable();
        // 创建2条线程执行任务
        Thread t1 = new Thread(mr);
        t1.start();

        Thread t2 = new Thread(mr);
        t2.start();

        Thread.sleep(2000);
        System.out.println("最终map集合键值对的个数:"+mr.map.size());
        /*
            根据分析,共享的map集合中的键值对个数应该是1000个,但实际运行的结果是大于1000的
         */
    }
}

运行结果可能会出现异常、或者结果不准确!!

Hashtable是线程安全的,但效率低:

我们改用JDK提供的一个早期的线程安全的Hashtable类来改写此例,注意:我们加入了"计时"。

  1. 线程类:
public class MyRunnable implements Runnable {

    // Hashtable线程安全
    Hashtable<Integer,Integer> map = new  Hashtable<>();// 2条线程共享的map集合


    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            map.put(i,i);
        }
        System.out.println("添加完毕");
    }
}
  1. 测试类:
public class Test {
    public static void main(String[] args) throws InterruptedException {
        // 创建MyRunnable任务对象
        MyRunnable mr = new MyRunnable();
        // 创建1000条线程
        for (int i = 0; i < 1000; i++) {
            Thread t = new Thread(mr);
            t.start();
        }


        Thread.sleep(5000);
        System.out.println("最终map集合键值对的个数:"+mr.map.size());
        /*
            根据分析,共享的map集合中的键值对个数应该是1000个,但实际运行的结果是大于1000的
         */
    }
}

能看到结果是正确的,但耗时较长。

改用ConcurrentHashMap

  1. 线程类:
public class MyRunnable implements Runnable {
   
    // ConcurrentHashMap线程安全
    ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();// 2条线程共享的map集合

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            map.put(i,i);
        }
        System.out.println("添加完毕");
    }
}

  1. 测试类:
public class Test {
    public static void main(String[] args) throws InterruptedException {
        // 创建MyRunnable任务对象
        MyRunnable mr = new MyRunnable();
        // 创建1000条线程
        for (int i = 0; i < 1000; i++) {
            Thread t = new Thread(mr);
            t.start();
        }


        Thread.sleep(5000);
        System.out.println("最终map集合键值对的个数:"+mr.map.size());
        /*
            根据分析,共享的map集合中的键值对个数应该是1000个,但实际运行的结果是大于1000的
         */
    }
}


可以看到效率提高了很多!!!

  • HashTable效率低下原因:
public synchronized V put(K key, V value) 
public synchronized V get(Object key)

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

2.4 CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。

例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行。

CountDownLatch构造方法:

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

CountDownLatch重要方法:

public void await() throws InterruptedException// 让当前线程等待
public void countDown()	// 计数器进行减1
  • 示例
    1). 制作线程1:
public class MyRunnable1 implements Runnable {
    CountDownLatch cdl;

    public MyRunnable1(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        System.out.println("A");
        // 暂停,等待线程2执行打印B,执行完回到这里来执行打印C
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");// 打印C之前一定要去执行打印B
    }
}

2). 制作线程2:

public class MyRunnable2 implements Runnable {

    CountDownLatch cdl;

    public MyRunnable2(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        System.out.println("B");
        // 计数器-1
        cdl.countDown();// 计数器的值为0
    }
}

3).制作测试类:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        /*
            CountDownLatch:
                作用: 允许一个或多个线程等待其他线程完成操作。
                常用方法:
                    public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象
                    public void await() throws InterruptedException// 让当前线程等待
                    public void countDown()	// 计数器进行减1

                案例演示:
                 线程1的任务是打印A和C,线程2的任务是打印B,要求打印B一定要在打印C的前面
         */
        CountDownLatch cdl = new CountDownLatch(1);

        // 创建2条线程,执行任务
        MyRunnable1 mr1 = new MyRunnable1(cdl);
        Thread t1 = new Thread(mr1);
        t1.start();


        MyRunnable2 mr2 = new MyRunnable2(cdl);
        Thread t2 = new Thread(mr2);
        t2.start();//
    }
}

4). 执行结果:
会保证按:A B C的顺序打印。

说明:

CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉。

CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch。

await()方法的线程阻塞状态解除,继续执行。

2.5 CyclicBarrier

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

例如:公司召集5名员工开会,等5名员工都到了,会议开始。

我们创建5个员工线程,1个开会线程,几乎同时启动,使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。

CyclicBarrier构造方法:

public CyclicBarrier(int parties, Runnable barrierAction
    //parties: 代表要达到屏障的线程数量
    //barrierAction:表示达到屏障后要执行的线程

CyclicBarrier重要方法:

public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
  • 示例代码:
    1). 制作员工线程:
public class MyRunnable implements Runnable {

    CyclicBarrier cb;

    public MyRunnable(CyclicBarrier cb) {
        this.cb = cb;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":到会议室了...");
        // 当前线程到达会议室(屏障),暂停
        try {
            cb.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":离开会议室");
    }
}

2). 制作开会线程:

public class MeetingRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("会议开始,会议的内容是...");
    }
}

3). 制作测试类:

public class Test {
    public static void main(String[] args) {
        /*
            CyclicBarrier类:
                作用:它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,
                     所有被屏障拦截的线程才会继续运行。
                常用方法:
                    public CyclicBarrier(int parties, Runnable barrierAction)
                     parties: 代表要达到屏障的线程数量
                     barrierAction:表示达到屏障后要执行的线程

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

                案例演示:
                例如:公司召集5名员工开会,等5名员工都到了,会议开始
         */
        // 创建CyclicBarrier对象
        CyclicBarrier cb = new CyclicBarrier(5,new MeetingRunnable());

        MyRunnable mr = new MyRunnable(cb);
        new Thread(mr,"员工1").start();
        new Thread(mr,"员工2").start();
        new Thread(mr,"员工3").start();
        new Thread(mr,"员工4").start();
        new Thread(mr,"员工5").start();


    }
}

4). 执行结果:在这里插入图片描述
使用场景

使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。

需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。

2.6 Semaphore

Semaphore的主要作用是控制线程的并发数量。

synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。

Semaphore可以设置同时允许几个线程执行。

Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。

Semaphore构造方法:

public Semaphore(int permits)						permits 表示许可线程的数量

Semaphore重要方法:

public void acquire() throws InterruptedException	表示获取许可
public void release()								release() 表示释放许可
  • 示例一:同时允许1个线程执行

1). 制作一个ClassRoom类:

public class ClassRoom {
    // 创建Semaphore对象,指定线程的并发数量是1
    Semaphore sp = new Semaphore(2);

    public void into(){
        // 获得许可证,才进来了
        try {
            sp.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":进来了...");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 出去了,就释放许可
        System.out.println(Thread.currentThread().getName()+":出去了...");
        sp.release();
    }
}


2). 制作线程类:

public class MyRunnable implements Runnable {
    ClassRoom cr;

    public MyRunnable(ClassRoom cr) {
        this.cr = cr;
    }

    @Override
    public void run() {
        cr.into();// 任务就是进入教室
    }
}


3). 测试类:

public class Test {
    public static void main(String[] args) {
        /*
            Semaphore类:
                作用: 控制线程的并发数量。
                常用方法:
                    public Semaphore(int permits)  permits 表示许可线程的数量
                    public void acquire() throws InterruptedException	表示获取许可
                    public void release()  release() 表示释放许可
                案例演示:
                    假设有个教室,只能容纳3个人,这个时候有5个人需要进来
         */
        ClassRoom cr = new ClassRoom();
        // 创建5条线程,执行任务
        MyRunnable mr = new MyRunnable(cr);
        new Thread(mr,"1号").start();
        new Thread(mr,"2号").start();
        new Thread(mr,"3号").start();
        new Thread(mr,"4号").start();
        new Thread(mr,"5号").start();

    }
}


2.7 Exchanger

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。

这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

A线程 exchange方法 把数据传递B线程

B线程 exchange方法 把数据传递A线程

Exchanger构造方法:

public Exchanger()

Exchanger重要方法:

public V exchange(V x)
  • 示例一
public class MyRunnable1 implements Runnable {
    Exchanger<String> ex;

    public MyRunnable1(Exchanger<String> ex) {
        this.ex = ex;
    }

    @Override
    public void run() {
        // 线程1 传递数据给 线程2
        try {
            // 线程1,把"信息1"传递给线程2
            String message2 = ex.exchange("信息1");
            System.out.println("线程2 传递给 线程1的信息是:"+message2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyRunnable2 implements Runnable {
    Exchanger<String> ex;

    public MyRunnable2(Exchanger<String> ex) {
        this.ex = ex;
    }

    @Override
    public void run() {
        // 线程2 传递数据给 线程1
        try {
            String message1 = ex.exchange("信息2");
            System.out.println("线程1 传递给 线程2的信息:"+message1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2). 制作main()方法:

public class Test {
    public static void main(String[] args) {
        /*
            Exchanger类:
                作用:是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
                常用方法:
                    public Exchanger()
                    public V exchange(V x)  参数就表示当前线程需要传递的数据,返回值是其他线程传递过来的数据
                案例演示:
         */
        Exchanger<String> ex = new Exchanger<>();
        MyRunnable1 mr1 = new MyRunnable1(ex);
        new Thread(mr1).start();


        MyRunnable2 mr2 = new MyRunnable2(ex);
        new Thread(mr2).start();

    }
}

使用场景:可以做数据校对工作

需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,

并对两个文件数据进行校对,看看是否录入一致,

总结

  • 能够使用同步代码块解决线程安全问题
    使用场景: 如果线程中某一段代码需要实现互斥的效果,就可以使用同步代码块对这段代码进行加锁
    格式:
    synchronized (锁对象){
    // 实现互斥的代码\需要加锁的代码
    }
    锁对象:
    1.锁对象可以是任意对象
    2.如果多条线程要实现同步,锁对象必须是唯一的(多条线程的锁对象要一致)
  • 能够使用同步方法解决线程安全问题
    使用场景: 如果线程中某个方法中的所有代码需要实现互斥的效果,就可以使用同步方法对这个方法中的所有代码进行加锁
    格式:
    方法的返回值类型前面加上 synchronized
    锁对象:
    1.非静态同步方法: 锁对象this
    2.静态同步方法: 锁对象是该方法所在的类的字节码对象 类名.class
  • 能够说明volatile关键字和synchronized关键字的区别
    volatile关键字: 保证可见性 多个线程可以一起操作,但是对共享变量操作后,其他线程是可见的
    synchronized关键字: 加锁 一个线程在操作其他线程得等着
  • 能够描述ConcurrentHashMap类的作用
    线程安全,效率相对高
  • 能够描述CountDownLatch类的作用
    允许一个或多个线程等待其他线程完成操作。
  • 能够描述CyclicBarrier类的作用
    它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
  • 能够表述Semaphore类的作用
    控制线程的并发数量。
  • 能够描述Exchanger类的作用
    线程间的数据交换
    必须练习—>同步代码块,同步方法,Lock锁
    理解做好笔记—> 并发包中的类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值