解决多线程高并发问题常用类

时间:2021-07-25作者:klpeng分类:Java技术浏览:248评论:0

Volatile关键字

1.volatile解决可见性

//创建一个类继承Thread类
public class MyThread extends Thread{
    //定义一个共享的静态成员变量,供多个线程一起使用
    //给共享的成员变量,添加一个volatile关键字修饰
    public static volatile int a = 0;

    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run() {
        System.out.println("Thread-0线程开始执行了,先睡眠2秒钟");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread-0线程睡醒了,改变a的值为1");
        //a变量被volatile修饰,当改变了变量a的值,volatile关键字会让所有a的变量副本失效,线程想要使用a变的值,需要在静态区中重新获取
        a=1;
        System.out.println("Thread-0线程的线程任务执行完毕!");
    }
}

public class Demo01Visible {
    public static void main(String[] args) {
        //创建Thread类的子类对象
        MyThread mt = new MyThread();
        //调用继续自Thread类中的start方法,开启新的线程执行run方法
        mt.start();

        System.out.println("main线程,会继续执行main方法中的代码");
        while (true){
            if(MyThread.a==1){
                System.out.println("main线程,判断变量a的值为1,结束死循环!");
                break;
            }
        }
    }
}

解决多线程高并发问题常用类
2.volatile解决有序性
解决多线程高并发问题常用类
3.volatile不能解决原子性

原子性的解决

概述: 所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,

多个操作是一个不可以分割的整体(原子)。

比如:从张三的账户给李四的账户转1000元,这个动作将包含两个基本的操作:从张三的账户扣除1000元,给李四的账户增加1000元。这两个操作必须符合原子性的要求,

要么都成功要么都失败

原子类

原子类概述: java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。

1). java.util.concurrent.atomic.AtomicInteger:对int变量进行原子操作的类。

2). java.util.concurrent.atomic.AtomicLong:对long变量进行原子操作的类。

3). java.util.concurrent.atomic.AtomicBoolean:对boolean变量进行原子操作的类。

这些类可以保证对“某种类型的变量”原子操作,多线程、高并发的环境下,就可以保证对变量访问的有序性,从而保证最终的结果是正确的。

AtomicInteger原子型Integer,可以实现原子更新操作

构造方法:
public AtomicInteger():	   				初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
成员方法:
int get():   			 				 获取值
int getAndIncrement():      			 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet():     				 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data):				 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value):   			 以原子方式设置为newValue的值,并返回旧值

2.AtomicInteger类的基本使用

/*
     java.util.concurrent.atomic.AtomicInteger:对int变量进行原子操作的类
        构造方法:
            public AtomicInteger():	   				初始化一个默认值为0的原子型Integer
            public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
        成员方法:
            int get():   			 				 获取值
            int getAndIncrement():      			 以原子方式将当前值加1,注意,这里返回的是自增前的值。
            int incrementAndGet():     				 以原子方式将当前值加1,注意,这里返回的是自增后的值。
            int addAndGet(int data):				 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
            int getAndSet(int value):   			 以原子方式设置为newValue的值,并返回旧值。
 */
public class Demo01AtomicInteger {
    public static void main(String[] args) {
        //创建AtomicInteger对象,初始值为10
        AtomicInteger ai = new AtomicInteger(10);

        //int get():  获取值
        int i = ai.get();
        System.out.println(i);//10

        //int getAndIncrement():  以原子方式将当前值加1,注意,这里返回的是自增前的值。
        int i2 = ai.getAndIncrement();  //i++;
        System.out.println("i2:"+i2);//i2:10
        System.out.println(ai);//11

        //int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
        int i3 = ai.incrementAndGet();// ++i
        System.out.println("i3:"+i3);//i3:12
        System.out.println(ai);//12

        //int addAndGet(int data):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
        int i4 = ai.addAndGet(100);// 12+100
        System.out.println("i4:"+i4);//i4:112
        System.out.println(ai);//112

        //int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
        int i5 = ai.getAndSet(88);
        System.out.println("i5:"+i5);//i5:112 被替换的值
        System.out.println(ai);//88
    }
}

