目录
synchronized和reentrantlock及其应用场景
为了提高性能,处理器和编译器常常会对指令进行重排序,但是重排序要满足下面 2 个条件才能进行:
线程池中shutdown (),shutdownNow()这两个方法有什么作用?
使用多线程要注意哪些问题?
Java的线程安全在三个方面体现:
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,在Java中使用了atomic和synchronized这两个关键字来确保原子性;
- 可见性:一个线程对主内存的修改可以及时地被其他线程看到,在Java中使用了synchronized和volatile这两个关键字确保可见性;
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,在Java中使用了happens-before原则来确保有序性。
保证数据的一致性有哪些方案呢?
- 事务管理:使用数据库事务来确保一组数据库操作要么全部成功提交,要么全部失败回滚。通过ACID属性,数据库事务可以保证数据的一致性。
- 锁机制:使用锁来实现对共享资源的互斥访问。在 Java 中,可以使用 synchronized 关键字、ReentrantLock 或其他锁机制来控制并发访问。
- 版本控制:通过乐观锁的方式,在更新数据时记录数据的版本信息,从而避免同时对同一数据进行修改,进而保证数据的一致性。
线程的创建方式
继承java.lang.Thread类,重写其run()方法,创建该类的实例后,通过调用start()方法启动线程。
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码
}
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
实现Runnable接口,重写run()方法,然后将此Runnable对象作为参数传递给Thread类的构造器,创建Thread对象后调用其start()方法启动线程。在这种方式下,可以多个线程共享同一个目标对象,适合多个相同线程来处理同一份资源的情况
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的代码
}
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
实现Callable接口,需将它包装进一个FutureTask,因为Thread类的构造器只接受Runnable参数,而FutureTask实现了Runnable接口。
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程执行的代码,这里返回一个整型结果
return 1;
}
}
public static void main(String[] args) {
MyCallable task = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(task);
Thread t = new Thread(futureTask);
t.start();
try {
Integer result = futureTask.get(); // 获取线程执行结果
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
从Java 5开始引入的java.util.concurrent.ExecutorService和相关类提供了线程池的支持,这是一种更高效的线程管理方式,避免了频繁创建和销毁线程的开销。
class Task implements Runnable {
@Override
public void run() {
// 线程执行的代码
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10); // 创建固定大小的线程池
for (int i = 0; i < 100; i++) {
executor.submit(new Task()); // 提交任务到线程池执行
}
executor.shutdown(); // 关闭线程池
}
如何停止一个线程的运行
- 异常法停止:线程调用interrupt()方法后,在线程的run方法中判断当前对象的interrupted()状态,如果是中断状态则抛出异常,达到中断线程的效果。
- 在sleep中停止:先将线程sleep,然后调用interrupt标记中断状态,interrupt会将阻塞状态的线程中断。会抛出中断异常,达到停止线程的效果
- stop()停止:线程调用stop()方法会被暴力停止,但由于这可能会导致线程的不安全状态,通常不建议使用。
- 使用return停止线程:调用interrupt标记为中断状态后,在run方法中判断当前线程状态,如果为中断状态则return,能达到停止线程的效果。
调用 interrupt 是如何让线程抛出异常的?
中断状态的初始值为false,当一个线程被其它线程调用Thread.interrupt()方法中断时,会根据实际情况做出响应。
- 如果该线程正在执行低级别的可中断方法(如
Thread.sleep()、Thread.join()或Object.wait()),则会解除阻塞并抛出InterruptedException异常。 - 否则
Thread.interrupt()仅设置线程的中断状态,在该被中断的线程中稍后可通过轮询中断状态来决定是否要停止当前正在执行的任务。
Java线程的状态

| 线程状态 | 解释 |
|---|---|
| NEW | 尚未启动的线程状态,即线程创建,还未调用start方法 |
| READY \ RUNNABLE | 就绪状态(调用start,等待调度)+正在运行 |
| BLOCKED | 等待监视器锁时,陷入阻塞状态 |
| WAITING | 等待状态的线程正在等待另一线程执行特定的操作(如notify) |
| TIMED_WAITING | 具有指定等待时间的等待状态 |
| TERMINATED | 线程完成执行,终止状态 |
blocked和waiting有啥区别
触发条件:线程进入BLOCKED状态通常是因为试图获取一个对象的锁,但该锁已经被另一个线程持有。线程进入WAITING状态是因为它正在等待另一个线程执行某些操作,例如调用Object.wait()方法、Thread.join()方法或LockSupport.park()方法。
唤醒机制:当一个线程被阻塞等待锁时,一旦锁被释放,线程将有机会重新尝试获取锁。如果锁此时未被其他线程获取,那么线程可以从BLOCKED状态变为RUNNABLE状态。线程在WAITING状态中需要被显式唤醒。例如,如果线程调用了Object.wait(),那么它必须等待另一个线程调用同一对象上的Object.notify()或Object.notifyAll()方法才能被唤醒。如果线程执行了 LockSupport.park() 方法进入了waiting 状态,那么解锁的时候会执行LockSupport.unpark(Thread)
怎么保证多线程安全?
synchronized关键字:同步代码块或方法
volatile关键字:用于变量
Lock接口和ReentrantLock类: java.util.concurrent.locks.Lock接口提供了比synchronized更强大的锁定机制,ReentrantLock是一个实现该接口的例子,提供了更灵活的锁管理和更高的性能。
private final ReentrantLock lock = new ReentrantLock();
public void someMethod() {
lock.lock();
try {
/* ... */
} finally {
lock.unlock();
}
}
原子类:Java并发库(java.util.concurrent.atomic)提供了原子类,如AtomicInteger、AtomicLong等,这些类提供了原子操作
AtomicInteger counter = new AtomicInteger(0);
int newValue = counter.incrementAndGet();
线程局部变量:ThreadLocal类可以为每个线程提供独立的变量副本
ThreadLocal<Integer> threadLocalVar = new ThreadLocal<>();
threadLocalVar.set(10);
int value = threadLocalVar.get();
并发集合:使用java.util.concurrent包中的线程安全集合,如ConcurrentHashMap、ConcurrentLinkedQueue
JUC工具类: 使用java.util.concurrent包中的一些工具类可以用于控制线程间的同步和协作。例如:Semaphore和CyclicBarrier等。
Java中有哪些常用的锁
内置锁(synchronized):Java中的synchronized关键字是内置锁机制的基础,可以用于方法或代码块。 syncronized加锁时有无锁、偏向锁、轻量级锁和重量级锁几个级别。偏向锁用于当一个线程进入同步块时,如果没有任何其他线程竞争,就会使用偏向锁,以减少锁的开销。轻量级锁使用线程栈上的数据结构,避免了操作系统级别的锁。重量级锁则涉及操作系统级的互斥锁。
ReentrantLock:java.util.concurrent.locks.ReentrantLock是一个显式的锁类,提供了比synchronized更高级的功能,如可中断的锁等待、定时锁等待、公平锁选项等。ReentrantLock使用lock()和unlock()方法来获取和释放锁。
读写锁(ReadWriteLock):java.util.concurrent.locks.ReadWriteLock接口定义了一种锁,允许多个读取者同时访问共享资源,但只允许一个写入者。读写锁常用于读远多于写的情况。
乐观锁和悲观锁:悲观锁通常指在访问数据前就锁定资源。synchronized和ReentrantLock都是悲观锁的例子。乐观锁常不锁定资源,而是在更新数据时检查数据是否已被其他线程修改。使用版本号或时间戳来实现。
自旋锁:自旋锁是一种锁机制,线程在等待锁时会持续循环检查锁是否可用,而不是放弃CPU并阻塞。通常可以使用CAS来实现。
synchronized和reentrantlock及其应用场景
synchronized是Java提供的原子性内置锁,是 JVM 层面通过监视器实现的
使用synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,他依赖操作系统底层互斥锁实现。他的作用主要就是实现原子性操作和解决共享变量的内存可见性问题。
执行monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其他竞争锁的线程则会进入等待队列中。执行monitorexit指令时则会把计数器-1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。
深入到源码来说,synchronized实际上有两个队列waitSet和entryList。
- 当多个线程进入同步代码块时,首先进入entryList
- 有一个线程获取到monitor锁后,就赋值给当前线程,并且计数器+1
- 如果线程调用wait方法,将释放锁,当前线程置为null,计数器-1,同时进入waitSet等待被唤醒,调用notify或者notifyAll之后又会进入entryList竞争锁
- 如果线程执行完毕,同样释放锁,计数器-1,当前线程置为null
ReentrantLock 的底层实现主要依赖于 AbstractQueuedSynchronizer(AQS)这个抽象类。AQS 是一个提供了基本同步机制的框架,其中包括了队列、状态值等。
可中断性: ReentrantLock 实现了可中断性,这意味着线程在等待锁的过程中,可以被其他线程中断而提前结束等待。在底层,ReentrantLock 使用了与 LockSupport.park() 和 LockSupport.unpark() 相关的机制来实现可中断性。
设置超时时间: ReentrantLock等待一定时间后如果还未获得锁,则放弃锁的获取。这是通过内部的 tryAcquireNanos 方法来实现的。
公平锁和非公平锁: 在直接创建 ReentrantLock 对象时,默认情况下是非公平锁。公平锁是按照线程等待的顺序来获取锁,而非公平锁则允许多个线程在同一时刻竞争锁。公平锁可以通过在创建 ReentrantLock 时传入 true 来设置
多个条件变量: ReentrantLock 支持多个条件变量,每个条件变量可以与一个 ReentrantLock 关联。多个条件变量的实现依赖于 Condition 接口
可重入性: ReentrantLock 支持可重入性,即同一个线程可以多次获得同一把锁,而不会造成死锁。这是通过内部的 holdCount 计数来实现的。
synchronized适用于简单同步需求和不需要额外锁功能的场景,而ReentrantLock适用于需要更高级锁功能、性能优化或复杂同步逻辑的情况。
ReentrantLock提供了synchronized所不具备的高级功能,如公平锁、响应中断、定时锁、以及多条件变量。在高竞争的环境中,ReentrantLock可以提供比synchronized更好的性能,因为它提供更细粒度的控制。ReentrantLock及其配套的Condition对象可以提供更灵活的同步解决方案。
怎么理解可重入锁?
可重入锁是指同一个线程在获取了锁之后,可以再次重复获取该锁而不会造成死锁或其他问题。当一个线程持有锁时,如果再次尝试获取该锁,可以成功获取而不会被阻塞。每次获取锁时,计数器加1;每次释放锁时,计数器减1。只有当计数器减到0时,锁才会完全释放。
synchronized 支持重入吗?
synchronized是基于原子性的内部锁机制,是可重入的。synchronized底层是利用计算机系统mutex Lock实现的。每一个可重入锁都会关联一个线程ID和一个锁状态status。
syncronized锁升级的过程
- 无锁:这是没有开启偏向锁的时候的状态,在JDK1.6之后偏向锁的默认开启的,但是有一个偏向延迟,需要在JVM启动之后的多少秒之后才能开启,这个可以通过JVM参数进行设置,同时是否开启偏向锁也可以通过JVM参数设置。
- 偏向锁:这个是在偏向锁开启之后的锁的状态,如果还没有一个线程拿到这个锁的话,这个状态叫做匿名偏向,当一个线程拿到偏向锁的时候,下次想要竞争锁只需要拿线程ID跟MarkWord当中存储的线程ID进行比较,如果线程ID相同则直接获取锁
- 轻量级锁:在这个状态下线程主要是通过CAS操作实现的。将对象的MarkWord存储到线程的虚拟机栈上,然后通过CAS将对象的MarkWord的内容设置为指向Displaced Mark Word的指针,如果设置成功则获取锁。在线程出临界区的时候,也需要使用CAS,如果使用CAS替换成功则同步成功,如果失败表示有其他线程在获取锁,那么就需要在释放锁之后将被挂起的线程唤醒。
- 重量级锁:当有两个以上的线程获取锁的时候轻量级锁就会升级为重量级锁,因为CAS如果没有成功的话始终都在自旋,进行while循环操作,这是非常消耗CPU的,但是在升级为重量级锁之后,线程会被操作系统调度然后挂起,这可以节约CPU资源。

JVM对Synchornized的优化?
- 锁膨胀:synchronized 从无锁升级到偏向锁,再到轻量级锁,最后到重量级锁的过程,它叫做锁膨胀也叫做锁升级。JDK 1.6 之前,synchronized 是重量级锁,也就是说 synchronized 在释放和获取锁时都会从用户态转换成内核态,而转换的效率是比较低的。但有了锁膨胀机制之后,synchronized 的状态就多了无锁、偏向锁以及轻量级锁了,这时候在进行并发操作时,大部分的场景都不需要用户态到内核态的转换了,这样就大幅的提升了 synchronized 的性能。
- 锁消除:指的是在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。
- 锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
- 自适应自旋锁:指通过自身循环尝试获取锁,优点在于它避免一些线程的挂起和恢复操作,因为挂起线程和恢复线程都需要从用户态转入内核态,一定程度上避免线程挂起和恢复所造成的性能开销。
介绍一下AQS
AQS是一个用于构建锁、同步器、协作工具类的工具类。
AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,主要用CLH队列的变体,将暂时获取不到锁的线程加入到队列中。

AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改
AQS最核心的就是三大部分:
- 状态:state;比如在Semapore里,他表示剩余许可证的数量;在CountDownLatch里,它表示还需要倒数的数量;在ReentrantLock中,state用来表示“锁”的占有情况,包括可重入计数,当state的值为0的时候,标识该Lock不被任何线程所占有。
- 控制线程抢锁和配合的FIFO队列(双向链表);
- 期望协作工具类去实现的获取/释放等重要方法(重写)。在Semaphore中,获取就是acquire方法,作用是获取一个许可证; 而在CountDownLatch里面,获取就是await方法,作用是等待,直到倒数结束;在Semaphore中,释放就是release方法,作用是释放一个许可证; 在CountDownLatch里面,获取就是countDown方法,作用是将倒数的数减一;
Threadlocal作用,原理
- Thread类中,有个ThreadLocal.ThreadLocalMap 的成员变量。
- ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型对象值。
线程隔离、提供了独立的变量副本,这意味着线程之间不会相互影响
降低耦合度、减少参数的传递
性能优势:避免了线程间的同步开销
-
当调用
ThreadLocal的get()方法时,ThreadLocal会检查当前线程的ThreadLocalMap中是否有与之关联的值。 -
如果有,返回该值;
-
如果没有,会调用
initialValue()方法(如果重写了的话)来初始化该值,然后将其放入ThreadLocalMap中并返回。 -
当调用
set()方法时,ThreadLocal会将给定的值与当前线程关联起来,即在当前线程的ThreadLocalMap中存储一个键值对,键是ThreadLocal对象自身,值是传入的值。 -
当调用
remove()方法时,会从当前线程的ThreadLocalMap中移除与该ThreadLocal对象关联的条目。 -
当一个线程结束时,其
ThreadLocalMap也会随之销毁,但是ThreadLocal对象本身不会立即被垃圾回收,直到没有其他引用指向它为止。如果不显式调用remove()方法,或者线程结束时未正确清理ThreadLocal变量,可能会导致内存泄漏。
实现乐观锁
- CAS操作: CAS 是乐观锁的基础。Java 提供了 java.util.concurrent.atomic 包,包含各种原子变量类(如 AtomicInteger、AtomicLong),这些类使用 CAS 操作实现了线程安全的原子操作,可以用来实现乐观锁。
- 版本号控制:增加一个版本号字段记录数据更新时候的版本,每次更新时递增版本号。在更新数据时,同时比较版本号,若当前版本号和更新前获取的版本号一致,则更新成功,否则失败。
- 时间戳:使用时间戳记录数据的更新时间,在更新数据时,在比较时间戳。如果当前时间戳大于数据的时间戳,则说明数据已经被其他线程更新,更新失败。
CAS 缺点
- ABA问题:ABA的问题指的是在CAS更新的过程中,当读取到的值是A,然后准备赋值的时候仍然是A,但是实际上有可能A的值被改成了B,然后又被改回了A,这个CAS更新的漏洞就叫做ABA。只是ABA的问题大部分场景下都不影响并发的最终效果。Java中有AtomicStampedReference来解决这个问题,他加入了预期标志和更新后标志两个字段,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部相等的话才会更新。
- 循环时间长开销大:自旋CAS的方式如果长时间不成功,会给CPU带来很大的开销。
- 只能保证一个共享变量的原子操作:只对一个共享变量操作可以保证原子性,但是多个则不行,多个可以通过AtomicReference来处理或者使用锁synchronized实现。
voliatle关键字有什么作用
保证变量对所有线程的可见性。
禁止指令重排序优化,主要通过内存屏障来禁止特定类型的指令重排序
写-写(Write-Write)屏障:在对volatile变量执行写操作之前,会插入一个写屏障。这确保了在该变量写操作之前的所有普通写操作都已完成,防止了这些写操作被移到volatile写操作之后。
读-写(Read-Write)屏障:在对volatile变量执行读操作之后,会插入一个读屏障。它确保了对volatile变量的读操作之后的所有普通读操作都不会被提前到volatile读之前执行,保证了读取到的数据是最新的。
写-读(Write-Read)屏障:它发生在volatile写之后和volatile读之前。这个屏障确保了volatile写操作之前的所有内存操作(包括写操作)都不会被重排序到volatile读之后,同时也确保了volatile读操作之后的所有内存操作(包括读操作)都不会被重排序到volatile写之前。
指令重排序
为了提高性能,处理器和编译器常常会对指令进行重排序,但是重排序要满足下面 2 个条件才能进行:
- 在单线程环境下不能改变程序运行的结果
- 存在数据依赖关系的不允许重排序。
volatile可以保证线程安全吗?
volatile关键字可以保证可见性,但不能保证原子性,因此不能完全保证线程安全。
什么是公平锁和非公平锁?
- 公平锁: 指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点在于各个线程公平,每个线程等待一段时间后都有执行的机会,而它的缺点就在于整体执行速度更慢,吞吐量更小。
- 非公平锁: 多个线程加锁时直接尝试获取锁,能抢到锁到直接占有锁,抢不到才会到等待队列的队尾等待。非公平锁的优势就在于整体执行速度更快,吞吐量更大,避免了线程休眠和恢复的操作,但同时也可能产生线程饥饿问题
ReentrantLock是怎么实现公平锁与非公平锁的?
在tryAcquire方法中,公平锁与非公平锁的 lock() 方法唯一的区别就在于公平锁在获取锁时多了一个限制条件:hasQueuedPredecessors() 为 false,这个方法就是判断在等待队列中是否已经有线程在排队了。
公平锁和非公平锁的核心区别,如果是公平锁,那么一旦已经有线程在排队了,当前线程就不再尝试获取锁;对于非公平锁而言,无论是否已经有线程在排队,都会尝试获取一下锁,获取不到再去排队。这里有一个特例需要我们注意,针对 tryLock() 方法,它不遵守设定的公平原则。调用的就是 nonfairTryAcquire(),表明了是不公平的,和锁本身是否是公平锁无关。
线程池的工作原理

线程池的参数
- corePoolSize:线程池核心线程数量。默认情况下,线程池中线程的数量如果 <= corePoolSize,那么即使这些线程处于空闲状态,那也不会被销毁。
- maximumPoolSize:线程池中最多可容纳的线程数量。当一个新任务交给线程池,如果此时线程池中有空闲的线程,就会直接执行,如果没有空闲的线程且当前线程池的线程数量小于corePoolSize,就会创建新的线程来执行任务,否则就会将该任务加入到阻塞队列中,如果阻塞队列满了,就会创建一个新线程,从阻塞队列头部取出一个任务来执行,并将新任务加入到阻塞队列末尾。如果当前线程池中线程的数量等于maximumPoolSize,就不会创建新线程,就会去执行拒绝策略。
- keepAliveTime:当线程池中线程的数量大于corePoolSize,并且某个线程的空闲时间超过了keepAliveTime,那么这个线程就会被销毁。
- unit:就是keepAliveTime时间的单位。
- workQueue:工作队列。当没有空闲的线程执行新任务时,该任务就会被放入工作队列中,等待执行。
- threadFactory:线程工厂。可以用来给线程取名字等等
- handler:拒绝策略。当一个新任务交给线程池,如果此时线程池中有空闲的线程,就会直接执行,如果没有空闲的线程,就会将该任务加入到阻塞队列中,如果阻塞队列满了,就会创建一个新线程,从阻塞队列头部取出一个任务来执行,并将新任务加入到阻塞队列末尾。如果当前线程池中线程的数量等于maximumPoolSize,就不会创建新线程,就会去执行拒绝策略
线程池工作队列满了有哪些拒接策略
四种预置的拒绝策略:
- CallerRunsPolicy,使用线程池的调用者所在的线程去执行被拒绝的任务,除非线程池被停止或者线程池的任务队列已有空缺。
- AbortPolicy(默认策略),抛出
RejectedExecutionException,直接拒绝处理新任务。 - DiscardPolicy,默默地丢弃被拒绝的任务,不抛出异常也不通知调用者。
- DiscardOldestPolicy,抛弃最老的任务,然后执行该任务。
有线程池参数设置的经验吗?
- CPU密集型:corePoolSize = CPU核数 + 1
- IO密集型:corePoolSize = CPU核数 * 2
核心线程数设置为0可不可以?
可以,当核心线程数为0的时候,会创建一个非核心线程进行执行。
线程池种类
ScheduledThreadPool:可以设置定期的执行任务,它支持定时或周期性执行任务
FixedThreadPool:它的核心线程数和最大线程数是一样的,可以把它看作是固定线程数的线程池
CachedThreadPool:可以称作可缓存线程池,它的特点在于线程数是几乎可以无限增加的(实际最大可以达到 Integer.MAX_VALUE)
SingleThreadExecutor:它会使用唯一的线程去执行任务,原理和 FixedThreadPool 是一样的,只不过这里线程只有一个
SingleThreadScheduledExecutor:它实际和 ScheduledThreadPool 线程池非常相似,它只是 ScheduledThreadPool 的一个特例,内部只有一个线程。
线程池中shutdown (),shutdownNow()这两个方法有什么作用?
- shutdown使用了以后会置状态为SHUTDOWN,正在执行的任务会继续执行下去,没有被执行的则中断。此时,则不能再往线程池中添加任何任务,否则将会抛出 RejectedExecutionException 异常
- shutdownNow 会置状态为STOP,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。 它试图终止线程的方法是通过调用 Thread.interrupt() 方法来实现的,但是这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
提交给线程池中的任务可以被撤回吗?
可以,当向线程池提交任务时,会得到一个Future对象。这个Future对象提供了几种方法来管理任务的执行,包括取消任务。取消任务的主要方法是Future接口中的cancel(boolean mayInterruptIfRunning)方法。指示是否允许中断正在执行的任务。
多线程打印奇偶数
public class PrintOE {
private final Object lock = new Object();
private int count = 1;
public void printOdd() throws InterruptedException {
while(count< 10){
synchronized (lock){
if(count%2 == 0){
lock.wait();
}
System.out.println("odd"+ count);
count++;
lock.notify();
}
}
}
public void printEven() throws InterruptedException {
while(count< 10){
synchronized (lock){
if(count%2 != 0){
lock.wait();
}
System.out.println("even"+ count);
count++;
lock.notify();
}
}
}
public static void main(String[] args) throws InterruptedException {
PrintOE p = new PrintOE();
Thread t1 = new Thread(() -> {
try {
p.printOdd();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread t2 = new Thread(() -> {
try {
p.printEven();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
478

被折叠的 条评论
为什么被折叠?



