线程池,Volatile,原子性类AtomicInteger,乐观锁悲观锁,并发工具类Hashtable,ConcurrentHashMap类,Semaphore类

本文详细介绍了Java并发编程中的关键概念,包括线程池的创建和使用,如Executors的默认线程池、自定义线程池,以及线程池参数与任务拒绝策略。此外,文章讨论了Volatile关键字及其在原子性方面的局限,以及如何使用synchronized和AtomicInteger解决并发问题。还涉及了悲观锁和乐观锁的概念。最后,深入探讨了并发工具类,如Hashtable、ConcurrentHashMap以及Semaphore的使用场景和原理。
摘要由CSDN通过智能技术生成

 

目录

 一、线程的状态

 二、线程池

1.创建线程池的方式

1.1线程池-Executors默认线程池

1.2线程池-Executors创建指定上限的线程池

1.3线程池-ThreadPoolExecutor【自定义线程池】

线程池参数讲解 

②线程池-非默认任务拒绝策略

三、原子性

Volatile问题

 Volatile关键字解决

使用synchronized解决

原子性

原子性类:AtomicInteger类 

悲观锁和乐观锁

3. 并发工具类

3.1 并发工具类-Hashtable

3.2 并发工具类-ConcurrentHashMap基本使用

3.3 并发工具类-ConcurrentHashMap1.7原理

3.4 并发工具类-ConcurrentHashMap1.8原理  ​

3.5 并发工具类-CountDownLatch【控制线程运行的类】

3.6 并发工具类-Semaphore


一、线程的状态

没有就绪状态 

线程状态具体含义
NEW一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
RUNNABLE当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
BLOCKED当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED一个完全运行完成的线程的状态。也称之为终止状态、结束状态

 二、线程池

概述:

系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系

统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就

会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。

  

步骤:

1.利用Executors类(接口)的静态方法创建池子对象,方法返回值为Executors的子类ExecutorService(接口)的实现类对象(多态写法,得到的是实现类对象)【创建池子】

2.利用ExecutorService的submit()方法,控制池子,放置执行任务

3.利用ExecutorService的shutdown()方法,关闭池子对象


submit如果有任务,那麽创建一个线程对象进行执行run方法,如果有多个任务一起执行,先看有没有空余的线程,没有则自动创建一个新线程来执行任务

ExecutorService接口中的方法: 

 Future<?>submit(Runnable task)
          提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
 voidshutdown()
          启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

1.创建线程池的方式

1.1线程池-Executors默认线程池

我们可以使用Executors中所提供的静态方法来创建线程池

static ExecutorService newCachedThreadPool() 创建一个默认的线程池  

【Executors中静态方法,创建一个ExecutorService 的实现类对象,进而通过此对象执行操作】

 

Executors
//static ExecutorService newCachedThreadPool​()   创建一个默认的线程池
//static newFixedThreadPool​(int nThreads)	    创建一个指定最多线程数量的线程池

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {

        //1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Executors --- 【可以帮助我们创建线程池对象】
        //ExecutorService --- 【而此返回值可以帮助我们控制线程池】


       ExecutorService的 submit方法控制池子
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        //Thread.sleep(2000);

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });


shutdown方法关闭池子对象
        executorService.shutdown();
    }
}

1.2线程池-Executors创建指定上限的线程池

使用Executors中所提供的静态方法来创建线程池

static ExecutorService newFixedThreadPool(int nThreads) : 创建一个指定最多线程数量的线程池

//static ExecutorService newFixedThreadPool​(int nThreads)
//创建一个指定最多线程数量的线程池


public class MyThreadPoolDemo2 {
    public static void main(String[] args) {
        //参数不是初始值而是最大值
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
        System.out.println(pool.getPoolSize());//0

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        System.out.println(pool.getPoolSize());//2
//        executorService.shutdown();
    }
}

1.3线程池-ThreadPoolExecutor【自定义线程池】

创建线程池对象 :

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

代码实现 :

package com.itheima.mythreadpool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo3 {
//    参数一:核心线程数量
//    参数二:最大线程数
//    参数三:空闲线程最大存活时间
//    参数四:时间单位
//    参数五:任务队列【阻塞队列】
//    参数六:创建线程工厂
//    参数七:任务的拒绝策略【任务过多时处理办法】
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), 
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();
    }
}

线程池参数讲解 

