a网站建设,商城网站内容模块有哪些,静态网站怎么容易做,搜狗网站排名软件目录
线程安全的什么情况用#xff1f;情况下用线程安全的类#xff1f;
线程池线程方面的优化
线程池调优主要涉及以下几个关键参数。
线程不安全原因?
1. 共享资源访问冲突
2. 缺乏原子操作
3. 内存可见性问题
4. 重排序问题
如何解决线程不安全的问题#xff1…目录
线程安全的什么情况用情况下用线程安全的类
线程池线程方面的优化
线程池调优主要涉及以下几个关键参数。
线程不安全原因?
1. 共享资源访问冲突
2. 缺乏原子操作
3. 内存可见性问题
4. 重排序问题
如何解决线程不安全的问题
1. 使用互斥锁Mutex
2. 使用信号量Semaphore
3. 使用读写锁ReadWriteLock
4. 采用线程安全的数据结构
5. 利用原子变量Atomic Variables
多用户同时修改同一条数据并发修改数据?
1. 数据库事务隔离级别
2. 乐观锁
3. 悲观锁
将任务并行执行完后再串行执行。
核心线程数设置
判断一个任务是 CPU 密集型还是 I/O 密集型可以从多个方面入手。
新建T1、T2、T3三个线程 ,怎么保持顺序执行
方法一使用 join 方法
方法二使用单线程执行器ExecutorService
使用 CountDownLatch
使用 CyclicBarrier
join方法的使用 线程核心参数7个参数核心线程数、最大线程数、
空闲线程存活时间、存活时间、
任务队列、线程工厂、拒绝策略
场景核心线程数10、最大线程数20、有界队列20
根据线程池运行原理什么时候创建最大线程数 核心线程数10有界队列2030大于的话开始
拒绝策略什么开始 最大线程数20队列数2040大于的话开始
拒绝策略有什么
4种AbortPolicy默认AbortPolicy [ˈpɒləsi]
直接拒绝抛出异常阻止系统继续接收该任务这种需要注意处理。
新任务会被直接丢弃并且不会有任何提示。
有调用者所在线程处理
丢弃最早的任务。 线程安全的什么情况用情况下用线程安全的类
多线程共享资源场景
资源共享当多个线程需要访问和修改同一个共享资源如对象、数据结构时必须使用线程安全的类。例如一个多线程的 Web 应用中多个线程可能会同时访问和修改用户的会话信息存储在一个共享的对象中此时就需要使用线程安全的会话管理类以确保会话数据的一致性。数据竞争风险如果不使用线程安全的类在多线程环境下可能会出现数据竞争的情况。比如两个线程同时对一个非线程安全的计数器进行自增操作可能会导致计数结果不准确。线程安全的类通过内部的同步机制如锁、原子操作来避免这种情况的发生。
并发数据结构操作
集合类操作在多线程环境下操作集合类如 List、Set、Map时通常需要考虑线程安全。例如在一个多线程的任务调度系统中多个线程可能会同时向一个任务列表ArrayList中添加或移除任务这时候使用线程安全的集合类如 CopyOnWriteArrayList可以避免数据不一致的问题。缓存系统对于多线程访问的缓存数据结构线程安全尤为重要。比如一个分布式缓存系统多个线程可能会同时查询、更新缓存中的数据。使用线程安全的缓存类可以保证缓存数据的准确性和完整性。
避免复杂的同步操作
降低开发难度如果手动实现同步机制来保证多线程环境下的资源安全需要编写复杂的代码包括正确地获取和释放锁、处理死锁等问题。而使用线程安全的类可以简化开发过程让开发者更专注于业务逻辑。例如在多线程的日志记录系统中使用线程安全的日志记录器如 Log4j 的线程安全版本可以避免开发者自己去处理多个线程同时写入日志文件可能导致的混乱。
线程池线程方面的优化
在核心线程数的设置上要根据任务类型和系统资源来确定。
对于计算密集型任务核心线程数应设置为处理器核心数加 1 或减 1以充分利用 CPU 资源且避免过多的上下文切换。
而对于 I/O 密集型任务由于线程大部分时间在等待 I/O 操作完成核心线程数可设置为处理器核心数的数倍以提高利用率。 在队列选择方面有多种队列可供选择。无界队列如 LinkedBlockingQueue可容纳很多任务但可能导致任务堆积使内存溢出适用于任务量小且任务处理速度快的场景。
有界队列如 ArrayBlockingQueue能控制队列大小防止内存无限膨胀但队列满时会拒绝任务需要合理处理拒绝策略。SynchronousQueue 是一种特殊的无存储功能的队列适合任务处理速度快且要求即时响应的场景。 在拒绝策略上
AbortPolicy 是默认策略当任务被拒绝时直接抛出异常适用于对任务丢失不敏感的场景。CallerRunsPolicy 会让调用者线程执行被拒绝的任务有助于减轻线程池压力但会降低调用者线程的执行效率。
DiscardPolicy 直接丢弃被拒绝的任务
而 DiscardOldestPolicy 则丢弃队列中最老的任务这两种策略可能会导致任务丢失需谨慎使用。 线程池的动态调整也很重要。可以根据系统负载动态地调整线程池的大小如在任务量突然增加时增加线程池的线程数以加快任务处理速度任务量减少时相应地减少线程数节省资源。此外还可以定期监控线程池的运行状况包括线程的活跃度、队列的长度等以便及时发现问题并优化。 线程池调优主要涉及以下几个关键参数。 一是核心线程数corePoolSize。这是线程池中始终保留的线程数量它的优化需依据任务特性。对于 CPU 密集型任务核心线程数应接近或等于 CPU 核心数这样能最大程度减少线程上下文切换带来的开销确保 CPU 资源高效利用。对于 I/O 密集型任务由于线程常处于等待 I/O 完成的空闲状态核心线程数通常设为 CPU 核心数的数倍从而提高整体的任务处理效率。 二是最大线程数maximumPoolSize。它规定了线程池允许的最大线程数量。在任务队列满时若有新任务到来线程池会在核心线程数和最大线程数之间创建新线程来处理任务。合理设置此参数很重要若设置过大可能导致过多的线程竞争 CPU、内存等资源引发性能下降和资源浪费若设置过小在任务高峰时无法及时处理任务造成任务堆积。 三是线程存活时间keepAliveTime和存活时间单位unit。当线程池中的线程数大于核心线程数时空闲时间超过存活时间的线程将被终止。存活时间的设置取决于任务的频率和持续时间。对于任务间隔时间长、持续时间短的情况可适当缩短存活时间以便更快地释放资源反之可适当延长。 四是任务队列workQueue。不同类型的任务队列对线程池性能有显著影响。有界队列如 ArrayBlockingQueue能限制队列长度防止任务过多导致内存耗尽但队列满时可能需要合理的拒绝策略来处理后续任务。无界队列如 LinkedBlockingQueue可容纳无限多的任务但可能使任务无限堆积造成内存问题或任务处理延迟。SynchronousQueue 没有存储功能新任务必须在有空闲线程时才能被接收常用于要求即时响应的场景。 五是拒绝策略rejectedExecutionHandler。当线程池和任务队列都无法容纳新任务时就会触发拒绝策略。常见的拒绝策略包括 AbortPolicy直接抛出异常、CallerRunsPolicy由调用线程处理任务、DiscardPolicy直接丢弃任务和 DiscardOldestPolicy丢弃队列中最老的任务。选择合适的拒绝策略要根据任务的重要性和业务对任务丢失的容忍度来决定。 线程不安全原因?
1. 共享资源访问冲突
当多个线程同时访问和修改共享的数据或资源如全局变量、共享文件、数据库连接等时如果没有适当的同步机制就可能导致数据不一致。
例如两个线程同时对一个计数器变量进行加 1 操作可能会出现覆盖写入的情况导致最终结果小于预期。
2. 缺乏原子操作
一些操作看起来是一个整体但在机器指令层面可能被分解为多个步骤。
如果一个线程在执行这些步骤的过程中被中断其他线程又开始执行相同的操作就会出现问题。
比如对一个变量的读取和写入操作在多线程环境下可能不是原子的导致数据不一致。
3. 内存可见性问题
在多线程环境中每个线程可能有自己的缓存如 CPU 缓存。当一个线程修改了共享变量的值后其他线程可能由于缓存的原因无法立即看到这个修改从而导致线程之间数据不一致。例如一个线程更新了一个共享的布尔变量的值但另一个线程可能因为缓存还在使用旧的值。
4. 重排序问题
为了提高性能编译器或处理器可能会对指令进行重新排序。在单线程环境下这种重排序通常不会影响程序的正确性。但在多线程环境下可能会导致线程安全问题。
例如一个线程中两条语句的执行顺序被改变可能会影响其他线程对共享变量的访问和操作。 如何解决线程不安全的问题
1. 使用互斥锁Mutex
原理互斥锁用于保护共享资源同一时刻只有一个线程可以获取锁并访问被保护的资源。其他线程如果试图获取已被占用的锁就会被阻塞直到锁被释放。
示例在 Java 中可以使用synchronized关键字
或者java.util.concurrent.locks.Lock接口如ReentrantLock来实现互斥锁。 import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter { private int count 0; private Lock lock new ReentrantLock(); public void increment() { lock.lock(); try { count; } finally { lock.unlock(); } }}
2. 使用信号量Semaphore
原理信号量维护了一个许可集线程在访问共享资源前需要获取许可
访问结束后释放许可。通过控制许可的数量
可以限制同时访问共享资源的线程数量。
示例在 Java 中java.util.concurrent.Semaphore类可以实现信号量。
例如限制同时访问某个资源的线程数量为 3 import java.util.concurrent.Semaphore;
class ResourceAccess { private Semaphore semaphore new Semaphore(3); public void accessResource() throws InterruptedException { semaphore.acquire(); try { // 访问共享资源的代码 } finally { semaphore.release(); } }
} 3. 使用读写锁ReadWriteLock
原理读写锁区分对共享资源的读操作和写操作。
多个线程可以同时进行读操作但写操作是互斥的
且当有一个线程进行写操作时其他线程包括读线程都不能访问。
示例在 Java 中java.util.concurrent.locks.ReadWriteLock接口
和ReentrantReadWriteLock类实现读写锁。 import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class DataHolder { private int data; private ReadWriteLock lock new ReentrantReadWriteLock(); public int readData() { lock.readLock().lock(); try { return data; } finally { lock.readLock().unlock(); } } public void writeData(int newData) { lock.writeLock().lock(); try { data newData; } finally { lock.writeLock().unlock(); } }
}
4. 采用线程安全的数据结构
原理许多编程语言都提供了线程安全的容器类。这些容器内部已经通过合适的同步机制如锁来保证在多线程环境下的安全访问。
示例在 Java 中java.util.concurrent包下的ConcurrentHashMap、
CopyOnWriteArrayList等容器是线程安全的。
例如使用ConcurrentHashMap来实现线程安全的计数 import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
class ThreadSafeCounter { private ConcurrentHashMapString, AtomicInteger counterMap new ConcurrentHashMap(); public void increment(String key) { AtomicInteger counter counterMap.get(key); if (counter null) { counter new AtomicInteger(0); AtomicInteger oldCounter counterMap.putIfAbsent(key, counter); if (oldCounter! null) { counter oldCounter; } } counter.incrementAndGet(); }}
5. 利用原子变量Atomic Variables
原理原子变量提供了一些原子操作如get、set、incrementAndGet等
这些操作在硬件层面保证了操作的原子性避免了多个线程同时操作一个变量时的不一致性。
示例在 Java 中java.util.concurrent.atomic包下有多种原子变量如AtomicInteger、AtomicLong、AtomicBoolean等。 import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter { private AtomicInteger count new AtomicInteger(0); public void increment() { count.incrementAndGet(); }} 主要有三点
原子性一个或者多个操作在 CPU 执行的过程中被中断可见性一个线程对共享变量的修改另外一个线程不能立刻看到有序性程序执行的顺序没有按照代码的先后顺序执行
原子性 count, i
一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断要么就都不执行。
Cpu切换时候会打断 读、改、写三步来进行即先从内存中读出count的值然后执行1操作再将结果写回内存中。 有序性动态编译器为了程序的整体性能会进行指令重排序 https://blog.csdn.net/a13545564067/article/details/105453345 多用户同时修改同一条数据并发修改数据?
当多用户同时修改同一条数据时可能会出现数据不一致的情况
以下是一些处理这种并发修改的方法
1. 数据库事务隔离级别
读未提交Read Uncommitted这是最低的隔离级别一个事务可以读取另一个事务未提 交的数据。在这种情况下
并发修改可能会导致脏读、不可重复读和幻读等问题
一般不建议用于对数据一致性要求较高的场景。
读已提交Read Committed一个事务只能读取另一个事务已经提交的数据。
这种隔离级别可以避免脏读但可能会出现不可重复读的情况即同一事务中多次读取同一数据可能得到不同的值。
可重复读Repeatable Read保证在同一事务中多次读取同一数据的结果是相同的
它可以解决不可重复读的问题但可能会出现幻读
在事务执行过程中另一个事务插入了满足条件的数据。
串行化Serializable这是最高的隔离级别事务是串行执行的
能完全避免脏读、不可重复读和幻读但会严重影响系统的并发性能。
2. 乐观锁
版本号机制在数据表中添加一个版本号version字段。
当用户读取数据时同时获取版本号。
在提交修改时检查版本号是否与读取时一致。
如果一致说明数据没有被其他用户修改更新数据并将版本号加 1
如果不一致说明数据已经被修改需要根据业务情况决定是重试还是抛出异常。
时间戳机制类似于版本号机制不过是使用时间戳来记录数据的最后修改时间。
提交修改时比较时间戳是否一致来判断数据是否被修改。
3. 悲观锁
数据库层面的锁例如在 SQL 语句中使用 FOR UPDATE针对 MySQL 的 InnoDB 引擎 来对查询的数据行进行加锁。当一个用户对数据行进行修改时其他用户 如果也想修改同一行数据就需要等待锁被释放。
这种方式可以有效防止数据被并发修改但会降低系统的并发性能。
应用程序层面的锁使用编程语言提供的锁机制如 Java 中的 synchronized 关键字或 Lock 接口对访问和修改数据的代码块进行加锁。
但这种锁是基于应用程序所在的服务器
在分布式环境下可能不太适用需要结合分布式锁来使用。 将任务并行执行完后再串行执行。
一种常见的方法是结合线程池和CountDownLatch。
首先创建线程池来执行并行任务对于每个并行任务在线程池中提交执行。
同时使用CountDownLatch来计数并行任务的数量。当每个并行任务完成时在任务内部调用CountDownLatch的countDown()方法。
主线程通过CountDownLatch的await()方法阻塞直到所有并行任务完成即计数器为 0。之后主线程就可以执行后续的串行任务了。 另一种方式是使用CompletableFuture在 Java 8 及以后版本中可以利用它的特性。
通过CompletableFuture的allOf()方法将多个并行的CompletableFuture任务组合在一起
这个组合后的任务会在所有并行任务都完成后才结束。
然后可以通过thenRun()或thenAccept()等方法在这个组合任务完成后执行串行任务实现先并行后串行的执行顺序。 import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ParallelThenSerial { public static void main(String[] args) throws InterruptedException { // 创建一个包含5个线程的线程池 ExecutorService executorService Executors.newFixedThreadPool(5); // 假设我们有10个并行任务创建CountDownLatch并初始化为10 CountDownLatch countDownLatch new CountDownLatch(10); // 提交10个并行任务到线程池 for (int i 0; i 10; i) { final int taskNumber i; executorService.submit(() - { try { System.out.println(执行并行任务: taskNumber); // 模拟任务执行时间 Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 每个任务完成后计数减1 countDownLatch.countDown(); } }); } // 主线程等待所有并行任务完成 countDownLatch.await(); System.out.println(所有并行任务已完成开始串行任务); // 在这里执行串行任务例如下面简单的打印 System.out.println(这是串行任务的一部分); // 关闭线程池 executorService.close(); }} 以下是使用CompletableFuture实现相同功能的示例代码 import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService
;import java.util.concurrent.Executors;
public class ParallelThenSerialWithCompletableFuture { public static void main(String[] args) { // 创建一个包含5个线程的线程池 ExecutorService executorService Executors.newFixedThreadPool(5); // 创建10个CompletableFuture代表并行任务 CompletableFuture?[] futures new CompletableFuture[10]; for (int i 0; i 10; i) { final int taskNumber i; CompletableFutureVoid future CompletableFuture.runAsync(() - { System.out.println(执行并行任务: taskNumber); // 模拟任务执行时间 try { Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }, executorService); futures[taskNumber] future; } // 使用allOf等待所有并行任务完成然后执行串行任务 CompletableFuture.allOf(futures).thenRun(() - { System.out.println(所有并行任务已完成开始串行任务); // 这里执行串行任务例如简单的打印 System.out.println(这是串行任务的一部分); }); // 关闭线程池注意这里关闭线程池的时机需要根据实际情况调整
因为CompletableFuture可能仍在使用线程池 executorService.shutdown(); }} 核心线程数设置
多线程核心线程数的设置没有固定标准需综合多方面因素考虑。
从 CPU 核心数角度看若任务是 CPU 密集型核心线程数一般设置为与 CPU 核心数相等或接近。例如一个四核 CPU核心线程数设为 4 左右这样能充分利用 CPU 资源避免线程切换带来的额外开销提高程序执行效率。 对于 I/O 密集型任务情况则不同。由于线程在等待 I/O 操作时会处于阻塞状态此时可设置较多的核心线程数。 还需考虑任务的性质和业务需求。如果任务执行时间较短且频繁提交过多的核心线程数可能导致线程频繁创建和销毁增加系统负担。此时可根据任务提交频率和处理速度合理设置核心线程数使线程资源得到充分利用。如果业务对响应时间要求极高可能需要适当增加核心线程数以确保任务能快速处理。 另外系统资源的限制也很关键。如果内存资源有限过多的线程会消耗大量内存导致内存不足进而影响系统性能。同时过高的线程数可能超出操作系统对线程的管理能力造成系统不稳定。所以在设置核心线程数时要兼顾系统的内存、CPU 使用率等资源状况。
如何判断当前任务是 CPU 密集型还是 I/O 密集型
判断一个任务是 CPU 密集型还是 I/O 密集型可以从多个方面入手。 首先从任务性质来看如果任务主要涉及大量的计算操作比如复杂的数学运算、数据加密解密、图形渲染等这些操作需要 CPU 进行持续的运算很少有等待 I/O 的时间那么该任务大概率是 CPU 密集型任务。相反如果任务的执行过程中需要频繁地与外部设备如磁盘、网络、数据库等进行数据交换像从数据库中频繁读取数据、网络文件传输等操作大部分时间都在等待 I/O 操作完成这类任务通常是 I/O 密集型任务。 其次可以通过性能分析工具来判断。在 Java 环境中有很多性能分析工具可供使用比如 JProfiler、VisualVM 等。以 JProfiler 为例它可以监测程序运行时的 CPU 使用率和 I/O 操作情况。如果在任务执行过程中CPU 使用率一直保持在较高水平接近或达到 100%而 I/O 操作很少那么很可能是 CPU 密集型任务反之如果 I/O 等待时间占总执行时间的比例较高就可能是 I/O 密集型任务。 还可以通过简单的代码测试来判断。在代码中可以分别记录任务执行过程中的 CPU 时间和 I/O 等待时间。例如在 Java 中可以使用 System.currentTimeMillis () 方法在任务开始和结束时分别记录时间同时使用操作系统提供的工具如 Linux 下的 top 命令查看 CPU 使用率。如果 CPU 时间占总时间的比重较大那么任务倾向于 CPU 密集型如果 I/O 等待时间占比较大则倾向于 I/O 密集型。 最后从业务逻辑角度分析某些业务场景本身就暗示了任务类型。
比如一个数据分析系统中的数据处理模块如果是对大量数据进行统计分析运算这就是 CPU 密集型业务
而如果是将处理好的数据存储到远程数据库或从远程数据库中频繁获取数据来更新本地缓存这就是 I/O 密集型业务。 IO密集 文件读写、DB读写、网格请求等。一般设置 线程数 2CPU核数 1
IO主要是网络传输、磁盘读取,不需要占用太多CPU
CPU密集计算型代码、Bitmap转换、Gson装换等。一般设置 线程数CPU核数 1 方法
压测(jmeter)找平衡点,设置多了出现线程上下文切换。采用理论然后压测
可以使用 Runtime.getRuntime().availableProcessor() 方法来获取 [ˈprəʊsesə(r)]
实际应用中 线程数 (线程CPU时间线程等待时间/ 线程CPU时间 ) * 核心数N
1因为线程由于偶尔的内存页失效或其他原因导致阻塞时这个额外的线程也能确保 CPU 的时钟周期不会被浪费从而保证 CPU 的利用率。
java中常见的六种线程池详解 - AnonyStar - 博客园
多线程面试题2021最新版-腾讯云开发者社区-腾讯云
https://zhuanlan.zhihu.com/p/458062389
①并发高、任务时间短-CPU1,减少上线文切换
②并发不高、任务执行时间长 IO密集型的任务- CPU核数*21 计算密集型的任务-CPU核数1
③并发高、业务执行时间长解决这种类型的任务关键不在于线程池而在于整体架构的设计看看这些业务里面某些数据是否能做缓存是第一步第二步增加服务器第三步......
新建T1、T2、T3三个线程 ,怎么保持顺序执行
现在有T1、T2、T3三个线程你怎样保证T2在T1执行完之后执行T3在T2执行完后执行 方法一使用 join 方法
原理join方法可以让一个线程等待另一个线程执行完毕后再执行。例如让 T2 线程等待 T1 线程执行完T3 线程等待 T2 线程执行完。示例代码class Main { public static void main(String[] args) { Thread T1 new Thread(() - { System.out.println(T1线程开始执行); // 模拟T1线程执行的任务 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T1线程执行完毕); }); Thread T2 new Thread(() - { try { T1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T2线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T2线程执行完毕); }); Thread T3 new Thread(() - { try { T2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T3线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T3线程执行完毕); }); T1.start(); T2.start(); T3.start(); }}
方法二使用单线程执行器ExecutorService
原理通过创建一个单线程的线程池将任务按顺序提交到线程池中
这样任务就会按提交的顺序依次执行。
示例代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;class Main { public static void main(String[] args) { ExecutorService executor Executors.newSingleThreadExecutor(); executor.submit(() - { System.out.println(T1线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T1线程执行完毕); }); executor.submit(() - { System.out.println(T2线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T2线程执行完毕); }); executor.submit(() - { System.out.println(T3线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T3线程执行完毕); }); executor.shutdown(); }}
使用 CountDownLatch
原理CountDownLatch是一个同步辅助类。它允许一个或多个线程等待其他线程完成操作。可以把CountDownLatch看作是一个计数器当创建CountDownLatch时需要指定一个计数值每个线程完成自己的任务后调用countDown方法来减少这个计数器的值。当计数器的值变为 0 时等待这个CountDownLatch的线程就可以继续执行。示例代码 import java.util.concurrent.CountDownLatch;class Main { public static void main(String[] args) { final CountDownLatch latch1 new CountDownLatch(1); final CountDownLatch latch2 new CountDownLatch(1); Thread T1 new Thread(() - { System.out.println(T1线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T1线程执行完毕); latch1.countDown(); }); Thread T2 new Thread(() - { try { latch1.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T2线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T2线程执行完毕); latch2.countDown(); }); Thread T3 new Thread(() - { try { latch2.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T3线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T3线程执行完毕); }); T1.start(); T2.start(); T3.start(); }} 使用 CyclicBarrier
原理CyclicBarrier是一个可循环使用的屏障。它会让一组线程到达一个屏障点时被阻塞直到最后一个线程到达屏障点这些线程才会一起继续执行。可以通过设置屏障点的数量参与的线程数量来控制线程的同步。示例代码
import java.util.concurrent.CyclicBarrier;class Main { public static void main(String[] args) { CyclicBarrier barrier1 new CyclicBarrier(2); CyclicBarrier barrier2 new CyclicBarrier(2); Thread T1 new Thread(() - { System.out.println(T1线程开始执行); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(T1线程执行完毕); try { barrier1.await(); } catch (Exception e) { e.printStackTrace(); } }); Thread T2 new Thread(() - { System.out.println(T2线程开始执行); try { barrier1.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println(T2线程执行完毕); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } try { barrier2.await(); } catch (Exception e) { e.printStackTrace(); } }); Thread T3 new Thread(() - { System.out.println(T3线程开始执行); try { barrier2.await(); } catch (Exception e) { e.printStackTrace(); } System.out.println(T3线程执行完毕); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); T1.start(); T2.start(); T3.start(); }} join方法的作用
答 Thread类中的join方法的主要作用就是同步它可以使得线程之间的并行执行变为串行执行。当我们调用某个线程的这个方法时这个方法会挂起调用线程直到被调用线程结束执行调用线程才会继续执行。
Join方法传参和不传参的区别
答join方法中如果传入参数则表示这样的意思如果A线程中掉用B线程的join(10)则表示A线程会等待B线程执行10毫秒10毫秒过后A、B线程并行执行。需要注意的是
jdk规定join(0)的意思不是A线程等待B线程0秒而是A线程等待B线程无限时间直到B线程执行完毕即join(0)等价于join()。 join方法将挂起调用线程的执行直到被调用的对象完成它的执行。
join方法的使用
join在线程里面意味着“插队”哪个线程调用join代表哪个线程插队先执行——但是插谁的队是有讲究了不是说你可以插到队头去做第一个吃螃蟹的人而是插到在当前运行线程的前面比如系统目前运行线程A在线程A里面调用了线程B.join方法则接下来线程B会抢先在线程A面前执行等到线程B全部执行完后才继续执行线程A。
join()方法的底层是利用wait()方法实现的
多线程中Thread的join方法_thread join-CSDN博客