java并发学习--第七章 JDK提供的线程工具类

一、ThreadLocal

  ThreadLocal类用于隔离多线程中使用的对象,为ThreadLocal类中传递的泛型就是要隔离的对象,简单的来说:如果我们在主线程创建了一个对象,并且需要给下面的多线程任务都传递这个对象,那么如果这个对象传递到ThreadLocal,那么每个线程获取的对象都是独立的,不会受其他线程的改变而改变。

  ThreadLocal中一共有三个常用方法:

  

  get()方法:获取与当前线程关联的ThreadLocal值。 

 

  set(T value)方法:设置与当前线程关联的ThreadLocal值。

  

  initialValue()方法:设置与当前线程关联的ThreadLocal初始值。

  

  我们来看一个列子,创建两个线程,两个线程共同使用一个对象,我们来观察这个对象的值以及ThreadLocal中这个对象的值:

  对象User类:

public class User {

    int num;

    public User(int num) {
        this.num = num;
    }

    /**
     * 我们只使用get方法,并且每次获取num都为其加1
     * 
     * 使用synchronized保证getNum获取的num是线程安全的
     * @return
     */
    synchronized public int getNum() {
        return num++;
    }

}

  线程任务类:

public class ThreadlocaDemo extends Thread{

    User user;

    ThreadlocaDemo(User user){
        this.user=user;
    }

    /**
     * 创建一个ThreadLocal对象,为它的泛型传入User
     */
    ThreadLocal<User> userLoacl = new ThreadLocal<User>(){
        /**
         * 初始化方法,将设置user中的初始值
         */
        @Override
        protected User initialValue() {
            User user=new User(10);
            return user;
        }
    };


    @Override
     public void run() {

        for (int i = 1; i <3 ; i++) {
            //休息一会儿
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印user中获取的值
            System.out.println(getName()+"user对象的值"+user.getNum());
            //打印ThreadLocal中的user对象的值
            System.out.println(getName()+"ThreadLocal中的user对象的值"+userLoacl.get().getNum());

        }

    }

}

  测试类:

public class Main {

    public static void main(String[] args) {
        //创建一个共用的对象
        User user=new User(10);
        //创建两个线程任务
        ThreadlocaDemo threadlocaDemo1=new ThreadlocaDemo(user);
        ThreadlocaDemo threadlocaDemo2=new ThreadlocaDemo(user);
        threadlocaDemo1.setName("这是1号线程:");
        threadlocaDemo2.setName("这是2号线程:");

        threadlocaDemo1.start();
        threadlocaDemo2.start();
    }
}

运行的结果:

 

 根据运行结果我们可以清楚的看到,在ThreadLocal中的user对象是隔离的,外面的user对象没有被隔离,被两个线程都进行修改过。

 二、Exchanger

  Exchanger可以交换两个线程的数据,它的实现思想是当线程进行到Exchanger类调用的exchange方法时,会阻塞当前线程,直到有其他线程也进入了exchange方法中就开始交换两个线程的数据。

  需要注意的是,exchange只能作为两个线程进行交换数据,如果有多个线程,那么获取的数据是随机的。

  我们来看一个列子:

  线程任务类,交换一个字符串数据

public class ExchangerThread extends Thread{

    //Exchanger对象
    Exchanger<String> change;
    //当前线程中的数据,需要交换的数据
    String thisStr;

    public ExchangerThread(Exchanger<String> change, String thisStr) {
        this.change = change;
        this.thisStr = thisStr;
    }