public ThreadPoolExecutor(int corePoolSize,    【构造方法中的各个参数】
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
参数:    
corePoolSize:   核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime:  空闲线程最大存活时间,不能小于0
unit:           时间单位
workQueue:      任务队列,不能为null
threadFactory:  创建线程工厂,不能为null      
handler:        任务的拒绝策略,不能为null  
public class MyThreadPoolDemo3 {
//    参数一:核心线程数量
//    参数二:最大线程数
//    参数三:空闲线程最大存活时间
//    参数四:时间单位	--使用TimeUnit类中的方法
//    参数五:任务队列	--让人物在队列中等着,等有线程空闲了,再从这个队列中获取任务并执行
//    参数六:创建线程工厂	--按照默认的方式创建线程对象
//    参数七:任务的拒绝策略	--①什么时候拒绝任务    ②如何拒绝
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
						2, 【2个核心线程】
						5, 【最多5个线程,执行5个任务,如果一次性提交了10个任务,多出的5个任务存放到任务队列中】
						2, 【存活时间为2】	
						TimeUnit.SECONDS, 【存活时间单位为小时】
						new ArrayBlockingQueue<>(10), 【队列为阻塞队列,队列中可以存放10个任务进行等待】
 						Executors.defaultThreadFactory(),【调用了默认的线程工厂】
						new ThreadPoolExecutor.AbortPolicy());【调用默认的拒绝策略】


        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();
    }
}

   ①:什么时候拒绝

②线程池-非默认任务拒绝策略

RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。

ThreadPoolExecutor.AbortPolicy:             丢弃任务并抛RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy:          丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy:    抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy:        调用任务的run()方法绕过线程池直接执行。

注:明确线程池对多可执行的任务数 = 队列容量 + 最大线程数


三、原子性

Volatile问题

问题描述:当A线程取走了共享数据中的10W时,而B线程仍以为共享数据中还有10W 

 案例展示:【执行测试类发现,程序未终止,打印并未输出】

public class Money {【共享数据】
    public static int money = 100000;
}


public class Demo {【测试类】
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("小路同学");
        t1.start();

        MyThread2 t2 = new MyThread2();
        t2.setName("小皮同学");
        t2.start();
    }
}

原因: 从内存模型来讲:java的堆内存是唯一的,而栈内存是线程独有的(每一个线程都有一个单独的栈内存,所以案例中男孩线程,女孩线程都有自己各自的线程栈。【各自的栈内存空间】)我们在堆里面创建了一个共享数据,最开始的值是十万,女孩线程先抢到了CPU的执行权,执行了以下步骤:获取共享数据,将共享数据在线程栈中临时存储,临时存储的空间称变量副本【拷贝了一份】,以后此线程使用共享数据的时候,使用的是变量副本的值(作用:线程获取变量值速度更快)。然后男生线程抢到了执行权,也做了共享数据在线程栈中临时存储,进行变量副本操作,男孩先把九万 赋值给变量副本,然后把变量副本的九万赋值给共享数据,此时共享数据的值才变为九万,而此时女孩线程中的值,仍是十万,他也会去查看共享数据的值,但什么时候看无法控制,如果一直不去看,则使用的值一直是变量副本中的十万。

导致问题:

 

volatile作用:

        用了volatile之后,在A线程修改了该变量,会别强制写入主内存使其他线程可以用到最新值,所以保持了可见性,

但是不保证原子性是因为A在修改变量后还没写入内存就被B抢走了执行权,此时B用的仍是更新前的值。【虽然能用到新值,但什么时候才能用到不能控制】

 Volatile关键字解决

: 强制线程每次在使用数据时候,都会看一下共享区域最新的值,【在修饰符后+volatile】

package com.itheima.myvolatile;

public class Money {
    public static volatile int money = 100000;
}

使用synchronized解决

synchronized解决 :

1 ,线程获得锁

2 ,清空变量副本

3 ,拷贝共享变量最新的值到变量副本中

4 ,执行代码

5 ,将修改后变量副本中的值赋值给共享数据

6 ,释放锁

public class Money {
    public static Object lock = new Object();
    public static volatile int money = 100000;
}

public class MyThread1 extends  Thread {
    @Override
    public void run() {
        while(true){
            synchronized (Money.lock){
                if(Money.money != 100000){
                    System.out.println("结婚基金已经不是十万了");
                    break;
                }
            }
        }
    }
}

public class MyThread2 extends Thread {
    @Override
    public void run() {
        synchronized (Money.lock) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Money.money = 90000;
        }
    }
}

public class Demo {【测试类】
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("小路同学");
        t1.start();

        MyThread2 t2 = new MyThread2();
        t2.setName("小皮同学");
        t2.start();
    }
}

原子性

volatile不能保证原子性问题

概述 : 所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。要么同时成功,要么同时失败。

public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();

        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}