AtomicInteger解决变量的可见性、有序性、原子性(重点)

public class MyThread extends Thread{
    //不要直接使用int类型的变量,使用AtomicInteger
    public static AtomicInteger money = new AtomicInteger(0);
    @Override
    public void run() {
        System.out.println("Thread-0线程开始改变money的值!");
        for (int i = 0; i < 10000; i++) {
            //money.getAndIncrement();//先获取后自增  money++
            money.incrementAndGet();//先自增后获取  ++money
        }
        System.out.println("Thread-0线程执行完毕!");
    }
}

public class Demo01Thread {
    public static void main(String[] args) throws InterruptedException {
        //开启新的线程
        MyThread mt  = new MyThread();
        mt.start();

        System.out.println("main继续往下执行");
        System.out.println("main线程开始改变money的值!");
        for (int i = 0; i < 10000; i++) {
            MyThread.money.incrementAndGet();
        }
        System.out.println("main线程暂停1秒,让Thread-0执行完毕!");
        Thread.sleep(1000);
        System.out.println("最终money为:"+ MyThread.money);
    }
}

3.AtomicInteger的原理_CAS机制(乐观锁)

解决多线程高并发问题常用类

第二章 线程安全

AtomicInteger可以对“int类型的变量”做原子操作。但如果需要将“很多行代码”一起作为“原子性”执行——一个线程进入后,必须将所有代码行执行完毕,其它线程才能进入,可以使用synchronized关键字——重量级的同步关键字。

AtomicInteger:只能解决一个变量的原子性

synchronized:可以解决一段代码的原子性
解决多线程高并发问题常用类

2.线程安全问题的代码实现

/*
    卖票案例
 */
public class RunnableImpl implements Runnable{
    //定义共享的票源
    private int ticket = 100;

    //线程任务:卖票
    @Override
    public void run() {
        //让卖票重复执行
        while (true){
            //票大于0,就进行卖票
            if(ticket>0){
                //让线程睡眠10毫秒,提供安全问题出现的几率
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
                ticket--;
            }
        }
    }
}

/*
    创建3个线程进行买相同的100张票
 */
public class Demo01PayTticket {
    public static void main(String[] args) {
        RunnableImpl r = new RunnableImpl();
        Thread t0 = new Thread(r);
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t0.start();
        t1.start();
        t2.start();
    }
}

解决多线程高并发问题常用类

4.解决线程安全问题的第一种方式使用同步代码块(重点)

/*
    卖票案例出现了线程安全问题:
        1.卖出了重复的票
        2.卖出了不存在的票
    解决线程安全问题的第一种方式:使用同步代码块
        格式:
            synchronized(锁对象){
                访问了共享数据的代码(产生了线程安全问题的代码)
            }
        注意:
            锁对象可以是任意的对象new Person()  new Object()  new Student()  "aaa"==>字符数组
            必须保证所有的线程使用的都是同一个锁对象(唯一)
 */
public class RunnableImpl implements Runnable {
    //定义多个线程访问的共享资源
    private int ticket = 100;
    //定义一个锁对象
    //Object obj = new Object();
    String obj = "abc";// new char[]{'a','b','c'}

