600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 第十章_多线程(2)_线程池原子性并发工具类

第十章_多线程(2)_线程池原子性并发工具类

时间:2021-10-03 23:02:38

相关推荐

第十章_多线程(2)_线程池原子性并发工具类

目录

一、线程池1 - 线程状态2 - 线程池3 - Executors线程池二、Volatile三、原子性四、并发工具类1 - 并发工具类-Hashtable2 - 并发工具类-ConcurrentHashMap3 - 并发工具类-CountDownLatch4 - 并发工具类-Semaphore

一、线程池

1 - 线程状态

2 - 线程池

线程池概述:提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程线程池存在的意义系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行

3 - Executors线程池

Executors概述:JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池;我们可以使用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(()->{System.out.println(Thread.currentThread().getName() + "在执行了");});//Thread.sleep(2000);executorService.submit(()->{System.out.println(Thread.currentThread().getName() + "在执行了");});executorService.shutdown();}}public class MyThreadPoolDemo2 {public static void main(String[] args) {//参数不是初始值而是最大值ExecutorService executorService = Executors.newFixedThreadPool(10);ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;System.out.println(pool.getPoolSize());//0executorService.submit(()->{System.out.println(Thread.currentThread().getName() + "在执行了");});executorService.submit(()->{System.out.println(Thread.currentThread().getName() + "在执行了");});System.out.println(pool.getPoolSize());//2// executorService.shutdown();}}