class MyAtomThread implements Runnable {
    private volatile int count = 0; //送冰淇淋的数量

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //1,从共享数据中读取数据到本线程栈中.
            //2,修改本线程栈中变量副本的值
            //3,会把本线程栈中变量副本的值赋值给共享数据.
            count++;
            System.out.println("已经送了" + count + "个冰淇淋");
        }
    }
}

运行发现此段代码执行了9999次,并未达到预期的10000次。不管给count变量加上或不加volatile与否都是这样的情况。

原因:

代码总结 : 定义为volatile的count++ 不是一个原子性操作, 他在执行的过程中,有可能被其他线程打断抢走执行权

 

解析:当A线程获取的共享数据自增变为101后,还没进行更新操作,就被B线程抢走执行权,B线程此时获取的共享数据值依旧为100。

总结:Volatile关键字:只能保证线程每次在使用共享数据的时候是最新值。但不能保证原子性


 【第一种解决方案【锁+volatile】】

解决方案 : 我们可以给count++操作添加锁,那么count++操作就是临界区中的代码,临界区中的代码一次只能被一个线程去执行,所以count++就变成了原子操作。【为其加上锁】【速度相对较慢,有一个获取锁,判断锁,释放锁的过程】

public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();

        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}
class MyAtomThread implements Runnable {
    private volatile int count = 0; //送冰淇淋的数量
    private Object lock = new Object();

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //1,从共享数据中读取数据到本线程栈中.
            //2,修改本线程栈中变量副本的值
            //3,会把本线程栈中变量副本的值赋值给共享数据.
            synchronized (lock) {
                count++;
                System.out.println("已经送了" + count + "个冰淇淋");
            }
        }
    }
}


原子性类:AtomicInteger类 

【第二种解决方案】

 【使用原子类AtomicInteger原由,使用同步代码块话要进行判断锁结束锁太麻烦,耗费时间】

概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:

AtomicBoolean: 原子更新布尔类型

AtomicInteger: 原子更新整型

AtomicLong: 原子更新长整型

以上3个类提供的方法几乎一模一样,所以本节仅以AtomicInteger为例进行讲解,AtomicInteger的常用方法如下:

构造方法:

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 MyAtomIntergerDemo2 {
//    int get():   		 		获取值
//    int getAndIncrement():     以原子方式将当前值加1,注意,这里返回的是自增前的值。
//    int incrementAndGet():     以原子方式将当前值加1,注意,这里返回的是自增后的值。
//    int addAndGet(int data):	 以原子方式将参数与对象中的值相加,并返回结果。
//    int getAndSet(int value):  以原子方式设置为newValue的值,并返回旧值。先获得原来的值作为返回值,然后再把方法的参数设置为新值
    public static void main(String[] args) {
            AtomicInteger ac1 = new AtomicInteger();
            System.out.println(ac1);【0】
          ---------------------------------  
             AtomicInteger ac1 = new AtomicInteger(10);
            System.out.println(ac1);【10】

//        AtomicInteger ac1 = new AtomicInteger(10);
//        System.out.println(ac1.get());【10】

//        AtomicInteger ac2 = new AtomicInteger(10);
//        int andIncrement = ac2.getAndIncrement();
//        System.out.println(andIncrement);【10】
//        System.out.println(ac2.get());【11】

//        AtomicInteger ac3 = new AtomicInteger(10);
//        int i = ac3.incrementAndGet();
//        System.out.println(i);//自增后的值【11】
//        System.out.println(ac3.get());【11】

//        AtomicInteger ac4 = new AtomicInteger(10);
//        int i = ac4.addAndGet(20);
//        System.out.println(i);【30】
//        System.out.println(ac4.get());【30】

        AtomicInteger ac5 = new AtomicInteger(100);
        int andSet = ac5.getAndSet(20);
        System.out.println(andSet);【100】
        System.out.println(ac5.get());【20】
    }
}

 使用AtomicIntege优化案例

public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();

        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}

public class MyAtomThread implements Runnable {
    //private volatile int count = 0; //送冰淇淋的数量
    //private Object lock = new Object();
    AtomicInteger ac = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //1,从共享数据中读取数据到本线程栈中.
            //2,修改本线程栈中变量副本的值
            //3,会把本线程栈中变量副本的值赋值给共享数据.
            //synchronized (lock) {
//                count++;
//                ac++; 错误写法,ac是一个对象没法自增,应调用他的方法
            int count = ac.incrementAndGet();先添加1在返回
            System.out.println("已经送了" + count + "个冰淇淋");
           // }
        }
    }
}

count++自增其内部是分为三步进行操作的,在执行每一步的时候cpu执行权都有可能被其他线程抢走,而我们想要的是三步操作要么同时成功,要么同时失败保证原子性。 


 AtomicInteger原理 : 自旋锁 + CAS 算法