    //线程任务:卖票
    @Override
    public void run() {
        //让卖票重复执行
        while (true){
           //同步代码
           synchronized (obj){
               //判断票大于0,就进行卖票
               if(ticket>0){
                   //让线程睡眠10毫秒,提高线程安全问题出现几率
                   try {
                       Thread.sleep(10);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
                   ticket--;
               }
           }
       }
    }
}

/*
    创建3个线程进行买相同的100张票
 */
public class Demo01PayTticket {
    public static void main(String[] args) {
        RunnableImpl r = new RunnableImpl();
        Thread t0 = new Thread(r);
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t0.start();
        t1.start();
        t2.start();
    }
}

5.同步原理

解决多线程高并发问题常用类

6.解决线程安全问题的第二种方式——同步方法

/*
    卖票案例出现了线程安全问题:
        1.卖出了重复的票
        2.卖出了不存在的票
    解决线程安全问题的第二种方式:使用同步方法
    原理:
        把访问了共享数据的代码,提取出来,放到一个方法中
        在方法上添加一个同步关键字synchronized
        底层也是使用了一个锁对象,把方法锁住,只让一个线程进入到方法中执行
    格式:
        修饰符 synchronized 返回值类型 方法名(参数列表){
            访问了共享数据的代码(产生了线程安全问题的代码)
        }
 */
public class RunnableImpl implements Runnable {
    //定义多个线程访问的共享资源
    private static int ticket = 100;

    //线程任务:卖票
    @Override
    public void run() {
        System.out.println("this:"+this);//this:com.llz.demo06synchronized.RunnableImpl@4554617c
        //让卖票重复执行
       while (true){
           payTicketStatic();
       }
    }

    /*
        定义一个静态的同步方法
        静态的同步方法锁对象是谁? 是this吗?
        不是this,静态优先于对象加载到内存中
        使用的是本类的class文件对象:RunnbaleImpl.class(反射)
     */
    public static /*synchronized*/ void payTicketStatic(){
        synchronized (RunnableImpl.class){
            //判断票大于0,就进行卖票
            if(ticket>0){
                //让线程睡眠10毫秒,提高线程安全问题出现几率
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
                ticket--;
            }
        }
    }

    /*
        定义一个同步方法:把代码抽取到一个方法中  ctrl+alt+m
        同步方法的锁对象是谁?
        使用的就是根据本类创建的对象this  new RunableImpl();
     */
    public /*synchronized*/ void payTicket() {
        synchronized (this){
            //判断票大于0,就进行卖票
            if(ticket>0){
                //让线程睡眠10毫秒,提高线程安全问题出现几率
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
                ticket--;
            }
        }
    }
    public synchronized void payTicket() {
        //判断票大于0,就进行卖票
        if(ticket>0){
            //让线程睡眠10毫秒,提高线程安全问题出现几率
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
            ticket--;
        }
    }
}

/*
    创建3个线程进行买相同的100张票
 */
public class Demo01PayTticket {
    public static void main(String[] args) {
        RunnableImpl r = new RunnableImpl();
        System.out.println("r:"+r);//r:com.llz.demo12synchronized.RunnableImpl@4554617c
        Thread t0 = new Thread(r);
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t0.start();
        t1.start();
        t2.start();
    }
}

7.解决线程安全的第三种方式——Lock锁

/*
   卖票案例出现了线程安全问题:
        1.卖出了重复的票
        2.卖出了不存在的票
    解决线程安全问题的第三种方式:使用Lock锁
    java.util.concurrent.locks.Lock接口
        JDK1.5之后出现的
        Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
    接口中的方法:
        void lock()获取锁。
        void unlock() 释放锁。
    java.util.concurrent.locks.ReentrantLock类 implements Lock接口
    实现步骤:
        1.在成员位置创建ReentrantLock对象
        2.在可能出现线程安全问题的代码前,使用lock方法获取锁对象
        3.在可能出现线程安全问题的代码后,使用unlock方法释放锁对象
 */
public class RunnableImpl implements Runnable {
    //定义多个线程访问的共享资源
    private int ticket = 100;
    //1.在成员位置创建ReentrantLock对象
    ReentrantLock lock = new ReentrantLock();

    //线程任务:卖票
    @Override
    public void run() {
        //让卖票重复执行
        while (true){
            //2.在可能出现线程安全问题的代码前,使用lock方法获取锁对象
            lock.lock();//默认使用的锁对象也是this
            //判断票大于0,就进行卖票
            if(ticket>0){
                try {
                    //让线程睡眠10毫秒,提高线程安全问题出现几率
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //3.在可能出现线程安全问题的代码后,使用unlock方法释放锁对象
                    lock.unlock();//无论是否出现了异常,都会把锁对象释放掉
                }
            }
        }
    }