    @Override
    public void run() {
        try {
            //exchange方法,将要交换的数据传递到exchange方法中
            //获取的值就是交换后的数据
            String getStr=change.exchange(thisStr);
            System.out.println(getName()+getStr);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

 

  测试类:

public class ExchangerDemo {


    public static void main(String[] args) {
        //创建Exchanger对象
        Exchanger<String> exchanger=new Exchanger<>();
        //创建两个线程交换数据
        ExchangerThread exchangerThread1=new ExchangerThread(exchanger,"这是1号线程的数据");
        ExchangerThread exchangerThread2=new ExchangerThread(exchanger,"这是2号线程的数据");
        exchangerThread1.setName("我是1号线程,交互获取的数据是:");
        exchangerThread2.setName("我是2号线程,交互获取的数据是:");
        exchangerThread1.start();
        exchangerThread2.start();
    }
}

运行的结果:

 

可以看到如果只有两个线程,交换的数据是确定的,如果有多个线程呢,我们在测试类中多添加几个线程:

public class ExchangerDemo {
    
    public static void main(String[] args) {
        //创建Exchanger对象
        Exchanger<String> exchanger=new Exchanger<>();
        //创建两个线程交换数据
        ExchangerThread exchangerThread1=new ExchangerThread(exchanger,"这是1号线程的数据");
        ExchangerThread exchangerThread2=new ExchangerThread(exchanger,"这是2号线程的数据");
        ExchangerThread exchangerThread3=new ExchangerThread(exchanger,"这是3号线程的数据");
        ExchangerThread exchangerThread4=new ExchangerThread(exchanger,"这是4号线程的数据");
        exchangerThread1.setName("我是1号线程,交互获取的数据是:");
        exchangerThread2.setName("我是2号线程,交互获取的数据是:");
        exchangerThread3.setName("我是3号线程,交互获取的数据是:");
        exchangerThread4.setName("我是4号线程,交互获取的数据是:");
        exchangerThread1.start();
        exchangerThread2.start();
        exchangerThread3.start();
        exchangerThread4.start();
    }
    
}

运行的结果:

 

 可以看到,多个线程进行交换数据,数据的交换是随机的。

 

 三、CountDownLatch

   CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作前,它允许一个或多个线程一直等待。简单的说就是一个线程组正在工作,但是他们执行的任务都有一道门,这道门是关着的,会导致每个线程接下来的任务都暂停进行,简单的说就是线程被阻塞了,只有当所有线程都在这个门前,即所有线程都被阻塞了,才会继续执行。

  当然了,上面的例子不太准确,CountDownLatch中实现所有线程都完成任务才能继续进行的方式不是这样,它是有一个计算器,当这个计算器的值等于0时,才会释放当前阻塞的所有线程。

  CountDownLatch中常用的方法:

  getCount():获取当前计数器剩余计数

  countDown():计算器的值减1

  await():阻塞当前线程,只有当计算器的值等于0时,才会释放当前线程

 例子:

 创建一个CountDownLatch对象,它的计数器的值为3,我们创建3个线程,每个线程执行完任务后先countDown(),再调用await()方法等待:

 线程任务类:

 

public class CountDownLatchDemo extends Thread {

    // 创建CountDownLatch对象
    CountDownLatch latch;
    // 休眠时间
    int num;

    // 构造器传参
    public CountDownLatchDemo(CountDownLatch latch, int num) {
        super();
        this.latch = latch;
        this.num = num;
    }

    @Override
    public void run() {
        try {
            System.out.println("当前执行的是:" + getName());
            // 让线程执行任务
            Thread.sleep(num);
            // 线程执行完成任务后,对CountDownLatch的值减1
            latch.countDown();
            System.out.println(getName() + "完成任务,等待其他线程执行任务");
            // 这里让线程阻塞,只有当CountDownLatch等于0时才继续执行线程
            // 即当所有的线程都完成了任务,才开始一起继续执行下面的任务
            latch.await();
            System.out.println("所有线程都完成后才执行的代码:" + getName());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

  测试类:

public class Main {

        public static void main(String[] args) {
            //创建一个CountDownLatch对象,设置值为3,表示当有3个线程都完成任务后,才开始执行阻塞后的代码
            CountDownLatch latch=new CountDownLatch(3) ;
            //创建3个线程,每个线程的CountDownLatch对象是同一个
            CountDownLatchDemo countDownLatchDemo1=new CountDownLatchDemo(latch,100);
            CountDownLatchDemo countDownLatchDemo2=new CountDownLatchDemo(latch,1000);
            CountDownLatchDemo countDownLatchDemo3=new CountDownLatchDemo(latch,2000);
            
            countDownLatchDemo1.setName("1号线程");
            countDownLatchDemo2.setName("2号线程");
            countDownLatchDemo3.setName("3号线程");
            
            countDownLatchDemo1.start();
            countDownLatchDemo2.start();
            countDownLatchDemo3.start();
        }
    
}

  运行的结果:

根据运行的结果我们可以看到,1、2、3号线程在执行完成任务后都等待,等待3号执行完成任务,使得计算器的值为0时,开始释放所有线程。

四、CyclicBarrier、

  CyclicBarrier与countDownLatch相识,都有一个计算器,但是两个类不同的是:countDownLatch都是当计算器的值为0时才释放所有线程,而CyclicBarrier是当阻塞的线程数等于计算器的值时,才开始释放线程。这两个类最大的区别就是一个是线程开始时的阻塞,一个是线程结束前的阻塞。

  CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。

  我们来看一个例子:阻塞了三个线程后,开始执行:

  线程任务类: 

public class CyclicBarrierDemo extends Thread{
    
    //创建CyclicBarrier对象
    CyclicBarrier cyclicBarrier;
    //休眠时间
    int num;
    
    
    public CyclicBarrierDemo(CyclicBarrier cyclicBarrier, int num) {
        super();
        this.cyclicBarrier = cyclicBarrier;
        this.num = num;
    }


    @Override
    public void run() {
        try {
            System.out.println("任务开始,当前执行的是:"+getName());
            //让线程执行任务
            Thread.sleep(num);
            System.out.println(getName()+"准备工作完成,等待其他线程执行任务");
            //执行完成后,等待其他线程完成任务
            cyclicBarrier.await();
            Thread.sleep(num);
            System.out.println("所有线程都准备好后才执行的代码:我是"+getName());
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

}

  测试类:

public static void main(String[] args) {
        //阻塞的线程数量
        int waitNum=3;
        //创建cyclicBarrier对象
        CyclicBarrier cyclicBarrier=new CyclicBarrier(waitNum);
        
        CyclicBarrierDemo cyclicBarrierDemo1=new CyclicBarrierDemo(cyclicBarrier,100);
        CyclicBarrierDemo cyclicBarrierDemo2=new CyclicBarrierDemo(cyclicBarrier,1000);
        CyclicBarrierDemo cyclicBarrierDemo3=new CyclicBarrierDemo(cyclicBarrier,2000);
        
        cyclicBarrierDemo1.setName("1号线程:");
        cyclicBarrierDemo2.setName("2号线程:");
        cyclicBarrierDemo3.setName("3号线程:");
        
        cyclicBarrierDemo1.start();
        cyclicBarrierDemo2.start();
        cyclicBarrierDemo3.start();
        
    }
    
}

运行的结果:

  

 

 五、Semaphore

  semaphore的作用是控制资源的访问,他能够限制当前资源能够被多少个线程所访问。它和线程池有些类似,但是与线程池不同的是,线程池是提前生成好了多个线程放进线程池里,如果超出线程池设置的数量,那么线程是不会被创建,但是semaphore超出了设置的线程数,还会继续创建线程。

  semaphore中的方法:

  acquire():  获取许可

  release():  释放资源

  availablePermits():  获取当前可用的资源数量

  semaphore的构造方法

  public Semaphore(int permits,boolean fair)

  permits:初始化设置线程的数据

    fair:表示是否以线程获取锁的顺序来执行线程,就是是否用公平锁,false表示不使用,默认是false

  我们来看一个例子,创建1个线程数组和一个semaphore任务类,线程数组的长度为10,semaphore任务类的值为5,我们观察它们的执行方式:

  semaphore任务类:

public class SemaphoreDemo extends Thread {

    Semaphore semaphore;

    public SemaphoreDemo(Semaphore semaphore) {
        super();
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            // 获取许可
            semaphore.acquire();
            System.out.println("任务开始,当前执行的是:" + getName());
            // 让线程执行任务
            Thread.sleep(2000);
            // 执行完成后,释放资源
            semaphore.release();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

  测试类:

public class Main {

    public static void main(String[] args) {
        // 创建Semaphore对象
        Semaphore semaphore = new Semaphore(5);
        // 创建一个长度为10的线程数组
        Thread[] threadAry = new Thread[10];
        for (int i = 0; i < threadAry.length; i++) {
            // 线程数组的值都添加线程任务
            SemaphoreDemo semaphoreDemo = new SemaphoreDemo(semaphore);
            semaphoreDemo.setName(i + "号线程");
            threadAry[i] = semaphoreDemo;
        }
        // 执行线程任务
        for (int i = 0; i < threadAry.length; i++) {
            threadAry[i].start();
        }

    }
}

 

 

  

  

posted @ 2019-09-25 16:23  想去天空的猫  阅读(375)  评论(0编辑  收藏  举报