CAS算法:

有3个操作数(内存值V, 旧的预期值A,要修改的值B)

当旧的预期值A == 内存值 此时修改成功,将V改为B

当旧的预期值A!=内存值 此时修改失败,不做任何操作

并重新获取现在的最新值(这个重新获取的动作就是自旋)

 


悲观锁和乐观锁

synchronized和CAS的区别 :

相同点:在多线程情况下,都可以保证共享数据的安全性。

不同点:synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。(悲观锁)

cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。

如果别人修改过,那么我再次获取现在最新的值。

如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)


3. 并发工具类

3.1 并发工具类-Hashtable

Hashtable出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。

因为是数组,且是引用类型,默认值为null 

3.2 并发工具类-ConcurrentHashMap基本使用

ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。

基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap。

体系结构:

总结 :

1 ,HashMap是线程不安全的。多线程环境下会有数据安全问题

2 ,Hashtable是线程安全的,但是会将整张表锁起来,效率低下

3,ConcurrentHashMap也是线程安全的,效率较高。 在JDK7和JDK8中,底层原理不一样。

public class MyConcurrentHashMapDemo {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>(100);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 25; i++) {
                hm.put(i + "", i + "");
            }
        });


        Thread t2 = new Thread(() -> {
            for (int i = 25; i < 51; i++) {
                hm.put(i + "", i + "");
            }
        });

        t1.start();
        t2.start();

        System.out.println("----------------------------");
        //为了t1和t2能把数据全部添加完毕
        Thread.sleep(1000);

        //0-0 1-1 ..... 50- 50

        for (int i = 0; i < 51; i++) {
            System.out.println(hm.get(i + ""));
        }//0 1 2 3 .... 50
    }
}

3.3 并发工具类-ConcurrentHashMap1.7原理

3.4 并发工具类-ConcurrentHashMap1.8原理  

总结 :

1,如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。 在第一次添加元素的时候创建哈希表

2,计算当前元素应存入的索引。

3,如果该索引位置为null,则利用cas算法,将本结点添加到数组中。

4,如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。

5,当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性


3.5 并发工具类-CountDownLatch【控制线程运行的类】

CountDownLatch类 :

使用场景: 让某一条线程等待其他线程执行完毕之后再执行

方法解释
public CountDownLatch(int count)【构造方法】参数传递线程数,表示等待线程数量
public void await()让线程等待
public void countDown()当前线程执行完毕

总结 :

1. CountDownLatch(int count):参数写等待线程的数量。并定义了一个计数器。

2. await():让线程等待,当计数器为0时,会唤醒等待的线程

3. countDown(): 线程执行完毕时调用,会将计数器-1。

public class MyCountDownLatchDemo {
    public static void main(String[] args) {
        //1.创建CountDownLatch的对象,需要传递给四个线程。
        //在底层就定义了一个计数器,此时计数器的值就是3
        CountDownLatch countDownLatch = new CountDownLatch(3);
        //2.创建四个线程对象并开启他们。
        MotherThread motherThread = new MotherThread(countDownLatch);
        motherThread.start();

        ChileThread1 t1 = new ChileThread1(countDownLatch);
        t1.setName("小明");

        ChileThread2 t2 = new ChileThread2(countDownLatch);
        t2.setName("小红");

        ChileThread3 t3 = new ChileThread3(countDownLatch);
        t3.setName("小刚");

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


public class ChileThread1 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread1(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }


public class ChileThread2 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread2(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 15; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }


public class ChileThread3 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread3(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 20; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}




public class MotherThread extends Thread {
    private CountDownLatch countDownLatch;
    public MotherThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        //1.等待
        try {
            //当计数器变成0的时候,会自动唤醒这里等待的线程。
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2.收拾碗筷
        System.out.println("妈妈在收拾碗筷");
    }
}

CountDownLatch相当于创建一个计数器。

让孩子与mom线程共用一个计数器,将CountDownLatch的对象并入各自线程创建的参数中,以达到共用计数器效果


3.6 并发工具类-Semaphore

使用场景 :

可以控制访问特定资源的线程数量。

 

public class MyRunnable implements Runnable {
    //1.获得管理员对象,
    private Semaphore semaphore = new Semaphore(2);
    @Override
    public void run() {
        //2.获得通行证
        try {
            semaphore.acquire();
            //3.开始行驶
            System.out.println("获得了通行证开始行驶");
            Thread.sleep(2000);
            System.out.println("归还通行证");
            //4.归还通行证
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class MySemaphoreDemo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        for (int i = 0; i < 100; i++) {
            new Thread(mr).start();
        }
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值