    /*@Override
    public void run() {
        //让卖票重复执行
       while (true){
           //2.在可能出现线程安全问题的代码前,使用lock方法获取锁对象
           lock.lock();//默认使用的锁对象也是this
               //判断票大于0,就进行卖票
               if(ticket>0){
                   //让线程睡眠10毫秒,提高线程安全问题出现几率
                   try {
                       Thread.sleep(10);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
                   ticket--;
               }
           //3.在可能出现线程安全问题的代码后,使用unlock方法释放锁对象
           lock.unlock();
       }
    }*/
}

8.CAS和Synchronized

CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?

Synchronized是从悲观的角度出发:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁

共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。

CAS是从乐观的角度出发:

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。

CAS这种机制我们也可以将其称之为乐观锁。

并发包

在JDK的并发包java.util.concurrent里提供了几个非常有用的并发容器和并发工具类。供我们在多线程开发中进行使用。这些集合和工具类都可以保证高并发的线程安全问题.

1.并发List集合CopyOnWriteArrayList(重点)

1).java.util.concurrent.CopyOnWriteArrayList(类):它是一个“线程安全”的ArrayList,而java.utils.ArrayList不是线程安全的。
2).如果是多个线程,并发访问同一个ArrayList,我们要使用:CopyOnWriteArrayList

/*
    java.util.concurrent.CopyOnWriteArrayList<E>:是一个高并发安全的集合
 */
public class MyThread extends Thread{
    //创建一个被多个线程共享的静态ArrayList集合
    //public static ArrayList<Integer> list = new ArrayList<>();//多线程不安全(1.集合索引越界异常2.数据不准确2000-->1998)
    public static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();//多线程安全

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            list.add(i);//往集合中添加元素
        }
        System.out.println("Thread-0线程往集合中添加元素结束!");
    }
}

public class Demo01CopyOnWriteArrayList {
    public static void main(String[] args) throws InterruptedException {
        //创建新线程对象并开启
        MyThread mt = new MyThread();
        mt.start();

        System.out.println("main线程继续执行");
        for (int i = 0; i < 1000; i++) {
            MyThread.list.add(i);
        }

        System.out.println("main线程休息1秒");
        Thread.sleep(1000);

        System.out.println("最终集合的长度为:"+MyThread.list.size());
    }
}

ArrayList集合的并发问题:

main线程会继续执行,往集合中添加元素
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 73
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
	at java.util.ArrayList.add(ArrayList.java:463)
	at com.llz.demo12CopyOnWriteArrayList.Demo01.main(Demo01.java:12)

main线程往集合中添加元素
main线程休息1秒钟,让Thread-0线程先执行完毕
Thread-0线程往集合中添加元素结束!
最终集合的长度为:1998


main线程会继续执行,往集合中添加元素
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
ArrayList集合的总长度为:2000

2.并发Set集合CopyOnWriteArraySet(重点)

/*
    java.util.concurrent.CopyOnWriteArraySet<E>:多线程访问安全的Set集合
 */
public class MyThread extends Thread{
    //创建公共的静态的Set集合,供所有的线程使用
    //public static HashSet<Integer> set = new HashSet<>();//多线程不安全(数据不准确2000-->1998)
    public static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();//多线程安全

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            set.add(i);
        }
        System.out.println("Thread-0线程执行完毕!");
    }
}

public class Demo01CopyOnWriteArraySet {
    public static void main(String[] args) throws InterruptedException {
        //创建新线程对象并开启
        MyThread mt = new MyThread();
        mt.start();

        System.out.println("main线程继续执行");
        for (int i = 1000; i < 2000; i++) {
            MyThread.set.add(i);
        }

        System.out.println("main线程休息1秒");
        Thread.sleep(1000);

        System.out.println("最终集合的长度为:"+ MyThread.set.size());
    }
}

HashSet集合存在并发问题:

main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
HashSet集合的总长度为:1996
    
main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
HashSet集合的总长度为:1999

3.并发Map集合ConcurrentHashMap(重点)

/*
    java.util.concurrent.ConcurrentHashMap<K,V>:多线程安全的双列集合,底层采用的是乐观锁(CAS机制),效率高
    java.util.Hashtable<K,V>:多线程安全的双列集合,底层采用的是悲观锁(synchronized,同步机制),效率低
 */
public class MyThread extends Thread{
    //创建公共的静态的map集合
    //public static HashMap<Integer,Integer> map = new HashMap<>();//多线程不安全(数据不准确:2000->1986)
    //public static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();//多线程安全
    public static Hashtable<Integer,Integer> map = new Hashtable<>();//多线程安全

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            map.put(i,i);
        }
        System.out.println("Thread-0线程执行结束!");
    }
}

public class Demo01ConcurrentHashMap {
    public static void main(String[] args) throws InterruptedException {
        //创建新线程对象并开启
        MyThread mt = new MyThread();
        mt.start();

        System.out.println("main线程继续执行");
        for (int i = 1000; i < 2000; i++) {
            MyThread.map.put(i,i);
        }

        System.out.println("main线程休息1秒");
        Thread.sleep(1000);

        System.out.println("最终集合的长度为:"+ MyThread.map.size());
    }
}

HashMap集合存在并发问题:

main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
Thread-0线程结束执行了!
HashMap集合的总长度为:1992

比较ConcurrentHashMap和Hashtable的效率

Java类库中,从1.0版本也提供一个线程安全的Map:Hashtable
Hashtable和ConcurrentHashMap有什么区别:
Hashtable采用的synchronized——悲观锁,效率更低。
ConcurrentHashMap:采用的CAS 机制——乐观锁,效率更高。

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

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  打印A、C
public class MyThreadAC extends Thread {
    private CountDownLatch countDownLatch;