ThreadPoolExecutor:newCachedThreadPool和newFixedThreadPool都是使用过JDK来帮助我们创建线程池对象,而ThreadPoolExecutor是我们自己创建线程池对象ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "在执行了");}}import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class MyThreadPoolDemo3 {// 参数一:核心线程数量// 参数二:最大线程数// 参数三:空闲线程最大存活时间// 参数四:时间单位// 参数五:任务队列 -> 让任务在队列中等待,等有线程空闲了,再从这个队列中获取任务并执行// 参数六:创建线程工厂 -> 按照默认的方式创建线程对象// 参数七:任务的拒绝策略 -> 1)什么时候拒绝任务:当提交的任务>池子中最大线程数量+队列容量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();}}

线程池-非默认任务拒绝策略:RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类

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

演示ThreadPoolExecutor.AbortPolicy任务处理策略

public class ThreadPoolExecutorDemo01 {public static void main(String[] args) {/*** 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s*/ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;// 提交5个任务,而该线程池最多可以处理4个任务,当我们使用AbortPolicy这个任务处理策略的时候,就会抛出异常for(int x = 0 ; x < 5 ; x++) {threadPoolExecutor.submit(() -> {System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");});}}}//控制台输出/*pool-1-thread-1---->> 执行了任务pool-1-thread-3---->> 执行了任务pool-1-thread-2---->> 执行了任务pool-1-thread-3---->> 执行了任务控制台报错,仅仅执行了4个任务,有一个任务被丢弃了*/

演示ThreadPoolExecutor.DiscardPolicy任务处理策略

public class ThreadPoolExecutorDemo02 {public static void main(String[] args) {/*** 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s*/ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;// 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错for(int x = 0 ; x < 5 ; x++) {threadPoolExecutor.submit(() -> {System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");});}}}//控制台输出/*pool-1-thread-1---->> 执行了任务pool-1-thread-1---->> 执行了任务pool-1-thread-3---->> 执行了任务pool-1-thread-2---->> 执行了任务控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃了*/

演示ThreadPoolExecutor.DiscardOldestPolicy任务处理策略

public class ThreadPoolExecutorDemo02 {public static void main(String[] args) {/*** 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s*/ThreadPoolExecutor threadPoolExecutor;threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy());// 提交5个任务for(int x = 0 ; x < 5 ; x++) {// 定义一个变量,来指定指定当前执行的任务;这个变量需要被final修饰final int y = x ;threadPoolExecutor.submit(() -> {System.out.println(Thread.currentThread().getName() + "---->> 执行了任务" + y);});}}}//控制台输出/*pool-1-thread-2---->> 执行了任务2pool-1-thread-1---->> 执行了任务0pool-1-thread-3---->> 执行了任务3pool-1-thread-1---->> 执行了任务4由于任务1在线程池中等待时间最长,因此任务1被丢弃*/

演示ThreadPoolExecutor.CallerRunsPolicy任务处理策略

public class ThreadPoolExecutorDemo04 {public static void main(String[] args) {/*** 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s*/ThreadPoolExecutor threadPoolExecutor;threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy());// 提交5个任务for(int x = 0 ; x < 5 ; x++) {threadPoolExecutor.submit(() -> {System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");});}}}//控制台输出/*pool-1-thread-1---->> 执行了任务pool-1-thread-3---->> 执行了任务pool-1-thread-2---->> 执行了任务pool-1-thread-1---->> 执行了任务main---->> 执行了任务通过控制台的输出,我们可以看到次策略没有通过线程池中的线程执行任务,而是直接调用任务的run()方法绕过线程池直接执行*/

二、Volatile

案例:问题引出以下代码执行的时候,可以发现控制台并没有打印女孩虽然知道结婚基金是十万,但是当基金的余额发生变化的时候,女孩无法知道最新的余额也就是说虽然MyThread2将money修改为了90000,但是并没有同步给MyThread1,在MyThread1中的money一直是等于100000

public class Money {public static int money = 100000;}public class MyThread1 extends Thread {@Overridepublic void run() {while(Money.money == 100000){}System.out.println("结婚基金已经不是十万了");}}public class MyThread2 extends Thread {@Overridepublic void run() {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();}}

问题分析:当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题 堆内存是唯一的,每一个线程都有自己的线程栈每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中在线程中,每一次使用是从变量的副本中获取的解决方案1:Volatile关键字,强制线程每次在使用的时候,都会看一下共享区域最新的值

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

解决方案2:synchronized同步代码块解决为什么synchronized同步代码块可以解决:因为同步代码块执行过程中会更新值给共享数据 线程获得锁清空变量副本拷贝共享变量最新的值到变量副本中执行代码将修改后变量副本中的值赋给共享数据释放锁

public class Money {public static Object lock = new Object();public static volatile int money = 100000;}public class MyThread1 extends Thread {@Overridepublic void run() {while(true){synchronized (Money.lock){if(Money.money != 100000){System.out.println("结婚基金已经不是十万了");break;}}}}}public class MyThread2 extends Thread {@Overridepublic 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();}}

三、原子性

原子性概念:所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体count++:不是一个原子性操作,也就是说它在执行的过程中,有可能被其他线程打断操作 下列案例的执行结果:已经送了9999个冰淇淋,正确的结果应该是10000个volatile不能保证原子性:即使count加了volatile关键字依然不能保证原子性

public class MyAtomThread implements Runnable {private volatile int count = 0; //送冰淇淋的数量@Overridepublic void run() {for (int i = 0; i < 100; i++) {//1,从共享数据中读取数据到本线程栈中.//2,修改本线程栈中变量副本的值//3,会把本线程栈中变量副本的值赋值给共享数据.count++;System.out.println("已经送了" + 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();}}}

解决方案1:synchronized锁解决

public class MyAtomThread implements Runnable {private volatile int count = 0; //送冰淇淋的数量private Object lock = new Object();@Overridepublic void run() {for (int i = 0; i < 100; i++) {//1,从共享数据中读取数据到本线程栈中.//2,修改本线程栈中变量副本的值//3,会把本线程栈中变量副本的值赋值给共享数据.synchronized (lock) {count++;System.out.println("已经送了" + 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();}}}

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

AtomicInteger的构造方法

public AtomicInteger():初始化一个默认值为0的原子型Integerpublic AtomicInteger(int initialValue):初始化一个指定值的原子型Integer

public class MyAtomIntergerDemo1 {// public AtomicInteger():初始化一个默认值为0的原子型Integer// public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integerpublic static void main(String[] args) {AtomicInteger ac = new AtomicInteger();System.out.println(ac);AtomicInteger ac2 = new AtomicInteger(10);System.out.println(ac2);}}

AtomicInteger的成员方法int get():获取值int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值int incrementAndGet():以原子方式将当前值加1,注意,这里返回的是自增后的值int addAndGet(int data):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果int getAndSet(int value):以原子方式设置为newValue的值,并返回旧值

public class MyAtomIntergerDemo2 {public static void main(String[] args) {AtomicInteger ac1 = new AtomicInteger(10);System.out.println(ac1.get());AtomicInteger ac2 = new AtomicInteger(10);int andIncrement = ac2.getAndIncrement();System.out.println(andIncrement);System.out.println(ac2.get());AtomicInteger ac3 = new AtomicInteger(10);int i = ac3.incrementAndGet();System.out.println(i);//自增后的值System.out.println(ac3.get());AtomicInteger ac4 = new AtomicInteger(10);int i = ac4.addAndGet(20);System.out.println(i);System.out.println(ac4.get());AtomicInteger ac5 = new AtomicInteger(100);int andSet = ac5.getAndSet(20);System.out.println(andSet);System.out.println(ac5.get());}}

使用原子类解决送冰淇淋问题

public class MyAtomThread implements Runnable {AtomicInteger ac = new AtomicInteger(0);@Overridepublic void run() {for (int i = 0; i < 100; i++) {int count = ac.incrementAndGet();System.out.println("已经送了" + 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();}}}

AtomicInteger原理:自旋锁 + CAS算法CAS算法: 有3个操作数(内存值V, 旧的预期值A,要修改的值B)当旧的预期值A == 内存值 此时修改成功,将V改为B当旧的预期值A!=内存值 此时修改失败,不做任何操作并重新获取现在的最新值(这个重新获取的动作就是自旋)incrementAndGet源码解析

//先自增,然后获取自增后的结果public final int incrementAndGet() {//+ 1 自增后的结果//this 就表示当前的atomicInteger(值)//1 自增一次return U.getAndAddInt(this, VALUE, 1) + 1;}public final int getAndAddInt(Object o, long offset, int delta) {//v 旧值int v;//自旋的过程do {//不断的获取旧值v = getIntVolatile(o, offset);//如果这个方法的返回值为false,那么继续自旋//如果这个方法的返回值为true,那么自旋结束//o 表示的就是内存值//v 旧值//v + delta 修改后的值} while (!weakCompareAndSetInt(o, offset, v, v + delta));//作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。// 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。//如果修改失败,那么继续自旋。return v;}

悲观锁和乐观锁:synchronized和CAS的区别 相同点:在多线程情况下,都可以保证共享数据的安全性不同点: synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁(悲观锁)cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据 如果别人修改过,那么我再次获取现在最新的值如果别人没有修改过,那么我现在直接修改共享数据的值(乐观锁)

四、并发工具类

1 - 并发工具类-Hashtable

Hashtable出现的原因:在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下Hashtable效率低下的原因:Hashtable采取悲观锁synchronized的形式保证数据的安全性;只要有线程访问,会将整张表全部锁起来,所以Hashtable的效率低下

//HashMappublic class MyHashMapDemo {public static void main(String[] args) throws InterruptedException {HashMap<String, String> hm = new HashMap<>();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- 50for (int i = 0; i < 51; i++) {System.out.println(hm.get(i + ""));}//0 1 2 3 .... 50}}//Hashtablepublic class MyHashtableDemo {public static void main(String[] args) throws InterruptedException {Hashtable<String, String> hm = new Hashtable<>();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- 50for (int i = 0; i < 51; i++) {System.out.println(hm.get(i + ""));}//0 1 2 3 .... 50}}

2 - 并发工具类-ConcurrentHashMap

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

public class MyConcurrentHashMapDemo {public static void main(String[] args) throws InterruptedException {ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>();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- 50for (int i = 0; i < 51; i++) {System.out.println(hm.get(i + ""));}//0 1 2 3 .... 50}}

ConcurrentHashMap1.7原理

ConcurrentHashMap1.8原理如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做;在第一次添加元素的时候创建哈希表计算当前元素应存入的索引 如果该索引位置为null,则利用cas算法,将本结点添加到数组中如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表 当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性

3 - 并发工具类-CountDownLatch

CountDownLatch的使用CountDownLatch(int count):参数写等待线程的数量。并定义了一个计数器await():让线程等待,当计数器为0时,会唤醒等待的线程countDown(): 线程执行完毕时调用,会将计数器-1使用场景:让某一条线程等待其他线程执行完毕之后再执行 案例需求:母亲线程等待3个孩子线程执行完毕后再执行母亲线程

//孩子线程1public class ChileThread1 extends Thread {private CountDownLatch countDownLatch;public ChileThread1(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.吃饺子for (int i = 1; i <= 10; i++) {System.out.println(getName() + "在吃第" + i + "个饺子");}//2.吃完说一声//每一次countDown方法的时候,就让计数器-1countDownLatch.countDown();}}//孩子线程2public class ChileThread2 extends Thread {private CountDownLatch countDownLatch;public ChileThread2(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.吃饺子for (int i = 1; i <= 15; i++) {System.out.println(getName() + "在吃第" + i + "个饺子");}//2.吃完说一声//每一次countDown方法的时候,就让计数器-1countDownLatch.countDown();}}//孩子线程3public class ChileThread3 extends Thread {private CountDownLatch countDownLatch;public ChileThread3(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.吃饺子for (int i = 1; i <= 20; i++) {System.out.println(getName() + "在吃第" + i + "个饺子");}//2.吃完说一声//每一次countDown方法的时候,就让计数器-1countDownLatch.countDown();}}//母亲线程public class MotherThread extends Thread {private CountDownLatch countDownLatch;public MotherThread(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.等待try {//当计数器变成0的时候,会自动唤醒这里等待的线程。countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}//2.收拾碗筷System.out.println("妈妈在收拾碗筷");}}//测试类public class MyCountDownLatchDemo {public static void main(String[] args) {//1.创建CountDownLatch的对象,需要传递给四个线程。//在底层就定义了一个计数器,此时计数器的值就是3CountDownLatch 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();}}//控制台打印/*三个孩子线程的打印都结束后打印母亲线程的“妈妈在收拾碗筷”*/

4 - 并发工具类-Semaphore

Semaphore使用场景:可以控制访问特定资源的线程数量案例实现步骤需要有人管理这个通道当有车进来了,发通行许可证当车出去了,收回通行许可证如果通行许可证发完了,那么其他车辆只能等着

public class MyRunnable implements Runnable {//1.获得管理员对象,private Semaphore semaphore = new Semaphore(2);//最多允许2个线程同时访问@Overridepublic 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();//需要使用同一个管理员对象}}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。