    public MyThreadAC(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        System.out.println("A");
        try {
            countDownLatch.await();//等待计数器归0执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}

//线程1 打印B
public class MyThreadB extends Thread {
    private CountDownLatch countDownLatch;

    public MyThreadB(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        System.out.println("B");
        countDownLatch.countDown();//让计数器的值-1
    }
}

public class Demo01Thread {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch cdl = new CountDownLatch(1);//创建1个计数器
        new MyThreadAC(cdl).start();
        Thread.sleep(1000);
        new MyThreadB(cdl).start();
    }
}

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

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

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

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

多线程协作CyclicBarrier

概述

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

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

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

CyclicBarrier构造方法:

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

CyclicBarrier重要方法:

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

示例

//会议线程
public class PersonThread extends Thread {
    private CyclicBarrier cb;

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

    @Override
    public void run() {
        try {
            Thread.sleep((int) (Math.random() * 1000));
            System.out.println(Thread.currentThread().getName()+"...来到了会场!");
            cb.await();//cb内部会将计数器 - 1
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

//会议线程
public class MeetingThread extends Thread{
    @Override
    public void run() {
        System.out.println("人齐了,开始开会了!");
    }
}

//测试类
public class Demo {
    public static void main(String[] args) {
        //等待5个线程执行完毕,再执行MeetingThread
        CyclicBarrier cb = new CyclicBarrier(5,new MeetingThread());
        PersonThread  p1 = new PersonThread(cb);
        PersonThread  p2 = new PersonThread(cb);
        PersonThread  p3 = new PersonThread(cb);
        PersonThread  p4 = new PersonThread(cb);
        PersonThread  p5 = new PersonThread(cb);
        p1.start();
        p2.start();
        p3.start();
        p4.start();
        p5.start();
    }
}

执行结果:

 Thread-3...来到了会场!
Thread-2...来到了会场!
Thread-5...来到了会场!
Thread-1...来到了会场!
Thread-4...来到了会场!
人齐了,开始开会了!

使用场景

使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。
需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。

6.并发数量控制SemaphoreSemaphore的主要作用是控制线程的并发数量。

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

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

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

Semaphore构造方法:

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

Semaphore重要方法

public void acquire() 表示获取许可  lock
public void release() 表示释放许可  unlock

示例:同时允许两个线程执行

//教室线程
public class ClassRoom {
    private Semaphore semaphore = new Semaphore(2);
    public void info() throws InterruptedException {
        semaphore.acquire();//开始同步
        System.out.println(Thread.currentThread().getName()+"..来到了教室!");
        Thread.sleep(2000);//参观2秒
        System.out.println(Thread.currentThread().getName()+"..离开了教室!");
        semaphore.release();//结束同步
    }
}

//学生线程
public class StudentThread extends Thread{
    private ClassRoom classRoom;

    public StudentThread(ClassRoom classRoom) {
        this.classRoom = classRoom;
    }

    @Override
    public void run() {
        try {
            classRoom.info();//学生进入教室
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//测试类
public class Demo {
    public static void main(String[] args) {
        //创建一个教室
        ClassRoom classRoom = new ClassRoom();
        //循环创建5名学员
        for (int i = 0; i < 5; i++) {
            new StudentThread(classRoom).start();
        }
    }
}

执行结果:

Thread-1..来到了教室!
Thread-0..来到了教室!
Thread-1..离开了教室!
Thread-0..离开了教室!
Thread-2..来到了教室!
Thread-3..来到了教室!
Thread-3..离开了教室!
Thread-2..离开了教室!
Thread-4..来到了教室!
Thread-4..离开了教室!

7.线程信息交互Exchanger

概述

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

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

Exchanger构造方法:

public Exchanger()

Exchanger重要方法:

public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据

示例一:exchange方法的阻塞特性

1).制作线程A,并能够接收一个Exchanger对象:

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

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

    @Override
    public void run() {
        System.out.println("线程A开始执行");
        System.out.println("线程A给线程B100元,并从线程B得到一个电影票");
        String result = null;
        try {
            result = exchanger.exchange("100元");
            System.out.println("线程A得到的东西:"+result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2).制作线程B

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

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

    @Override
    public void run() {
        System.out.println("线程B开始执行");
        System.out.println("线程B给线程A一张电影票,并从线程A得到100元钱");
        String result = null;
        try {
            result = exchanger.exchange("电影票");
            System.out.println("线程B得到的东西:"+result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试类

public class Demo {
    public static void main(String[] args) {
        //创建Exchanger对象
        Exchanger<String> exchanger = new Exchanger<>();
        new ThreadA(exchanger).start();
        new ThreadB(exchanger).start();
    }
}

执行结果:

线程A开始执行
线程A给线程B100元,并从线程B得到一个电影票
线程B开始执行
线程B给线程A一张电影票,并从线程A得到100元钱
线程A得到的东西:电影票
线程B得到的东西:100

示例2:exchanger方法的超时

//A线程
public class ThreadA extends Thread {
    private Exchanger<String> exchanger;

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

    @Override
    public void run() {
        System.out.println("线程A开始执行");
        System.out.println("线程A给线程B100元,并从线程B得到一个电影票");
        String result = null;
        try {
            result = exchanger.exchange("100元",5, TimeUnit.SECONDS);
            System.out.println("线程A得到的东西:"+result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            System.out.println("5秒钟没等到线程B的值,线程A结束!");
        }
    }
}

//测试类
public class Demo {
    public static void main(String[] args) {
        //创建Exchanger对象
        Exchanger<String> exchanger = new Exchanger<>();
        new ThreadA(exchanger).start();
    }
}

结果:

线程A开始执行
线程A给线程B100元,并从线程B得到一个电影票
5秒钟没等到线程B的值,线程A结束!

使用场景

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

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

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

打赏
文章版权声明:除非注明,否则均为彭超的博客原创文章,转载或复制请以超链接形式并注明出处。
上一篇:C语言快速入门 下一篇:线程池回顾
相关推荐

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

猜你喜欢