并发是指什么
并发是程序同时执行多个计算的能力。 这可以通过将计算分布在机器的可用CPU内核上(多核CPU支持)或甚至通过同一网络内的不同机器来实现(后台分布式)。
进程和线程有什么区别
进程是操作系统提供的执行环境,具有自己的一组专用资源(例如内存,打开的文件等)。 线程相对进程而言,线程存活在一个进程中,并与进程的其他线程共享其资源(内存,打开的文件等)。 在不同线程之间共享资源的能力使线程更适合于对性能要求的任务。
在Java中,什么是进程和线程
在Java中,进程对应于正在运行的Java虚拟机(JVM),而线程驻留在JVM中,并且可以在运行时动态地由Java应用程序创建和停止。
什么是 scheduler(调度程序)
scheduler是一种调度算法的实现,它管理进程和线程对处理器或某些I / O通道等有限资源的访问。 大多数调度算法的目标是为可用进程/线程提供某种负载均衡,以确保每个进程/线程获得适当的时间片来专门访问请求的资源。
一个Java程序至少有多少个线程
每个Java程序都在主线程中执行; 因此每个Java应用程序至少有一个线程。
Java应用程序如何访问当前线程
当前线程可以通过调用JDK中提供的类java.lang.Thread的静态方法currentThread()来访问:
public class MainThread {
public static void main(String[] args) {
long id = Thread.currentThread().getId();
String name = Thread.currentThread().getName();
...
}
}
复制代码
每个Java线程都有哪些属性
每个Java线程都有以下属性:
- JVM中唯一的long类型标识符
- String类型的名称
- int类型的优先级
- 类型为java.lang.Thread.State的状态
- 线程所属的线程组
线程组的目的是什么
每个线程都属于一组线程。 JDK类java.lang.ThreadGroup提供了一些方法来处理整组线程。 例如,通过这些方法,可以中断线程组的所有线程或设置其最大优先级。
线程可以拥有哪些状态以及每个状态的含义
- NEW:尚未启动的线程处于此状态。
- RUNNABLE:在Java虚拟机中执行的线程处于此状态。
- BLOCKED:阻塞等待监视器锁定的线程处于此状态。
- WAITING:无限期地等待另一个线程执行特定动作的线程处于这种状态。
- TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED:已退出的线程处于此状态。
我们如何设置线程的优先级
线程的优先级通过setPriority(int)方法设置。 要将优先级设置为最大值,我们使用常量Thread.MAX_PRIORITY。要将其设置为最小值,我们使用常量Thread.MIN_PRIORITY,因为这些值在不同的JVM实现之间可能会有所不同。
Java中如何创建一个线程
基本上,有两种方法可以在Java中创建线程。 第一个是编写一个扩展JDK类java.lang.Thread并调用其方法start()的类:
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("Executing thread "+Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread("myThread");
myThread.start();
}
}
复制代码
第二种方法是实现接口java.lang.Runnable并将此实现作为参数传递给java.lang.Thread的构造函数:
public class MyRunnable implements Runnable {
public void run() {
System.out.println("Executing thread "+Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
Thread myThread = new Thread(new MyRunnable(), "myRunnable");
myThread.start();
}
}
复制代码
我们如何停止Java中的线程
public class StopThread {
public static void main(String[] arg) throws InterruptedException {
MyStopThread myStopThread = new MyStopThread();
myStopThread.start();
Thread.sleep(1000 * 5);
myStopThread.stopThread();
}
private static class MyStopThread extends Thread {
private volatile Thread stopIndicator;
public void start() {
stopIndicator = new Thread(this);
stopIndicator.start();
}
public void stopThread() {
stopIndicator = null;
}
@Override
public void run() {
Thread thisThread = Thread.currentThread();
while (thisThread == stopIndicator) {
try {
System.out.println("wait...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
复制代码
输出内容:
为什么一个线程不能通过调用stop()方法来停止
不应该使用java.lang.Thread的废弃方法stop()停止线程,因为此方法的调用会导致线程解锁其已获取的所有监视器。 如果任何一个由释放锁保护的对象处于不一致状态,则此状态对所有其他线程都可见。 当其他线程处理这个不一致的对象时,这可能导致不可测的行为。
是否有可能启动一个线程两次
不能,在通过调用start()方法启动线程后,第二次调用start()将抛出IllegalT hreadStateException异常。
以下代码的输出是什么
public class MultiThreading {
private static class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread myThread = new MyThread("myThread");
myThread.run();
}
}
}
复制代码
上面的代码输出“main”而不是“myThread”。 从main()方法的第二行可以看出,我们错误地调用run()方法而不是start()。 因此,没有新的线程启动,run()方法依旧在主线程中执行。
什么是守护线程
当所有用户线程(与守护线程相对)都终止时,JVM才会停止。当JVM决定是否停止时,不会考虑到守护线程的执行状态。 因此,守护线程可以用于实现监视功能,只要所有用户线程都停止了,守护线程就会被JVM停止:
public class Example {
private static class MyDaemonThread extends Thread {
public MyDaemonThread() {
setDaemon(true);
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyDaemonThread();
thread.start();
}
}
复制代码
上面的示例应用程序将立即终止,即使守护程序线程仍在其while循环中运行。
是否有可能在普通用户线程启动后将其转换为守护线程
用户线程一旦启动就无法转换为守护线程。 在已经运行的线程实例上调用thread.setDaemon( true) 方法会导致IllegalThreadStateException异常。
busy waiting 告诉我们什么
busy waiting 意味着通过执行一些主动计算来等待事件的实现,这些计算使线程/进程占用处理器,尽管它已经可以被调度程序从中移除。 busy waiting 的一个例子是在循环内花费等待时间,该循环一次又一次地确定当前时间,直到达到某个时间点:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
long millisToStop = System.currentTimeMillis() + 5000;
long currentTimeMillis = System.currentTimeMillis();
while (millisToStop > currentTimeMillis) {
currentTimeMillis = System.currentTimeMillis();
}
}
});
复制代码
我们如何防止 busy waiting
防止 busy waiting 的一种方法是将当前线程休眠一段给定的时间。 这可以通过调用方法java.lang.Thread.sleep(long) 来完成,将毫秒数作为参数休眠。
我们可以使用Thread.sleep()进行实时处理吗
传递给Thread.sleep(long) 调用的毫秒数只是scheduler指示当前线程不需要执行多长时间。 根据实际的实现情况,scheduler可能会让线程再提前几毫秒执行一次。 因此,Thread.sleep()的调用不应该用于实时处理。
如何在使用Thread.sleep()之前将线程唤醒
java.lang.Thread的interrupt()方法中断正在睡眠的线程。 已通过调用Thread.sleep() 进入睡眠状态的中断线程被InterruptedException唤醒:
public class InterruptThread implements Runnable {
public static void main(String[] arg) throws InterruptedException {
Thread myThread = new Thread(new InterruptThread(), "myThread");
myThread.start();
System.out.println("[" + Thread.currentThread().getName() + "] Sleeping in main ← thread for 5s...");
Thread.sleep(5000);
System.out.println("[" + Thread.currentThread().getName() + "] Interrupting ← myThread");
myThread.interrupt();
}
@Override
public void run() {
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("[" + Thread.currentThread().getName() + "] ← Interrupted by exception!");
}
}
}
复制代码
输出内容:
[main] Sleeping in main ← thread for 5s...
[main] Interrupting ← myThread
[myThread] ← Interrupted by exception!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.zpw.test.thread.InterruptThread.run(InterruptThread.java:23)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0
复制代码
一个线程如何查询它是否被中断
如果线程不在像Thread.sleep() 这样会抛出InterruptedException的方法内,线程可以通过调用从java.lang.Thread继承的静态方法Thread.interrupted() 或方法isInterrupted() 来查询它是否已被中断 。
应该如何处理InterruptedException
像sleep()和join()这样的方法会抛出一个InterruptedException来告诉调用者另一个线程已经中断了这个线程。 在大多数情况下,这是为了告诉当前线程停止当前的计算并以异常的方式完成它们。 因此,通过捕获异常并仅将其记录到控制台或某些日志文件来忽略异常通常不是处理这种异常的适当方式。 这个异常的问题是,Runnable接口的run()方法不允许run()抛出任何异常。 所以重新抛出它并没有意义。 这意味着run()的实现必须自己处理这个检查的异常,这通常会导致它被捕获并被忽略的事实。
在启动一个子线程之后,我们如何在父线程中等待子线程的终止
等待线程终止是通过调用线程实例变量上的join()方法来完成的:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
}
});
thread.start();
thread.join();
复制代码
以下程序的输出是什么
public class MyThreads {
private static class MyDaemonThread extends Thread {
public MyDaemonThread() {
setDaemon(true);
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyDaemonThread();
thread.start();
thread.join();
System.out.println(thread.isAlive());
}
}
复制代码
上述代码的输出是“false”。 尽管MyDaemonThread的实例是一个守护线程,但调用join()会导致主线程等待,直到守护线程的执行完成。 因此,在线程实例上调用isAlive()会发现守护线程不再运行。
当未捕获的异常离开run()方法时会发生什么
可能会发生一个未经检查的异常从run() 方法中逃脱。 在这种情况下,线程被Java虚拟机停止。 可以通过注册一个实现接口UncaughtExceptionHandler的实例作为异常处理程序来捕获此异常。 这可以通过调用静态方法Thread.setDefaultUncaughtExceptionHandler(Thread.Unc aughtExceptionHandler)来完成,该方法告诉JVM在线程本身没有注册特定处理程序的情况下使用提供的处理程序,或通过在线程实例本身调用setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler)。
什么是 shutdown hook
shutdown hook 是在JVM关闭时执行的线程。 它可以通过在Runtime实例上调用addShutdownHook(Runnable)来注册:
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
}
});
复制代码
关于 synchronized 关键字的用途
当你必须实现对某个资源的独占访问时,如某些静态值或某个文件引用,与独占资源一起工作的代码可以包含一个 synchronized 块:
synchronized (SynchronizedCounter.class) {
counter++;
}
复制代码
同步方法获得谁的内在锁
synchronized方法获取该方法对象的内部锁并在方法返回时释放它。 即使该方法抛出异常,内部锁也被释放。 因此,一个同步方法等于以下代码:
public void method() {
synchronized(this) {
...
}
}
复制代码
构造函数是否可以同步
构造函数不能同步。 导致语法错误的原因是只有构造线程才能访问正在构建的对象。
基本类型变量可以用做内部锁吗
基本类型变量不能用做内部锁。
内在锁可重入吗
可以。内部锁可以一次又一次地被相同的线程访问。 否则,获取锁的代码将不得不注意,它不会意外地尝试获取它已获取的锁。
通过原子操作了解什么
原子操作要么完全执行,要么根本不执行。
语句c++是原子性的吗
不是,整数变量的增量由多个操作组成。 首先,我们必须加载c的当前值,然后增加它,然后最后将新值存回。 执行此增量的当前线程可能会在这三个步骤中的任何一个之间中断,因此此操作不是原子操作。
Java中的原子操作是什么
Java语言提供了一些基本的操作,因此可用于确保并发线程始终看到相同的值:
- 以引用变量和原始变量(long和double除外)的读取和写入操作
- 对所有声明为volatile的变量进行读写操作
以下实现是线程安全的
public class DoubleCheckedSingleton {
private DoubleCheckedSingleton instance = null;
public DoubleCheckedSingleton getInstance() {
if(instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if(instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
复制代码
上面的代码不是线程安全的。 尽管它在同步块内再次检查实例的值(出于性能原因),但JIT编译器可以重新排列字节码,以便在构造函数完成其执行之前设置对实例的引用。 这意味着getInstance()方法返回一个可能尚未完全初始化的对象。 为了让代码是线程安全的,关键字volatile可以在Java 5以后用于实例变量。 标记为volatile的变量只有在对象的构造函数完成完成后才会被其他线程看到。
deadlock 是什么
死锁是两个(或更多)线程在等待另一个线程释放已锁定的资源的情况,而线程本身锁定了另一个线程正在等待的资源:线程1:锁定资源A ,等待资源B;线程2:锁定资源B,等待资源A。
死锁发生的前提
通常可以确定以下死锁要求:
- 相互排斥:有一种资源只能在任何时间点由一个线程访问。
- 资源保持:当锁定一个资源时,该线程试图获取另一个独占资源上的另一个锁。
- 不抢占:没有任何机制,如果某个线程在特定时间段内持有锁,则释放该资源。
- 循环等待:在运行时期间会出现一个群集,其中两个(或更多)线程互相都在另一个线程上等待以释放它已锁定的资源。
是否有可能防止死锁
为了防止死锁,必须消除一个(或多个)死锁要求:
- 相互排斥:在某些情况下,可以通过使用乐观锁定来防止相互排斥。
- 资源保存:当线程无法获得所有排他锁时,它可能会释放其所有排他锁。
- 不抢占:对排他锁使用超时在给定时间后释放锁。
- 循环等待:当所有线程以相同顺序获得所有排它锁时,不会发生循环等待。
是否有可能实现死锁检测
当所有独占锁都被监视并建模为定向图时,死锁检测系统可以搜索两个线程,每个线程都在另一个线程上等待以释放它已锁定的资源。 等待的线程然后可以被某种异常强制释放另一个线程正在等待的锁。
什么是活锁
活锁是两个或多个线程通过响应由另一个线程引起的操作而彼此阻塞的情况。 与死锁情况相反,两个或更多线程在一个特定状态下等待,参与活锁的线程以阻止正常工作进度的方式更改其状态。 一个例子就是两个线程试图获得两个锁的情况,但是当他们无法获得第二个锁时释放第一个获得的锁。 现在可能发生两个线程同时尝试获取第一个线程。 由于只有一个线程成功,第二个线程可能成功获取第二个锁。 现在两个线程都拥有两个不同的锁,但由于两者都想拥有这两个锁,它们释放它们的锁并从头开始重试。 这种情况可能会一次又一次地发生。
我们通过线程饥饿了解什么
具有较低优先级的线程比具有较高优先级的线程获得较少的执行时间。 当优先级较低的线程执行长期持久的计算时,可能会发生这些线程没有足够的时间来及时完成其计算。 他们似乎“饿死”,因为具有更高优先级的线程窃取他们的计算时间。
同步块可能导致线程饥饿
没有定义线程可以进入同步块的顺序。 所以理论上可能发生的情况是,如果许多线程正在等待同步块的入口,则某些线程必须等待比其他线程更长的时间。 因此他们没有足够的计算时间来及时完成工作。
术语竞赛条件来理解什么
竞争条件描述了一些现象,其中一些多线程实现的结果取决于参与线程的确切时间行为。 在大多数情况下,不希望出现这种行为,因此术语竞争条件也意味着由于缺少线程同步而导致的错误会导致不同的结果。 一个竞争条件的简单例子是由两个并发线程增加一个整数变量。 由于该操作由多个单一操作和原子操作组成,因此可能会发生这两个线程读取并递增相同的值。 在这个并发增量之后,整数变量的数量不会增加2,而只会增加1。
公平锁是什么
当选择下一个将屏障传递给某个独占资源的线程时,公平锁会将线程的等待时间考虑在内。 Java SDK提供了一个公平锁的示例实现:java.util.concurrent。locks.ReentrantLock。 通过将构造函数使用布尔标志设置为true,则ReentrantLock授予访问最长等待线程的权限。
每个对象从java.lang.Object继承的哪两种方法可用于实现简单的生产者/消费者方案
当工作线程完成当前任务并且新任务的队列为空时,它可以通过获取队列对象的内部锁并通过调用方法wait()来释放处理器。 该线程将被某个生产者线程唤醒,该线程已将新任务放入队列中,并再次获取队列对象上的相同内部锁并调用notify()。
notify()和notifyAll()有什么区别
这两种方法都用来唤醒一个或多个通过调用wait()使自己进入睡眠状态的线程。 虽然notify()只唤醒其中一个等待的线程,notifyAll()唤醒所有等待的线程。
如何通过调用notify()来确定哪个线程被唤醒
如果有多个线程正在等待,则不会指定哪个线程将通过调用notify()来唤醒。 因此,代码不应该依赖任何具体的JVM实现。
以下代码是从某个队列实现中检索整数值的正确方法吗
public Integer getNextInt() {
Integer retVal = null;
synchronized (queue) {
try {
while (queue.isEmpty()) {
queue.wait();
}
} catch (InterruptedException e) {
}
}
synchronized (queue) {
retVal = queue.poll();
if (retVal == null) {
System.err.println("retVal is null");
throw new IllegalStateException();
}
}
return retVal;
}
复制代码
尽管上面的代码使用队列作为对象监视器,但它在多线程环境中的行为不正确。 原因是它有两个独立的同步块。 当另一个调用notifyAll()的线程在第6行唤醒两个线程时,两个线程都会相继输入第二个同步块。它的第二个块现在只有一个新的值,因此第二个线程将轮询一个空的队列并将null作为返回值。
是否有可能检查某个线程是否对某个给定对象持有监视器锁
类java.lang.Thread提供了返回true的静态方法Thread.holdsLock(Object) ,当且仅当当前线程持有作为方法调用的参数给定的对象上的锁时才返回true。
Thread.yield()方法的作用
对静态方法Thread.yield() 的调用为调度器提供了一条提示,即当前线程愿意释放处理器。 调度器可以自由地忽略这个提示。 由于没有定义哪个线程在调用Thread.yield()后会得到处理器,因此甚至可能发生当前线程变为要执行的“下一个”线程。
将对象实例从一个线程传递到另一个线程时需要考虑什么
在线程之间传递对象时,必须注意这些对象不能同时由两个线程操纵。 一个例子是一个Map实现,其键/值对由两个并发线程修改。 为了避免并发修改的问题,你可以设计一个对象为不可变的。
为了实现一个不可变的类,你必须遵循哪些规则
- 所有字段应该是final的和private。
- 不应该有setter方法。
- 为了防止子类违反不可变性原则,类本身应该被宣布为final。
- 如果字段不是原始类型,而是对另一个对象的引用:
- 不应该有一个getter方法将参考直接暴露给调用者。
- 不要改变引用的对象(或者至少改变这些引用对对象的客户端不可见)。
类java.lang.ThreadLocal的目的是什么
由于内存在不同的线程之间共享,ThreadLocal提供了一种为每个线程单独存储和检索值的方法。 ThreadLocal的实现存储并为每个线程独立检索值,以便当线程A存储值A1并且线程B将值B1存储在同一个ThreadLocal实例中时,线程A稍后从此ThreadLocal实例中检索值A1,并且线程B检索值B1。
java.lang.ThreadLocal有哪些可能的用例
ThreadLocal的实例可用于在整个应用程序中传输信息,而无需将它由方法传递给方法。 例子就是在ThreadLocal的一个实例中传输安全/登录信息,这样每个方法都可以访问它。 另一个用例是传输事务信息或一般对象,这些对象应该可以在所有方法中访问,而无需将它们从方法传递到方法。
是否可以通过使用多线程来提高应用程序的性能
如果我们有多个CPU内核可用,如果可以通过可用的CPU内核对计算进行并行化,则可以通过多线程来提高应用程序的性能。 一个例子是缩放存储在本地目录结构中的所有图像的应用程序。 生产者/消费者实现可以使用单个线程来扫描目录结构以及执行实际缩放操作的一群工作线程,而不是一个接一个地遍历所有映像。 另一个例子是一个映射网页的应用程序。 生产者线程可以解析第一个HTML页面,并将它找到的链接发布到队列中,而不是一个接一个加载HTML页面。 工作线程监视队列并加载解析器找到的网页。 当工作线程等待页面完全加载时,其他线程可以使用CPU来解析已加载的页面并发出新的请求。
术语可伸缩性代表什么
可伸缩性意味着程序通过增加更多资源来提高性能的能力。
是否有可能通过使用多个处理器来计算应用程序的理论最大加速度
Amdahl’s law provides a formula to compute the theoretical maximum speed up by providing multiple processors to an applica- tion.ThetheoreticalspeedupiscomputedbyS(n) =1 /(B + (1-B)/n)wherendenotesthenumberofprocessorsandB the fraction of the program that cannot be executed in parallel. When n converges against infinity, the term (1-B)/n converges against zero. Hence the formula can be reduced in this special case to 1/B. As we can see, the theoretical maximum speedup behaves reciprocal to the fraction that has to be executed serially. This means the lower this fraction is, the more theoretical speedup can be achieved.
锁争夺
当两个或两个以上的线程竞争锁时,会发生锁争夺。 调度器必须决定它是否允许线程等待休眠,并执行上下文切换以让另一个线程占用CPU,或让等待线程 busy-waiting效率更高。 两种方式都将空闲时间引入劣质线程。
减少锁争夺
在某些情况下,通过应用以下技术之一可以减少锁争用:
- 锁的范围缩小。
- 获取特定锁的次数减少(锁分割)。
- 使用硬件支持的乐观锁操作而不是同步。
- 尽可能避免同步。
- 避免使用对象池。
下面的代码可以应用哪种技术来减少锁争夺
synchronized (map) {
UUID randomUUID = UUID.randomUUID();
Integer value = Integer.valueOf(42);
String key = randomUUID.toString();
map.put(key, value);
}
复制代码
上面的代码执行随机UUID的计算以及将文字42转换为同步块内的Integer对象,尽管这两行代码对当前线程是本地的并且不影响其他线程。 因此可以将它们移出同步块:
UUID randomUUID = UUID.randomUUID();
Integer value = Integer.valueOf(42);
String key = randomUUID.toString();
synchronized (map) {
map.put(key, value);
}
复制代码
锁分裂技术
当使用一个锁来同步对相同应用程序的不同方面的访问时,锁分割可能是减少锁争用的一种方式。 假设我们有一个类来实现我们应用程序的一些统计数据的计算。 该类的第一个版本在每个方法签名中使用关键字synchronized,以便在多个并发线程损坏之前保护内部状态。 这也意味着每个方法调用都可能导致锁争用,因为其他线程可能会尝试同时获取相同的锁。 但是也可以将对象实例上的锁分为每种方法中每种统计数据的几个较小的锁。 因此,尝试递增统计数据D1的线程T1在线程T2同时更新数据D2的同时不必等待锁。
SDK类ReadWriteLock使用了哪种减少锁争用的技术
SDK类ReadWriteLock使用这样一个技术,即如果没有其他线程尝试更新值时,并发线程不需要获取锁就可以读取值。 这是通过一对锁实现的,一个用于只读操作,另一个用于写入操作。 虽然只读锁可以通过多个线程获得,但是实现保证了一旦写入锁被释放,所有读操作看到更新的值。
锁条纹技术
在锁分割中,我们为应用程序的不同方面引入了不同的锁,与锁分割相反,锁条纹使用多个锁来保护相同数据结构的不同部分。 此技术的一个示例是JDK的java.util.concurrent包中的类ConcurrentHashMap。 Map实现使用内部不同的桶来存储其值。 存储桶由值的键选择。 ConcurrentHashMap现在使用不同的锁来保护不同的散列桶。 因此,一个尝试访问第一个哈希桶的线程可以获取该桶的锁,而另一个线程可以同时访问第二个桶。 与HashMap的同步版本相比,此技术可以在不同线程在不同存储桶上工作时提高性能。
CAS操作
CAS代表比较和交换,意味着处理器提供了一个单独的指令,只有当提供的值等于当前值时才更新寄存器的值。 CAS操作可以用来避免同步,因为线程可以通过向CAS操作提供当前值和新值来尝试更新值。 如果另一个线程同时更新了该值,则该线程的值不等于当前值,并且更新操作失败。 线程然后读取新值并再次尝试。 这种方式通过乐观的自旋等待交换了必要的同步。
哪些Java类使用CAS操作
包java.util.concurrent.atomic中的SDK类(如AtomicInteger或AtomicBoolean)在内部使用CAS操作来实现并发增量。
public class CounterAtomic {
private AtomicLong counter = new AtomicLong();
public void increment() {
counter.incrementAndGet();
}
public long get() {
return counter.get();
}
}
复制代码
提供一个例子说明为什么单线程应用程序的性能改进会导致多线程应用程序的性能下降
这种优化的一个突出例子是List实现,它将元素的数量保存为一个单独的变量。 这可以提高单线程应用程序的性能,因为size()操作不必遍历所有元素,但可以直接返回当前元素数。 在多线程应用程序中,附加计数器必须由锁保护,因为多个并发线程可能会将元素插入到列表中。 当列表的更新数量多于size()操作的调用时,额外锁可能会降低性能。
对象池总是对多线程应用程序的性能改进
避免建新对象的对象池可以提高单线程应用程序的性能,因为通过向池中请求新对象来交换对象创建成本。 在多线程应用程序中,这样的对象池必须具有对池的同步访问权限,并且锁争夺的额外成本可能会超过额外构建和垃圾收集新对象所节省的成本。 因此,对象池并不总是可以提高多线程应用程序的整体性能。
接口Executor和ExecutorServices之间的关系
接口Executor只定义了一个方法:execute(Runnable)。 此接口的实现将不得不在未来的某个时间执行给定的Runnable实例。 ExecutorService接口是Executor接口的扩展,提供了关闭底层实现的其他方法,以等待终止所有提交的任务,并允许提交Callable实例。
将新任务submit()给ExecutorService实例(其队列已满)时会发生什么情况
由submit()的方法签名指示,ExecutorService实现应该抛出Rejected ExecutionException异常。
ScheduledExecutorService
接口ScheduledExecutorService扩展了接口ExecutorService,并添加了允许将新任务提交给应该在给定时间点执行的底层实现的方法。 有两种方法可以调度一次性任务和两种方法来创建和执行周期性任务。
构造一个带有5个线程的线程池,它执行将会返回值的任务
SDK提供了一个工厂和实用类的Executors,它们通过静态方法newFixedThreadPool(int nThreads)允许创建一个具有固定数量线程的线程池(MyCallable的实现被省略):
public static void main2(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Future<Integer>[] futures = new Future[5];
for (int i = 0; i < futures.length; i++) {
futures[i] = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return Integer.valueOf(UUID.randomUUID().toString());
}
});
}
for (int i = 0; i < futures.length; i++) {
Integer retVal = futures[i].get();
System.out.println(retVal);
}
executorService.shutdown();
}
复制代码
Runnable和Callable之间有什么区别
Runnable接口定义了没有任何返回值的方法run(),而Callable接口允许方法run()返回一个值并抛出一个异常。
java.util.concurrent.Future的用例
类java.util.concurrent.Future的实例用于表示异步计算的结果,其结果不是立即可用的。 因此,该类提供了检查异步计算是否完成,取消任务和检索实际结果的方法。 后者可以使用提供的两个get() 方法完成。 第一个get() 方法在结果可用之前不接受任何参数和块,而第二个get() 方法接受一个超时参数,如果结果在给定时间范围内不可用,则该方法调用将返回。
HashMap和Hashtable之间有什么区别,特别是关于线程安全性
Hashtable的方法都是同步的。HashMap则不是。 因此Hashtable是线程安全的,而HashMap不是线程安全的。 对于单线程应用程序,使用HashMap实现更高效。
有没有简单的方法来创建一个任意实现的Collection,List或Map的同步实例
实用程序类Collections提供了返回给定实例支持的线程安全的collection/list/map的方法synchronizedCollection(Collection),synchronizedList(List)和synchronizedMap(Map) 。
Semaphore
信号量是一个数据结构,它维护一组必须通过竞争线程获取的许可证。 因此可以使用信号量来控制有多少线程同时访问关键部分或资源。 因此,java.util.concurrent.Semaphore的构造函数将第一个参数作为线程竞争许可的数量。 其acquire()方法的每次调用都会尝试获取其中一个可用的许可证。 方法acquire()没有任何参数块,直到下一个许可证可用。 稍后,当线程在关键资源上完成其工作时,它可以通过调用Semaphore实例上的方法release()来释放许可证。
信号量维护一个许可集,可通过acquire()获取许可(若无可用许可则阻塞),通过release()释放许可,从而可能唤醒一个阻塞等待许可的线程。
与互斥锁类似,信号量限制了同一时间访问临界资源的线程的个数,并且信号量也分公平信号量与非公平信号量。而不同的是,互斥锁保证同一时间只会有一个线程访问临界资源,而信号量可以允许同一时间多个线程访问特定资源。所以信号量并不能保证原子性。
信号量的一个典型使用场景是限制系统访问量。每个请求进来后,处理之前都通过acquire获取许可,若获取许可成功则处理该请求,若获取失败则等待处理或者直接不处理该请求。
信号量的使用方法
- acquire(int permits) 申请permits(必须为非负数)个许可,若获取成功,则该方法返回并且当前可用许可数减permits;若当前可用许可数少于permits指定的个数,则继续等待可用许可数大于等于permits;若等待过程中当前线程被中断,则抛出InterruptedException。
- acquire() 等价于acquire(1)。
- acquireUninterruptibly(int permits) 申请permits(必须为非负数)个许可,若获取成功,则该方法返回并且当前可用许可数减permits;若当前许可数少于permits,则继续等待可用许可数大于等于permits;若等待过程中当前线程被中断,继续等待可用许可数大于等于permits,并且获取成功后设置线程中断状态。
- acquireUninterruptibly() 等价于acquireUninterruptibly(1)。
- drainPermits() 获取所有可用许可,并返回获取到的许可个数,该方法不阻塞。
- tryAcquire(int permits) 尝试获取permits个可用许可,如果当前许可个数大于等于permits,则返回true并且可用许可数减permits;否则返回false并且可用许可数不变。
- tryAcquire() 等价于tryAcquire(1)。
- tryAcquire(int permits, long timeout, TimeUnit unit) 尝试获取permits(必须为非负数)个许可,若在指定时间内获取成功则返回true并且可用许可数减permits;若指定时间内当前线程被中断,则抛出InterruptedException;若指定时间内可用许可数均小于permits,则返回false。
- tryAcquire(long timeout, TimeUnit unit) 等价于tryAcquire(1, long timeout, TimeUnit unit)*
- release(int permits) 释放permits个许可,该方法不阻塞并且某线程调用release方法前并不需要先调用acquire方法。
- release() 等价于release(1)。
注意:与wait/notify和await/signal不同,acquire/release完全与锁无关,因此acquire等待过程中,可用许可满足要求时acquire可立即返回,而不用像锁的wait和条件变量的await那样重新获取锁才能返回。或者可以理解成,只要可用许可满足需求,就已经获得了锁。
CountDownLatch
SDK类CountDownLatch提供了一个同步辅助工具,可用于实现线程必须等待其他线程达到相同状态以便所有线程都可以启动的场景。 这是通过提供一个减量的同步计数器来完成的,直到达到零值。 CountDownLatch实例达到零后,所有线程都可以继续。 这可以用来让所有线程在给定的时间点启动,方法是使用计数器的值1或等待多个线程完成。 在后一种情况下,计数器用线程数进行初始化,每个完成其工作的线程将锁存器计数一次。
Java多线程编程中经常会碰到这样一种场景——某个线程需要等待一个或多个线程操作结束(或达到某种状态)才开始执行。比如开发一个并发测试工具时,主线程需要等到所有测试线程均执行完成再开始统计总共耗费的时间,此时可以通过CountDownLatch轻松实现。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int totalThread = 3;
long start = System.currentTimeMillis();
CountDownLatch countDown = new CountDownLatch(totalThread);
for(int i = 0; i < totalThread; i++) {
final String threadName = "Thread " + i;
new Thread(() -> {
System.out.println(String.format("%s\t%s %s", new Date(), threadName, "started"));
try {
Thread.sleep(1000);
} catch (Exception ex) {
ex.printStackTrace();
}
countDown.countDown();
System.out.println(String.format("%s\t%s %s", new Date(), threadName, "ended"));
}).start();;
}
countDown.await();
long stop = System.currentTimeMillis();
System.out.println(String.format("Total time : %sms", (stop - start)));
}
}
复制代码
执行结果
Sun Jun 19 20:34:31 CST 2016 Thread 1 started
Sun Jun 19 20:34:31 CST 2016 Thread 0 started
Sun Jun 19 20:34:31 CST 2016 Thread 2 started
Sun Jun 19 20:34:32 CST 2016 Thread 2 ended
Sun Jun 19 20:34:32 CST 2016 Thread 1 ended
Sun Jun 19 20:34:32 CST 2016 Thread 0 ended
Total time : 1072ms
复制代码
可以看到,主线程等待所有3个线程都执行结束后才开始执行。
CountDownLatch工作原理相对简单,可以简单看成一个倒计数器,在构造方法中指定初始值,每次调用countDown()方法时将计数器减1,而await()会等待计数器变为0。CountDownLatch关键接口如下
- countDown() 如果当前计数器的值大于1,则将其减1;若当前值为1,则将其置为0并唤醒所有通过await等待的线程;若当前值为0,则什么也不做直接返回。
- await() 等待计数器的值为0,若计数器的值为0则该方法返回;若等待期间该线程被中断,则抛出InterruptedException并清除该线程的中断状态。
- await(long timeout, TimeUnit unit) 在指定的时间内等待计数器的值为0,若在指定时间内计数器的值变为0,则该方法返回true;若指定时间内计数器的值仍未变为0,则返回false;若指定时间内计数器的值变为0之前当前线程被中断,则抛出InterruptedException并清除该线程的中断状态。
- getCount() 读取当前计数器的值,一般用于调试或者测试。
CyclicBarrier
内存屏障,它能保证屏障之前的代码一定在屏障之后的代码之前被执行。CyclicBarrier可以译为循环屏障,也有类似的功能。CyclicBarrier可以在构造时指定需要在屏障前执行await的个数,所有对await的调用都会等待,直到调用await的次数达到预定指,所有等待都会立即被唤醒。
从使用场景上来说,CyclicBarrier是让多个线程互相等待某一事件的发生,然后同时被唤醒。而上文讲的CountDownLatch是让某一线程等待多个线程的状态,然后该线程被唤醒。
public class CyclicBarrierDemo {
public static void main(String[] args) {
int totalThread = 5;
CyclicBarrier barrier = new CyclicBarrier(totalThread);
for(int i = 0; i < totalThread; i++) {
String threadName = "Thread " + i;
new Thread(() -> {
System.out.println(String.format("%s\t%s %s", new Date(), threadName, " is waiting"));
try {
barrier.await();
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(String.format("%s\t%s %s", new Date(), threadName, "ended"));
}).start();
}
}
}
复制代码
执行结果如下
Sun Jun 19 21:04:49 CST 2016 Thread 1 is waiting
Sun Jun 19 21:04:49 CST 2016 Thread 0 is waiting
Sun Jun 19 21:04:49 CST 2016 Thread 3 is waiting
Sun Jun 19 21:04:49 CST 2016 Thread 2 is waiting
Sun Jun 19 21:04:49 CST 2016 Thread 4 is waiting
Sun Jun 19 21:04:49 CST 2016 Thread 4 ended
Sun Jun 19 21:04:49 CST 2016 Thread 0 ended
Sun Jun 19 21:04:49 CST 2016 Thread 2 ended
Sun Jun 19 21:04:49 CST 2016 Thread 1 ended
Sun Jun 19 21:04:49 CST 2016 Thread 3 ended
复制代码
从执行结果可以看到,每个线程都不会在其它所有线程执行await()方法前继续执行,而等所有线程都执行await()方法后所有线程的等待都被唤醒从而继续执行。
CyclicBarrier提供的关键方法如下
- await() 等待其它参与方的到来(调用await())。如果当前调用是最后一个调用,则唤醒所有其它的线程的等待并且如果在构造CyclicBarrier时指定了action,当前线程会去执行该action,然后该方法返回该线程调用await的次序(getParties()-1说明该线程是第一个调用await的,0说明该线程是最后一个执行await的),接着该线程继续执行await后的代码;如果该调用不是最后一个调用,则阻塞等待;如果等待过程中,当前线程被中断,则抛出InterruptedException;如果等待过程中,其它等待的线程被中断,或者其它线程等待超时,或者该barrier被reset,或者当前线程在执行barrier构造时注册的action时因为抛出异常而失败,则抛出BrokenBarrierException。
- await(long timeout, TimeUnit unit) 与await()唯一的不同点在于设置了等待超时时间,等待超时时会抛出TimeoutException。
- reset() 该方法会将该barrier重置为它的初始状态,并使得所有对该barrier的await调用抛出BrokenBarrierException。
CountDownLatch和CyclicBarrier之间有什么区别
两个SDK类都在内部维护一个由不同线程递减的计数器。 线程一直等到内部计数器达到0并从此处继续。 但是与CountDownLatch相反,一旦值达到零CyclicBarrier类将内部值重置为初始值。 由于该名称指示CyclicBarrier的实例因此可以用于实现线程必须一次又一次地彼此等待的用例。
Phaser
CountDownLatch和CyclicBarrier都是JDK 1.5引入的,而Phaser是JDK 1.7引入的。Phaser的功能与CountDownLatch和CyclicBarrier有部分重叠,同时也提供了更丰富的语义和更灵活的用法。
Phaser顾名思义,与阶段相关。Phaser比较适合这样一种场景,一种任务可以分为多个阶段,现希望多个线程去处理该批任务,对于每个阶段,多个线程可以并发进行,但是希望保证只有前面一个阶段的任务完成之后才能开始后面的任务。这种场景可以使用多个CyclicBarrier来实现,每个CyclicBarrier负责等待一个阶段的任务全部完成。但是使用CyclicBarrier的缺点在于,需要明确知道总共有多少个阶段,同时并行的任务数需要提前预定义好,且无法动态修改。而Phaser可同时解决这两个问题。
public class PhaserDemo {
public static void main(String[] args) throws IOException {
int parties = 3;
int phases = 4;
final Phaser phaser = new Phaser(parties) {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("====== Phase : " + phase + " ======");
return registeredParties == 0;
}
};
for(int i = 0; i < parties; i++) {
int threadId = i;
Thread thread = new Thread(() -> {
for(int phase = 0; phase < phases; phase++) {
System.out.println(String.format("Thread %s, phase %s", threadId, phase));
phaser.arriveAndAwaitAdvance();
}
});
thread.start();
}
}
}
复制代码
执行结果如下
Thread 0, phase 0
Thread 1, phase 0
Thread 2, phase 0
====== Phase : 0 ======
Thread 2, phase 1
Thread 0, phase 1
Thread 1, phase 1
====== Phase : 1 ======
Thread 1, phase 2
Thread 2, phase 2
Thread 0, phase 2
====== Phase : 2 ======
Thread 0, phase 3
Thread 1, phase 3
Thread 2, phase 3
====== Phase : 3 ======
复制代码
从上面的结果可以看到,多个线程必须等到其它线程的同一阶段的任务全部完成才能进行到下一个阶段,并且每当完成某一阶段任务时,Phaser都会执行其onAdvance方法。
Phaser主要接口如下
- arriveAndAwaitAdvance() 当前线程当前阶段执行完毕,等待其它线程完成当前阶段。如果当前线程是该阶段最后一个未到达的,则该方法直接返回下一个阶段的序号(阶段序号从0开始),同时其它线程的该方法也返回下一个阶段的序号。
- arriveAndDeregister() 该方法立即返回下一阶段的序号,并且其它线程需要等待的个数减一,并且把当前线程从之后需要等待的成员中移除。如果该Phaser是另外一个Phaser的子Phaser(层次化Phaser会在后文中讲到),并且该操作导致当前Phaser的成员数为0,则该操作也会将当前Phaser从其父Phaser中移除。
- arrive() 该方法不作任何等待,直接返回下一阶段的序号。
- awaitAdvance(int phase) 该方法等待某一阶段执行完毕。如果当前阶段不等于指定的阶段或者该Phaser已经被终止,则立即返回。该阶段数一般由arrive()方法或者arriveAndDeregister()方法返回。返回下一阶段的序号,或者返回参数指定的值(如果该参数为负数),或者直接返回当前阶段序号(如果当前Phaser已经被终止)。
- awaitAdvanceInterruptibly(int phase) 效果与awaitAdvance(int phase)相当,唯一的不同在于若该线程在该方法等待时被中断,则该方法抛出InterruptedException。
- awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit) 效果与awaitAdvanceInterruptibly(int phase)相当,区别在于如果超时则抛出TimeoutException。
- bulkRegister(int parties) 注册多个party。如果当前phaser已经被终止,则该方法无效,并返回负数。如果调用该方法时,onAdvance方法正在执行,则该方法等待其执行完毕。如果该Phaser有父Phaser则指定的party数大于0,且之前该Phaser的party数为0,那么该Phaser会被注册到其父Phaser中。
- forceTermination() 强制让该Phaser进入终止状态。已经注册的party数不受影响。如果该Phaser有子Phaser,则其所有的子Phaser均进入终止状态。如果该Phaser已经处于终止状态,该方法调用不造成任何影响。
通过使用Fork / Join框架可以解决哪些类型的任务
Fork / Join Framework基类java.util.concurrent.ForkJoinPool基本上是一个线程池,用于执行java.util.concurrent.ForkJoinTask的实例。 类ForkJoinTask提供了fork()和join()两个方法。 虽然fork()用于启动任务的异步执行,但join()方法用于等待计算结果。 因此Fork / Join框架可以用来实现分治算法,其中一个更复杂的问题被分成许多更小更容易解决的问题。
是否可以使用Fork / Join-Framework在数组数组中找到最小的数字
在数字数组中寻找最小数字的问题可以通过使用分而治之算法来解决。 可以很容易解决的最小问题是两个数字的数组,因为我们可以直接通过一个比较来确定两个数字中较小的一个。 使用分而治之的方法,初始数组被分成两部分长度相等,并且这两部分都被提供给扩展类ForkJoinTask的两个RecursiveTask实例。 通过分解它们执行的两个任务并直接解决问题,如果它们的数组切片长度为2,或者它们再次递归地将数组分割成两部分并分叉两个新的RecursiveTasks。 最后,每个任务实例返回其结果(通过直接计算或等待两个子任务)。 根任务然后返回数组中的最小数字。
RecursiveTask和Recursiveaction之间有什么区别
与RecursiveTask相比,RecursiveAction的方法compute()不必返回值。 因此,当动作直接在某些数据结构上工作时,可以使用RecursiveAction,而不必返回计算值。
是否有可能通过线程池在Java 8中执行流操作
集合提供了方法parallelStream()来创建一个由线程池处理的流。 或者可以调用给定流上的中间方法parallel()来将顺序流转换为并行对象。
如何访问使用并行流操作的线程池
用于并行流操作的线程池可以通过ForkJoinPool.commonPool()访问。 这样我们就可以用commonPool.getParallelism()来查询它的并行级别。 该级别在运行时不能更改,但可以通过提供以下JVM参数进行配置:-Djava.util.concurrent.ForkJoinPool.common. parallelism=5.
Executor 接口
用于执行提交给它的 Runnable 任务的对象。此接口提供了一种将任务提交与每个任务的运行机制解耦的方法,包括线程使用,调度等的详细信息。通常使用 Executor 而不是显式创建线程。 例如,调用一组任务不再使用
new Thread(new(RunnableTask())).start()
复制代码
而是使用:
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
复制代码
但是,Executor 接口并不严格要求执行是异步的。 在最简单的情况下,executor 可以立即在调用者的线程中运行提交的任务:
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
复制代码
更典型地,任务在某个线程中执行而不是在调用者线程执行。 下面的 executor 为每个任务生成一个新线程。
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
复制代码
许多 Executor 实现类对如何以及何时调度任务施加了某种限制。 下面的 executor 将任务提交序列化到第二个 executor,说明了一个复合执行程序。
class SerialExecutor implements Executor {
final Queue<Runnable> tasks = new ArrayDeque<Runnable>();
final Executor executor;
Runnable active;
SerialExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(final Runnable r) {
tasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (active == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((active = tasks.poll()) != null) {
executor.execute(active);
}
}
}
复制代码
内存一致性效果:在将 Runnable 对象提交给 Executor 执行之前的操作,happen-before 被提交的 Runnable 的执行,也许在另一个线程中。
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
复制代码
ExecutorService 接口
作为一个 Executor,提供管理终止的方法和可以生成 Future 以跟踪一个或多个异步任务进度的方法。
ExecutorService 可以被关闭,这将导致它拒绝接收新任务。ExecutorService 提供了两种不同的方法来关闭。 shutdown 方法将允许先前提交的任务在终止之前保持执行,而 shutdownNow 方法阻止等待任务启动并尝试停止当前正在执行的任务。 终止时,executor 没有正在执行的任务,没有等待执行的任务,也没有任何新任务可以提交。 未使用的 ExecutorService 应被关闭以允许回收其资源。
方法 submit 通过创建并返回可用于取消执行和/或等待完成的 Future 来扩展基本方法 Executor.execute(Runnable)。 方法 invokeAny 和 invokeAll 执行最常用的批量执行形式,执行一组任务,然后等待至少一个或全部完成。类 ExecutorCompletionService 可用于编写这些方法的自定义变体。
Executors 类为此包中提供的 executor 服务提供工厂方法。
下面是网络服务的草图,其中线程池中的线程为传入的请求提供服务。 它使用预配置的 Executors.newFixedThreadPool 工厂方法:
class NetworkService implements Runnable {
private final ServerSocket serverSocket;
private final ExecutorService pool;
public NetworkService(int port, int poolSize) throws IOException {
serverSocket = new ServerSocket(port);
pool = Executors.newFixedThreadPool(poolSize);
}
public void run() { // run the service
try {
for (; ; ) {
pool.execute(new Handler(serverSocket.accept()));
}
} catch (IOException ex) {
pool.shutdown();
}
}
}
class Handler implements Runnable {
private final Socket socket;
Handler(Socket socket) {
this.socket = socket;
}
public void run() {
// read and service request on socket
}
}
复制代码
以下方法分两个阶段关闭 ExecutorService,首先调用 shutdown 拒绝传入的任务,然后在必要时调用 shutdownNow 以取消任何延迟的任务:
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
复制代码
内存一致性效果:在将 Runnable 或 Callable 任务提交到 ExecutorService 之前,线程中的操作 happen-before 该任务所采取的任何行动,而该任务 happen-before 通过 Future.get() 检索的结果。
// 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。
boolean awaitTermination(long timeout, TimeUnit unit)
// 执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
// 执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
// 执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
// 执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
// 如果此执行程序已关闭,则返回 true。
boolean isShutdown()
// 如果关闭后所有任务都已完成,则返回 true。
boolean isTerminated()
// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
void shutdown()
// 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
List<Runnable> shutdownNow()
// 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
<T> Future<T> submit(Callable<T> task)
// 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future<?> submit(Runnable task)
// 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
<T> Future<T> submit(Runnable task, T result)
复制代码
AbstractExecutorService 抽象类
提供 ExecutorService 执行方法的默认实现。 此类使用 newTaskFor 返回的 RunnableFuture 实现 submit, invokeAny 和 invokeAll 方法,默认为此中提供的 FutureTask 类包。 例如, submit(Runnable) 的实现会创建一个执行并返回的关联 RunnableFuture。 子类可以覆盖 newTaskFor 方法,以返回 FutureTask 以外的 RunnableFuture 实现。
扩展示例。 下面是一个类的草图,它定制 ThreadPoolExecutor 以使用 CustomTask 类而不是默认的 FutureTask:
public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
static class CustomTask<V> implements RunnableFuture<V> {...
}
protected <V> RunnableFuture<V> newTaskFor(Callable<V> c) {
return new CustomTask<V>(c);
}
protected <V> RunnableFuture<V> newTaskFor(Runnable r, V v) {
return new CustomTask<V>(r, v);
}
// ... add constructors, etc.
}
复制代码
public abstract class AbstractExecutorService implements ExecutorService {
/**
* Returns a {@code RunnableFuture} for the given runnable and default
* value.
*
* @param runnable the runnable task being wrapped
* @param value the default value for the returned future
* @param <T> the type of the given value
* @return a {@code RunnableFuture} which, when run, will run the
* underlying runnable and which, as a {@code Future}, will yield
* the given value as its result and provide for cancellation of
* the underlying task
* @since 1.6
*/
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
/**
* Returns a {@code RunnableFuture} for the given callable task.
*
* @param callable the callable task being wrapped
* @param <T> the type of the callable's result
* @return a {@code RunnableFuture} which, when run, will call the
* underlying callable and which, as a {@code Future}, will yield
* the callable's result as its result and provide for
* cancellation of the underlying task
* @since 1.6
*/
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
/**
* the main mechanics of invokeAny.
*/
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
if (tasks == null)
throw new NullPointerException();
int ntasks = tasks.size();
if (ntasks == 0)
throw new IllegalArgumentException();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
// For efficiency, especially in executors with limited
// parallelism, check to see if previously submitted tasks are
// done before submitting more of them. This interleaving
// plus the exception mechanics account for messiness of main
// loop.
try {
// Record exceptions so that if we fail to obtain any
// result, we can throw the last exception we got.
ExecutionException ee = null;
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Iterator<? extends Callable<T>> it = tasks.iterator();
// Start one task for sure; the rest incrementally
futures.add(ecs.submit(it.next()));
--ntasks;
int active = 1;
for (;;) {
Future<T> f = ecs.poll();
if (f == null) {
if (ntasks > 0) {
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
else if (active == 0)
break;
else if (timed) {
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
if (f == null)
throw new TimeoutException();
nanos = deadline - System.nanoTime();
}
else
f = ecs.take();
}
if (f != null) {
--active;
try {
return f.get();
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
}
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return doInvokeAny(tasks, true, unit.toNanos(timeout));
}
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
execute(f);
}
for (int i = 0, size = futures.size(); i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) {
try {
f.get();
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
long nanos = unit.toNanos(timeout);
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks)
futures.add(newTaskFor(t));
final long deadline = System.nanoTime() + nanos;
final int size = futures.size();
// Interleave time checks and calls to execute in case
// executor doesn't have any/much parallelism.
for (int i = 0; i < size; i++) {
execute((Runnable)futures.get(i));
nanos = deadline - System.nanoTime();
if (nanos <= 0L)
return futures;
}
for (int i = 0; i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) {
if (nanos <= 0L)
return futures;
try {
f.get(nanos, TimeUnit.NANOSECONDS);
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
} catch (TimeoutException toe) {
return futures;
}
nanos = deadline - System.nanoTime();
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
}
复制代码
ThreadPoolExecutor 类
作为一个 ExecutorService,它使用可能的几个池化线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。
线程池解决了两个不同的问题:它们通常在执行大量异步任务时将提升性能,这是由于减少了每个任务的调用开销,并且它们提供了一种绑定和管理资源和线程的方法,减少了执行一系列任务时所消耗的性能。 每个 ThreadPoolExecutor 还维护一些基本统计信息,例如已完成任务的数量。
为了在各种上下文中有用,该类提供了许多可调参数和可扩展性钩子。 但是,程序员应该使用更方便的 Executors 工厂方法 Executor.newCachedThreadPool(无界线程池,自动线程回收), Executors.newFixedThreadPool(固定大小的线程池)和 Executors.newSingleThreadExecutor(单一后台线程),为最常见的使用场景预配置设置。 否则,在手动配置和调整此类时,请使用以下指南:
核心和最大池大小
ThreadPoolExecutor 将根据corePoolSize(getCorePoolSize)和maximumPoolSize(getMaximumPoolSize)设置的边界自动调整线程池大小(getPoolSize)。
当在方法 execute(Runnable) 中提交新任务并且运行的线程少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求。 如果有多于corePoolSize但少于maximumPoolSize的运行线程,则只有在队列已满时才会创建新线程。 通过设置corePoolSize和maximumPoolSize相同,可以创建一个固定大小的线程池。 通过将maximumPoolSize设置为基本无限制的值(例如 Integer.MAX_VALUE),可以允许池容纳任意数量的并发任务。 最典型的情况是,核心和最大池大小仅在构造时设置,但也可以使用 setCorePoolSize 和 #setMaximumPoolSize 动态更改。
按需构建
默认情况下,即使核心线程最初只在新任务到达时创建并启动,但可以使用方法 prestartCoreThread 或 prestartAllCoreThreads 动态覆写。 如果使用非空队列构造池,则可能需要预启动线程。
创建新线程
使用 ThreadFactory 创建新线程。 如果没有另外指定,则使用 Executors.defaultThreadFactory,它创建的线程都在同一个 ThreadGroup 中,并且具有相同的 NORM_PRIORITY 优先级和非守护进程状态。 通过提供不同的ThreadFactory,可以更改线程的名称,线程组,优先级,守护程序状态等。如果 ThreadFactory 在通过从 newThread 返回null而无法创建线程时,executor 将继续,但可能无法执行任何任务。 线程应该拥有“modifyThread” RuntimePermission。 如果使用池的工作线程或其他线程不具有此权限,则服务可能会降级:配置更改可能不会及时生效,并且关闭池可能保持可以终止但未完成的状态。
保持存活的时间
如果池当前具有多个corePoolSize线程,则多余的线程如果空闲时间超过keepAliveTime,则将终止(getKeepAliveTime(TimeUnit))。 这提供了一种在不主动使用池时减少资源消耗的方法。 如果池稍后变得更活跃,则将构造新线程。 也可以使用方法 setKeepAliveTime(long,TimeUnit) 动态更改此参数。 使用值 Long.MAX_VALUE TimeUnit#NANOSECONDS 可以有效地禁止空闲线程在关闭之前终止。 默认情况下,仅当存在多个corePoolSize线程时,保持活动策略才适用。 但是方法 allowCoreThreadTimeOut(boolean) 也可用于将此超时策略应用于核心线程,只要keepAliveTime值为非零。
队列
任何BlockingQueue都可以用于传输和保存所提交的任务。这个队列的使用与池大小交互:
-
如果运行的线程少于corePoolSize的线程,Executor 总是添加新线程,而不是排队。
-
如果corePoolSize或更多的线程正在运行,Executor 总是喜欢排队请求,而不是添加一个新的线程。
-
如果请求不能排队,那么将创建一个新的线程,除非这个线程超过maximumPoolSize,在这种情况下,该任务将被拒绝。
排队的一般策略有三种:
直接切换。工作队列的一个很好的默认选择是 SynchronousQueue,它将任务移交给线程而不另外保存它们。 在这里,如果没有线程立即可用于运行它,则尝试对任务进行入队将失败,因此将构造新线程。 此策略在处理可能具有内部依赖性的请求集时避免了锁定。 直接切换通常需要无限制的maximumPoolSizes以避免拒绝新提交的任务。 这反过来承认,当命令继续以比处理它们更快的速度到达时,无限制的线程增长的可能性。
无界队列。使用无界队列(例如没有预定义容量的 LinkedBlockingQueue 将导致新任务在所有corePoolSize线程忙时在队列中等待。 因此,只会创建corePoolSize线程。 (maximumPoolSize的值因此没有任何影响。)这可能是适当的每个任务完全独立于其他任务,因此任务不会影响彼此的执行; 例如,在网页服务器中。 虽然这种排队方式可以有助于平滑瞬态突发请求,但需要承认的是当命令继续以平均到达的速度超过可处理速度时,可能导致无限制的工作队列增长。
有界队列。有界队列(例如, ArrayBlockingQueue)与有限maximumPoolSizes一起使用时有助于防止资源耗尽,但可能更难调整和控制。 队列大小和最大池大小可以相互交换:使用大型队列和小型池最小化CPU使用率,OS资源和上下文切换开销,但可能导致人为的低吞吐量。 如果任务经常被阻塞(例如,如果它们是I / O绑定的),那么系统可能会安排更多线程的时间,而不是允许的时间。 使用小队列通常需要更大的池大小,这会使CPU更繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。
被拒绝的任务
当 Executor 关闭时,以及当Executor对最大线程和工作队列容量使用有限边界时,方法 execute(Runnable) 中提交的新任务将被拒绝,因为已经饱和了。 在任何一种情况下, execute 方法都会调用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(Runnable,ThreadPoolExecutor) 方法。 提供了四种预定义的处理程序策:
在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序在拒绝时抛出运行时 异常 RejectedExecutionException。
在 ThreadPoolExecutor.CallerRunsPolicy 中,调用 execute 本身的线程运行该任务。 这提供了一个简单的反馈控制机制,可以减慢提交新任务的速度。
在 ThreadPoolExecutor.DiscardPolicy 中,简单地删除了无法执行的任务。
在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果 executor 未关闭,则会删除工作队列头部的任务,然后重试执行(可能会再次失败,导致重复执行)。
可以定义和使用其他种类的 RejectedExecutionHandler 扩展类。 这样做需要特别小心,特别是当策略设计为仅在特定容量或排队策略下工作时。
钩子方法
此类提供在执行每个任务之前和之后调用的 protected 可覆盖 beforeExecute(Thread,Runnable) 和 afterExecute(Runnable,Throwable) 方法。 这些可以用来操纵执行环境; 例如,重新初始化 ThreadLocals ,收集统计信息或添加日志条目。 此外,可以重写方法 terminated 以执行 Executor 完全终止后需要执行的任何特殊处理。
如果钩子或回调方法抛出异常,则内部工作者线程可能会失败并突然终止。
队列维护
方法 getQueue() 允许访问工作队列以进行监视和调试。 强烈建议不要将此方法用于任何其他目的。 当大量排队的任务被取消时,两种提供的方法 remove(Runnable) 和 purge 可用于协助存储回收。
终结
程序中不再引用的池和没有剩余的线程将自动 shutdown。 如果希望确保即使用户忘记调用 shutdown 也会回收未引用的池,那么必须通过设置适当的保持活动时间,使用零核心线程的下限来安排未使用的线程最终死亡 和/或设置 allowCoreThreadTimeOut(boolean)。
扩展示例。 此类的大多数扩展都会覆盖一个或多个受保护的钩子方法。 例如,这是一个添加简单暂停/恢复功能的子类:
class PausableThreadPoolExecutor extends ThreadPoolExecutor {
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition unpaused = pauseLock.newCondition();
public PausableThreadPoolExecutor(...) {
super(...);
}
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (isPaused) unpaused.await();
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}
public void pause() {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
}
public void resume() {
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
}
复制代码
// 用给定的初始参数和默认的线程工厂及被拒绝的执行处理程序创建新的 ThreadPoolExecutor。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
// 用给定的初始参数和默认的线程工厂创建新的 ThreadPoolExecutor。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
// 用给定的初始参数和默认被拒绝的执行处理程序创建新的 ThreadPoolExecutor。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
// 用给定的初始参数创建新的 ThreadPoolExecutor。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
// 基于完成执行给定 Runnable 所调用的方法。
protected void afterExecute(Runnable r, Throwable t)
// 如果在保持活动时间内没有任务到达,新任务到达时正在替换(如果需要),则设置控制核心线程是超时还是终止的策略。
void allowCoreThreadTimeOut(boolean value)
// 如果此池允许核心线程超时和终止,如果在 keepAlive 时间内没有任务到达,新任务到达时正在替换(如果需要),则返回 true。
boolean allowsCoreThreadTimeOut()
// 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。
boolean awaitTermination(long timeout, TimeUnit unit)
// 在执行给定线程中的给定 Runnable 之前调用的方法。
protected void beforeExecute(Thread t, Runnable r)
// 在将来某个时间执行给定任务。
void execute(Runnable command)
// 当不再引用此执行程序时,调用 shutdown。
protected void finalize()
// 返回主动执行任务的近似线程数。
int getActiveCount()
// 返回已完成执行的近似任务总数。
long getCompletedTaskCount()
// 返回核心线程数。
int getCorePoolSize()
// 返回线程保持活动的时间,该时间就是超过核心池大小的线程可以在终止前保持空闲的时间值。
long getKeepAliveTime(TimeUnit unit)
// 返回曾经同时位于池中的最大线程数。
int getLargestPoolSize()
// 返回允许的最大线程数。
int getMaximumPoolSize()
// 返回池中的当前线程数。
int getPoolSize()
// 返回此执行程序使用的任务队列。
BlockingQueue<Runnable> getQueue()
// 返回用于未执行任务的当前处理程序。
RejectedExecutionHandler getRejectedExecutionHandler()
// 返回曾计划执行的近似任务总数。
long getTaskCount()
// 返回用于创建新线程的线程工厂。
ThreadFactory getThreadFactory()
// 如果此执行程序已关闭,则返回 true。
boolean isShutdown()
// 如果关闭后所有任务都已完成,则返回 true。
boolean isTerminated()
// 如果此执行程序处于在 shutdown 或 shutdownNow 之后正在终止但尚未完全终止的过程中,则返回 true。
boolean isTerminating()
// 启动所有核心线程,使其处于等待工作的空闲状态。
int prestartAllCoreThreads()
// 启动核心线程,使其处于等待工作的空闲状态。
boolean prestartCoreThread()
// 尝试从工作队列移除所有已取消的 Future 任务。
void purge()
// 从执行程序的内部队列中移除此任务(如果存在),从而如果尚未开始,则其不再运行。
boolean remove(Runnable task)
// 设置核心线程数。
void setCorePoolSize(int corePoolSize)
// 设置线程在终止前可以保持空闲的时间限制。
void setKeepAliveTime(long time, TimeUnit unit)
// 设置允许的最大线程数。
void setMaximumPoolSize(int maximumPoolSize)
// 设置用于未执行任务的新处理程序。
void setRejectedExecutionHandler(RejectedExecutionHandler handler)
// 设置用于创建新线程的线程工厂。
void setThreadFactory(ThreadFactory threadFactory)
// 按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。
void shutdown()
// 尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。
List<Runnable> shutdownNow()
// 当 Executor 已经终止时调用的方法。
protected void terminated()
复制代码
ScheduledExecutorService 接口
一个 ExecutorService,它可以调度命令在给定的延迟之后运行,或者定期执行。
schedule 方法创建具有各种延迟的任务,并返回可用于取消或检查执行的任务对象。scheduleAtFixedRate 和 scheduleWithFixedDelay 方法创建并执行定期运行的任务,直到被取消。
使用 Executor.execute(Runnable) 和 ExecutorService.submit 方法提交的命令的调度请求延迟为零。 schedule 方法中也允许零延迟和负延迟(但不包括时间段),并将其视为立即执行的请求。
所有 schedule 方法都接受相对延迟和周期作为参数,而不是绝对时间或日期。 将表示为 java.util.Date 的绝对时间转换为所需的形式是一件简单的事情。 例如,要在某个将来安排 date,可以使用: schedule(task,date.getTime() - System.currentTimeMillis(),TimeUnit.MILLISECONDS) 。 但请注意,相对延迟的到期不一定与由于网络时间同步协议,时钟漂移或其他因素而启用任务的当前 Date 一致。
Executors 类为此程序包中提供的 ScheduledExecutorService 实现提供了方便的工厂方法。
这是一个带有方法的类,该方法将 ScheduledExecutorService 设置为每隔一小时发出十秒钟的哔声:
import static java.util.concurrent.TimeUnit.*;
class BeeperControl {
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
public void beepForAnHour() {
final Runnable beeper = new Runnable() {
public void run() {
System.out.println("beep");
}
};
final ScheduledFuture<?> beeperHandle =
scheduler.scheduleAtFixedRate(beeper, 10, 10, SECONDS);
scheduler.schedule(new Runnable() {
public void run() {
beeperHandle.cancel(true);
}
}, 60 * 60, SECONDS);
}
}
复制代码
// 创建并执行在给定延迟后启用的 ScheduledFuture。
ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit)
// 创建并执行在给定延迟后启用的一次性操作。
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
// 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
// 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
复制代码
ScheduledThreadPoolExecutor
一个 ThreadPoolExecutor ,可以额外安排命令在给定的延迟后运行,或定期执行。 当需要多个工作线程时,或者当需要 ThreadPoolExecutor(此类扩展)的额外灵活性或功能时,此类优于 java.util.Timer。
延迟任务在启用后立即执行,但没有任何实时保证启用它们后何时启动它们。 按照先进先出(FIFO)提交顺序启用计划完全相同执行时间的任务。
在提交的任务在运行之前取消时,将禁止执行。 默认情况下,此类已取消的任务不会自动从工作队列中删除,直到其延迟过去。 虽然这可以进一步检查和监控,但也可能导致取消任务的无限制保留。 为避免这种情况,请将 setRemoveOnCancelPolicy 设置为 true ,这会导致在取消时立即从工作队列中删除任务。
通过 scheduleAtFixedRate 或 scheduleWithFixedDelay 安排的任务的连续执行不重叠。 虽然不同的执行可以由不同的线程执行,但先前执行的效果 happen-before 之后的效果。
虽然这个类继承自 ThreadPoolExecutor,但是一些继承的调优方法对它没用。 特别是,因为它使用 corePoolSize 线程和无界队列充当固定大小的池,所以对 maximumPoolSize 的调整没有任何有用的效果。 此外,将 corePoolSize 设置为零或使用 allowCoreThreadTimeOut 几乎绝不是一个好主意,因为一旦它们有资格运行,这可能会使池没有线程来处理任务。
扩展注释:此类覆盖 ThreadPoolExecutor.execute(Runnable) execute 和 AbstractExecutorService.submit(Runnable) submit 方法,以生成内部 ScheduledFuture 对象以控制每个任务的延迟和调度。 为了保留功能,子类中这些方法的任何进一步覆盖必须调用超类版本,这有效地禁用了其他任务自定义。 但是,此类提供了替代的受保护扩展方法 decorateTask 支持 Runnable 和 Callable 各一个版本),可用于自定义用于执行通过 submit, schedule, scheduleAtFixedRate 和 scheduleWithFixedDelay。 默认情况下, ScheduledThreadPoolExecutor 使用扩展 FutureTask 的任务类型。 但是,可以使用以下形式的子类来修改或替换它:
public class CustomScheduledExecutor extends ScheduledThreadPoolExecutor {
static class CustomTask<V> implements RunnableScheduledFuture<V> {
...
}
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable r, RunnableScheduledFuture<V> task) {
return new CustomTask<V>(r, task);
}
protected <V> RunnableScheduledFuture<V> decorateTask(
Callable<V> c, RunnableScheduledFuture<V> task) {
return new CustomTask<V>(c, task);
}
// ... add constructors, etc.
}
复制代码
Executors 类
此程序包中定义的 Executor, ExecutorService,ScheduledExecutorService, ThreadFactory 和 Callable 类的工厂和实用程序方法。 该类支持以下几种方法:
* <li> Methods that create and return an {@link ExecutorService}
* set up with commonly useful configuration settings.
* <li> Methods that create and return a {@link ScheduledExecutorService}
* set up with commonly useful configuration settings.
* <li> Methods that create and return a "wrapped" ExecutorService, that
* disables reconfiguration by making implementation-specific methods
* inaccessible.
* <li> Methods that create and return a {@link ThreadFactory}
* that sets newly created threads to a known state.
* <li> Methods that create and return a {@link Callable}
* out of other closure-like forms, so they can be used
* in execution methods requiring {@code Callable}.
复制代码
// 返回 Callable 对象,调用它时可运行给定特权的操作并返回其结果。
static Callable<Object> callable(PrivilegedAction<?> action)
// 返回 Callable 对象,调用它时可运行给定特权的异常操作并返回其结果。
static Callable<Object> callable(PrivilegedExceptionAction<?> action)
// 返回 Callable 对象,调用它时可运行给定的任务并返回 null。
static Callable<Object> callable(Runnable task)
// 返回 Callable 对象,调用它时可运行给定的任务并返回给定的结果。
static <T> Callable<T> callable(Runnable task, T result)
// 返回用于创建新线程的默认线程工厂。
static ThreadFactory defaultThreadFactory()
// 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
static ExecutorService newCachedThreadPool()
// 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
// 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
static ExecutorService newFixedThreadPool(int nThreads)
// 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
// 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
// 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
// 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
static ExecutorService newSingleThreadExecutor()
// 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程,并在需要时使用提供的 ThreadFactory 创建新线程。
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
// 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newSingleThreadScheduledExecutor()
// 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
// 返回 Callable 对象,调用它时可在当前的访问控制上下文中执行给定的 callable 对象。
static <T> Callable<T> privilegedCallable(Callable<T> callable)
// 返回 Callable 对象,调用它时可在当前的访问控制上下文中,使用当前上下文类加载器作为上下文类加载器来执行给定的 callable 对象。
static <T> Callable<T> privilegedCallableUsingCurrentClassLoader(Callable<T> callable)
// 返回用于创建新线程的线程工厂,这些新线程与当前线程具有相同的权限。
static ThreadFactory privilegedThreadFactory()
// 返回一个将所有已定义的 ExecutorService 方法委托给指定执行程序的对象,但是使用强制转换可能无法访问其他方法。
static ExecutorService unconfigurableExecutorService(ExecutorService executor)
// 返回一个将所有已定义的 ExecutorService 方法委托给指定执行程序的对象,但是使用强制转换可能无法访问其他方法。
static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor)
复制代码
Android 线程池执行器
它是一个功能强大的任务执行框架,因为它支持队列中的任务添加,任务取消和任务优先级。
它减少了与线程创建相关的开销,因为它在其线程池中管理所需数量的线程。
线程池执行器有自己的创建线程的线程工厂。
public class PriorityThreadFactory implements ThreadFactory {
private final int mThreadPriority;
public PriorityThreadFactory(int threadPriority) {
mThreadPriority = threadPriority;
}
@Override
public Thread newThread(final Runnable runnable) {
Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
try {
Process.setThreadPriority(mThreadPriority);//设置优先级
} catch (Throwable t) {
}
runnable.run();
}
};
return new Thread(wrapperRunnable);
}
}
复制代码
主线程执行器
public class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable runnable) {
handler.post(runnable);
}
}
复制代码
将线程池执行器封装在 DefaultExecutorSupplier 中,可以包含多个线程池执行器。
/*
* Singleton class for default executor supplier
*/
public class DefaultExecutorSupplier{
/*
* Number of cores to decide the number of threads
*/
public static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
/*
* thread pool executor for background tasks
*/
private final ThreadPoolExecutor mForBackgroundTasks;
/*
* thread pool executor for light weight background tasks
*/
private final ThreadPoolExecutor mForLightWeightBackgroundTasks;
/*
* thread pool executor for main thread tasks
*/
private final Executor mMainThreadExecutor;
/*
* an instance of DefaultExecutorSupplier
*/
private static DefaultExecutorSupplier sInstance;
/*
* returns the instance of DefaultExecutorSupplier
*/
public static DefaultExecutorSupplier getInstance() {
if (sInstance == null) {
synchronized(DefaultExecutorSupplier.class){
sInstance = new DefaultExecutorSupplier();
}
return sInstance;
}
/*
* constructor for DefaultExecutorSupplier
*/
private DefaultExecutorSupplier() {
// setting the thread factory
ThreadFactory backgroundPriorityThreadFactory = new
PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND);
// setting the thread pool executor for mForBackgroundTasks;
mForBackgroundTasks = new ThreadPoolExecutor(
NUMBER_OF_CORES * 2,
NUMBER_OF_CORES * 2,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
backgroundPriorityThreadFactory
);
// setting the thread pool executor for mForLightWeightBackgroundTasks;
mForLightWeightBackgroundTasks = new ThreadPoolExecutor(
NUMBER_OF_CORES * 2,
NUMBER_OF_CORES * 2,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
backgroundPriorityThreadFactory
);
// setting the thread pool executor for mMainThreadExecutor;
mMainThreadExecutor = new MainThreadExecutor();
}
/*
* returns the thread pool executor for background task
*/
public ThreadPoolExecutor forBackgroundTasks() {
return mForBackgroundTasks;
}
/*
* returns the thread pool executor for light weight background task
*/
public ThreadPoolExecutor forLightWeightBackgroundTasks() {
return mForLightWeightBackgroundTasks;
}
/*
* returns the thread pool executor for main thread task
*/
public Executor forMainThreadTasks() {
return mMainThreadExecutor;
}
}
复制代码
这样子就可以使用了
/*
* Using it for Background Tasks
*/
public void doSomeBackgroundWork(){
DefaultExecutorSupplier.getInstance().forBackgroundTasks()
.execute(new Runnable() {
@Override
public void run() {
// do some background work here.
}
});
}
/*
* Using it for Light-Weight Background Tasks
*/
public void doSomeLightWeightBackgroundWork(){
DefaultExecutorSupplier.getInstance().forLightWeightBackgroundTasks()
.execute(new Runnable() {
@Override
public void run() {
// do some light-weight background work here.
}
});
}
/*
* Using it for MainThread Tasks
*/
public void doSomeMainThreadWork(){
DefaultExecutorSupplier.getInstance().forMainThreadTasks()
.execute(new Runnable() {
@Override
public void run() {
// do some Main Thread work here.
}
});
}
复制代码
当前传递的任务都是 Runnable 类型,无法获得返回值或者取消任务。可以使用下列方式操作任务:
/*
* Get the future of the task by submitting it to the pool
*/
Future future = DefaultExecutorSupplier.getInstance().forBackgroundTasks().submit(new Runnable() {
@Override
public void run() {
// do some background work here.
}
});
/*
* cancelling the task
*/
future.cancel(true);
复制代码
添加可以设置线程优先级的线程池处理器,首先设置优先级配置:
/**
* Priority levels
*/
public enum Priority {
/**
* NOTE: DO NOT CHANGE ORDERING OF THOSE CONSTANTS UNDER ANY CIRCUMSTANCES.
* Doing so will make ordering incorrect.
*/
/**
* Lowest priority level. Used for prefetches of data.
*/
LOW,
/**
* Medium priority level. Used for warming of data that might soon get visible.
*/
MEDIUM,
/**
* Highest priority level. Used for data that are currently visible on screen.
*/
HIGH,
/**
* Highest priority level. Used for data that are required instantly(mainly for emergency).
*/
IMMEDIATE;
}
复制代码
创建具有优先级的任务实现
public class PriorityRunnable implements Runnable {
private final Priority priority;
public PriorityRunnable(Priority priority) {
this.priority = priority;
}
@Override
public void run() {
// nothing to do here.
}
public Priority getPriority() {
return priority;
}
}
复制代码
接着创建具有优先级的线程池处理器
public class PriorityThreadPoolExecutor extends ThreadPoolExecutor {
public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, ThreadFactory threadFactory) {
//在调用父类方法时使用具有优先级的队列
super(corePoolSize, maximumPoolSize, keepAliveTime, unit,new PriorityBlockingQueue<Runnable>(), threadFactory);
}
@Override
public Future<?> submit(Runnable task) {
//将普通的Runnable封装成具有优先级的Runnable
PriorityFutureTask futureTask = new PriorityFutureTask((PriorityRunnable) task);
//进行入队操作
execute(futureTask);
return futureTask;
}
private static final class PriorityFutureTask extends FutureTask<PriorityRunnable>
implements Comparable<PriorityFutureTask> {
private final PriorityRunnable priorityRunnable;
public PriorityFutureTask(PriorityRunnable priorityRunnable) {
super(priorityRunnable, null);
this.priorityRunnable = priorityRunnable;
}
/*
* compareTo() method is defined in interface java.lang.Comparable and it is used
* to implement natural sorting on java classes. natural sorting means the the sort
* order which naturally applies on object e.g. lexical order for String, numeric
* order for Integer or Sorting employee by there ID etc. most of the java core
* classes including String and Integer implements CompareTo() method and provide
* natural sorting.
*/
@Override
public int compareTo(PriorityFutureTask other) {
Priority p1 = priorityRunnable.getPriority();
Priority p2 = other.priorityRunnable.getPriority();
return p2.ordinal() - p1.ordinal();
}
}
}
复制代码
在 DefaultExecutorSupplier 中暴露出优先级线程池执行器
public class DefaultExecutorSupplier{
private final PriorityThreadPoolExecutor mForBackgroundTasks;
private DefaultExecutorSupplier() {
mForBackgroundTasks = new PriorityThreadPoolExecutor(
NUMBER_OF_CORES * 2,
NUMBER_OF_CORES * 2,
60L,
TimeUnit.SECONDS,
backgroundPriorityThreadFactory
);
}
}
复制代码
使用方式为
/*
* do some task at high priority
*/
public void doSomeTaskAtHighPriority(){
DefaultExecutorSupplier.getInstance().forBackgroundTasks()
.submit(new PriorityRunnable(Priority.HIGH) {
@Override
public void run() {
// do some background work here at high priority.
}
});
}
复制代码
Android中的线程的优先级
在Android中有两种线程的优先级,一种为Android API版本,另一种是 Java 原生版本。
Android API
THREAD_PRIORITY_DEFAULT,默认的线程优先级,值为0。
THREAD_PRIORITY_LOWEST,最低的线程级别,值为19。
THREAD_PRIORITY_BACKGROUND 后台线程建议设置这个优先级,值为10。
THREAD_PRIORITY_FOREGROUND 用户正在交互的UI线程,代码中无法设置该优先级,系统会按照情况调整到该优先级,值为-2。
THREAD_PRIORITY_DISPLAY 也是与UI交互相关的优先级界别,但是要比THREAD_PRIORITY_FOREGROUND优先,代码中无法设置,由系统按照情况调整,值为-4。
THREAD_PRIORITY_URGENT_DISPLAY 显示线程的最高级别,用来处理绘制画面和检索输入事件,代码中无法设置成该优先级。值为-8。
THREAD_PRIORITY_AUDIO 声音线程的标准级别,代码中无法设置为该优先级,值为 -16。
THREAD_PRIORITY_URGENT_AUDIO 声音线程的最高级别,优先程度较THREAD_PRIORITY_AUDIO要高。代码中无法设置为该优先级。值为-19。
THREAD_PRIORITY_MORE_FAVORABLE 相对THREAD_PRIORITY_DEFAULT稍微优先,值为-1。
THREAD_PRIORITY_LESS_FAVORABLE 相对THREAD_PRIORITY_DEFAULT稍微落后一些,值为1。
复制代码
使用方式
new Thread () {
@Override
public void run() {
super.run();
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}.start();
复制代码
Java API 的设置效果不如 Android API。
多线程编程中的三个核心概念
原子性
这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。
关于原子性,一个非常经典的例子就是银行转账问题:比如A和B同时向C转账10万元。如果转账操作不具有原子性,A在向C转账时,读取了C的余额为20万,然后加上转账的10万,计算出此时应该有30万,但还未来及将30万写回C的账户,此时B的转账请求过来了,B发现C的余额为20万,然后将其加10万并写回。然后A的转账操作继续——将30万写回C的余额。这种情况下C的最终余额为30万,而非预期的40万。
可见性
可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。可见性问题是好多人忽略或者理解错误的一点。
CPU从主内存中读数据的效率相对来说不高,现在主流的计算机中,都有几级缓存。每个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会立即更新该缓存,但并不一定会立即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤其是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。
这一点是操作系统或者说是硬件层面的机制,所以很多应用开发人员经常会忽略。
顺序性
顺序性指的是,程序执行的顺序按照代码的先后顺序执行。
以下面这段代码为例
boolean started = false; // 语句1
long counter = 0L; // 语句2
counter = 1; // 语句3
started = true; // 语句4
复制代码
从代码顺序上看,上面四条语句应该依次执行,但实际上JVM真正在执行这段代码时,并不保证它们一定完全按照此顺序执行。
处理器为了提高程序整体的执行效率,可能会对代码进行优化,其中的一项优化方式就是调整代码顺序,按照更高效的顺序执行代码。但它会保证程序最终的执行结果和代码顺序执行时的结果一致。
Java如何保证原子性
锁和同步
常用的保证Java操作原子性的工具是锁和同步方法(或者同步代码块)。使用锁,可以保证同一时间只有一个线程能拿到锁,也就保证了同一时间只有一个线程能执行申请锁和释放锁之间的代码。
public void testLock () {
lock.lock();
try{
int j = i;
i = j + 1;
} finally {
lock.unlock();
}
}
复制代码
与锁类似的是同步方法或者同步代码块。使用非静态同步方法时,锁住的是当前实例;使用静态同步方法时,锁住的是该类的Class对象;使用静态代码块时,锁住的是synchronized关键字后面括号内的对象。下面是同步代码块示例
public void testLock () {
synchronized (anyObject){
int j = i;
i = j + 1;
}
}
复制代码
无论使用锁还是synchronized,本质都是一样,通过锁来实现资源的排它性,从而实际目标代码段同一时间只会被一个线程执行,进而保证了目标代码段的原子性。这是一种以牺牲性能为代价的方法。
CAS(compare and swap)
基础类型变量自增(i++)是一种常被新手误以为是原子操作而实际不是的操作。Java中提供了对应的原子操作类来实现该操作,并保证原子性,其本质是利用了CPU级别的CAS指令。由于是CPU级别的指令,其开销比需要操作系统参与的锁的开销小。AtomicInteger使用方法如下。
AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
new Thread(() -> {
for(int a = 0; a < iteration; a++) {
atomicInteger.incrementAndGet();
}
}).start();
}
复制代码
Java如何保证可见性
Java提供了volatile关键字来保证可见性。当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。
Java如何保证顺序性
编译器和处理器对指令进行重新排序时,会保证重新排序后的执行结果和代码顺序执行的结果一致,所以重新排序过程并不会影响单线程程序的执行,却可能影响多线程程序并发执行的正确性。
Java中可通过volatile在一定程序上保证顺序性,另外还可以通过synchronized和锁来保证顺序性。
synchronized和锁保证顺序性的原理和保证原子性一样,都是通过保证同一时间只会有一个线程执行目标代码段来实现的。
除了从应用层面保证目标代码段执行的顺序性外,JVM还通过被称为happens-before原则隐式地保证顺序性。两个操作的执行顺序只要可以通过happens-before推导出来,则JVM会保证其顺序性,反之JVM对其顺序性不作任何保证,可对其进行任意必要的重新排序以获取高效率。
happens-before原则(先行发生原则)
- 传递规则:如果操作1在操作2前面,而操作2在操作3前面,则操作1肯定会在操作3前发生。该规则说明了happens-before原则具有传递性
- 锁定规则:一个unlock操作肯定会在后面对同一个锁的lock操作前发生。这个很好理解,锁只有被释放了才会被再次获取
- volatile变量规则:对一个被volatile修饰的写操作先发生于后面对该变量的读操作
- 程序次序规则:一个线程内,按照代码顺序执行
- 线程启动规则:Thread对象的start()方法先发生于此线程的其它动作
- 线程终结原则:线程的终止检测后发生于线程中其它的所有操作
- 线程中断规则: 对线程interrupt()方法的调用先发生于对该中断异常的获取
- 对象终结规则:一个对象构造先于它的finalize发生
volatile适用场景
volatile适用于不需要保证原子性,但却需要保证可见性的场景。一种典型的使用场景是用它修饰用于停止线程的状态标记。如下所示
boolean isRunning = false;
public void start () {
new Thread( () -> {
while(isRunning) {
someOperation();
}
}).start();
}
public void stop () {
isRunning = false;
}
复制代码
在这种实现方式下,即使其它线程通过调用stop()方法将isRunning设置为false,循环也不一定会立即结束。可以通过volatile关键字,保证while循环及时得到isRunning最新的状态从而及时停止循环,结束线程。
线程安全Q&A
问:平时项目中使用锁和synchronized比较多,而很少使用volatile,难道就没有保证可见性? 答:锁和synchronized即可以保证原子性,也可以保证可见性。都是通过保证同一时间只有一个线程执行目标代码段来实现的。
问:锁和synchronized为何能保证可见性? 答:根据JDK 7的Java doc中对concurrent包的说明,一个线程的写结果保证对另外线程的读操作可见,只要该写操作可以由happen-before原则推断出在读操作之前发生。
问:既然锁和synchronized即可保证原子性也可保证可见性,为何还需要volatile? 答:synchronized和锁需要通过操作系统来仲裁谁获得锁,开销比较高,而volatile开销小很多。因此在只需要保证可见性的条件下,使用volatile的性能要比使用锁和synchronized高得多。
问:既然锁和synchronized可以保证原子性,为什么还需要AtomicInteger这种的类来保证原子操作? 答:锁和synchronized需要通过操作系统来仲裁谁获得锁,开销比较高,而AtomicInteger是通过CPU级的CAS操作来保证原子性,开销比较小。所以使用AtomicInteger的目的还是为了提高性能。
问:还有没有别的办法保证线程安全 答:有。尽可能避免引起非线程安全的条件——共享变量。如果能从设计上避免共享变量的使用,即可避免非线程安全的发生,也就无须通过锁或者synchronized以及volatile解决原子性、可见性和顺序性的问题。
问:synchronized即可修饰非静态方式,也可修饰静态方法,还可修饰代码块,有何区别 答:synchronized修饰非静态同步方法时,锁住的是当前实例;synchronized修饰静态同步方法时,锁住的是该类的Class对象;synchronized修饰静态代码块时,锁住的是synchronized关键字后面括号内的对象。
sleep和wait到底什么区别
其实这个问题应该这么问——sleep和wait有什么相同点。因为这两个方法除了都能让当前线程暂停执行完,几乎没有其它相同点。
wait方法是Object类的方法,这意味着所有的Java类都可以调用该方法。sleep方法是Thread类的静态方法。
wait是在当前线程持有wait对象锁的情况下,暂时放弃锁,并让出CPU资源,并积极等待其它线程调用同一对象的notify或者notifyAll方法。注意,即使只有一个线程在等待,并且有其它线程调用了notify或者notifyAll方法,等待的线程只是被激活,但是它必须得再次获得锁才能继续往下执行。换言之,即使notify被调用,但只要锁没有被释放,原等待线程因为未获得锁仍然无法继续执行。
public class Wait {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (Wait.class) {
try {
System.out.println(new Date() + " Thread1 is running");
Wait.class.wait();
System.out.println(new Date() + " Thread1 ended");
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
synchronized (Wait.class) {
try {
System.out.println(new Date() + " Thread2 is running");
Wait.class.notify();
// Don't use sleep method to avoid confusing
for(long i = 0; i < 200000; i++) {
for(long j = 0; j < 100000; j++) {}
}
System.out.println(new Date() + " Thread2 release lock");
} catch (Exception ex) {
ex.printStackTrace();
}
}
for(long i = 0; i < 200000; i++) {
for(long j = 0; j < 100000; j++) {}
}
System.out.println(new Date() + " Thread2 ended");
});
// Don't use sleep method to avoid confusing
for(long i = 0; i < 200000; i++) {
for(long j = 0; j < 100000; j++) {}
}
thread2.start();
}
}
复制代码
执行结果如下
Tue Jun 14 22:51:11 CST 2016 Thread1 is running
Tue Jun 14 22:51:23 CST 2016 Thread2 is running
Tue Jun 14 22:51:36 CST 2016 Thread2 release lock
Tue Jun 14 22:51:36 CST 2016 Thread1 ended
Tue Jun 14 22:51:49 CST 2016 Thread2 ended
复制代码
从运行结果可以看出
- thread1执行wait后,暂停执行
- thread2执行notify后,thread1并没有继续执行,因为此时thread2尚未释放锁,thread1因为得不到锁而不能继续执行
- thread2执行完synchronized语句块后释放锁,thread1得到通知并获得锁,进而继续执行
注意:wait方法需要释放锁,前提条件是它已经持有锁。所以wait和notify(或者notifyAll)方法都必须被包裹在synchronized语句块中,并且synchronized后锁的对象应该与调用wait方法的对象一样。否则抛出IllegalMonitorStateException
sleep方法告诉操作系统至少指定时间内不需为线程调度器为该线程分配执行时间片,并不释放锁(如果当前已经持有锁)。实际上,调用sleep方法时并不要求持有任何锁。
public class Sleep {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (Sleep.class) {
try {
System.out.println(new Date() + " Thread1 is running");
Thread.sleep(2000);
System.out.println(new Date() + " Thread1 ended");
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
synchronized (Sleep.class) {
try {
System.out.println(new Date() + " Thread2 is running");
Thread.sleep(2000);
System.out.println(new Date() + " Thread2 ended");
} catch (Exception ex) {
ex.printStackTrace();
}
}
for(long i = 0; i < 200000; i++) {
for(long j = 0; j < 100000; j++) {}
}
});
// Don't use sleep method to avoid confusing
for(long i = 0; i < 200000; i++) {
for(long j = 0; j < 100000; j++) {}
}
thread2.start();
}
}
复制代码
执行结果如下
Thu Jun 16 19:46:06 CST 2016 Thread1 is running
Thu Jun 16 19:46:08 CST 2016 Thread1 ended
Thu Jun 16 19:46:13 CST 2016 Thread2 is running
Thu Jun 16 19:46:15 CST 2016 Thread2 ended
复制代码
由于thread 1和thread 2的run方法实现都在同步块中,无论哪个线程先拿到锁,执行sleep时并不释放锁,因此其它线程无法执行。直到前面的线程sleep结束并退出同步块(释放锁),另一个线程才得到锁并执行。
注意:sleep方法并不需要持有任何形式的锁,也就不需要包裹在synchronized中。
调用sleep方法的线程,在jstack中显示的状态为sleeping。
java.lang.Thread.State: TIMED_WAITING (sleeping)
复制代码
调用wait方法的线程,在jstack中显示的状态为on object monitor
java.lang.Thread.State: WAITING (on object monitor)
复制代码
synchronized几种用法
每个Java对象都可以用做一个实现同步的互斥锁,这些锁被称为内置锁。线程进入同步代码块或方法时自动获得内置锁,退出同步代码块或方法时自动释放该内置锁。进入同步代码块或者同步方法是获得内置锁的唯一途径。
实例同步方法
synchronized用于修饰实例方法(非静态方法)时,执行该方法需要获得的是该类实例对象的内置锁(同一个类的不同实例拥有不同的内置锁)。如果多个实例方法都被synchronized修饰,则当多个线程调用同一实例的不同同步方法(或者同一方法)时,需要竞争锁。但当调用的是不同实例的方法时,并不需要竞争锁。
静态同步方法
synchronized用于修饰静态方法时,执行该方法需要获得的是该类的class对象的内置锁(一个类只有唯一一个class对象)。调用同一个类的不同静态同步方法时会产生锁竞争。
同步代码块
synchronized用于修饰代码块时,进入同步代码块需要获得synchronized关键字后面括号内的对象(可以是实例对象也可以是class对象)的内置锁。
synchronized使用总结
锁的使用是为了操作临界资源的正确性,而往往一个方法中并非所有的代码都操作临界资源。换句话说,方法中的代码往往并不都需要同步。此时建议不使用同步方法,而使用同步代码块,只对操作临界资源的代码,也即需要同步的代码加锁。这样做的好处是,当一个线程在执行同步代码块时,其它线程仍然可以执行该方法内同步代码块以外的部分,充分发挥多线程并发的优势,从而相较于同步整个方法而言提升性能。
释放Java内置锁的唯一方式是synchronized方法或者代码块执行结束。若某一线程在synchronized方法或代码块内发生死锁,则对应的内置锁无法释放,其它线程也无法获取该内置锁(即进入跟该内置锁相关的synchronized方法或者代码块)。
使用jstack dump线程栈时,可查看到相关线程通过synchronized获取到或等待的对象,但Locked ownable synchronizers仍然显示为None。下例中,线程thead-test-b已获取到类型为java.lang.Double的对象的内置锁(monitor),且该对象的内存地址为0x000000076ab95cb8
"thread-test-b" #11 prio=5 os_prio=31 tid=0x00007fab0190b800 nid=0x5903 runnable [0x0000700010249000]
java.lang.Thread.State: RUNNABLE
at com.jasongj.demo.TestJstack.lambda$1(TestJstack.java:27)
- locked <0x000000076ab95cb8> (a java.lang.Double)
at com.jasongj.demo.TestJstack$$Lambda$2/1406718218.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
复制代码
Java中的锁
重入锁
Java中的重入锁(即ReentrantLock)与Java内置锁一样,是一种排它锁。使用synchronized的地方一定可以用ReentrantLock代替。
重入锁需要显示请求获取锁,并显示释放锁。为了避免获得锁后,没有释放锁,而造成其它线程无法获得锁而造成死锁,一般建议将释放锁操作放在finally块里,如下所示。
try{
renentrantLock.lock();
// 用户操作
} finally {
renentrantLock.unlock();
}
复制代码
如果重入锁已经被其它线程持有,则当前线程的lock操作会被阻塞。除了lock()方法之外,重入锁(或者说锁接口)还提供了其它获取锁的方法以实现不同的效果。
- lockInterruptibly() 该方法尝试获取锁,若获取成功立即返回;若获取不成功则阻塞等待。与lock方法不同的是,在阻塞期间,如果当前线程被打断(interrupt)则该方法抛出InterruptedException。该方法提供了一种解除死锁的途径。
- tryLock() 该方法试图获取锁,若该锁当前可用,则该方法立即获得锁并立即返回true;若锁当前不可用,则立即返回false。该方法不会阻塞,并提供给用户对于成功获利锁与获取锁失败进行不同操作的可能性。
- tryLock(long time, TimeUnit unit) 该方法试图获得锁,若该锁当前可用,则立即获得锁并立即返回true。若锁当前不可用,则等待相应的时间(由该方法的两个参数决定):1)若该时间内锁可用,则获得锁,并返回true;2)若等待期间当前线程被打断,则抛出InterruptedException;3)若等待时间结束仍未获得锁,则返回false。
重入锁可定义为公平锁或非公平锁,默认实现为非公平锁。
- 公平锁是指多个线程获取锁被阻塞的情况下,锁变为可用时,最新申请锁的线程获得锁。可通过在重入锁(RenentrantLock)的构造方法中传入true构建公平锁,如Lock lock = new RenentrantLock(true)
- 非公平锁是指多个线程等待锁的情况下,锁变为可用状态时,哪个线程获得锁是随机的。synchonized相当于非公平锁。可通过在重入锁的构造方法中传入false或者使用无参构造方法构建非公平锁。
使用jstack dump线程栈时,可查看到获取到或正在等待的锁对象,获取到该锁的线程会在Locked ownable synchronizers处显示该锁的对象类型及内存地址。在下例中,从Locked ownable synchronizers部分可看到,线程thread-test-e获取到公平重入锁,且该锁对象的内存地址为0x000000076ae3d708
"thread-test-e" #17 prio=5 os_prio=31 tid=0x00007fefaa0b6800 nid=0x6403 runnable [0x0000700002939000]
java.lang.Thread.State: RUNNABLE
at com.jasongj.demo.TestJstack.lambda$4(TestJstack.java:64)
at com.jasongj.demo.TestJstack$$Lambda$5/466002798.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x000000076af86810> (a java.util.concurrent.locks.ReentrantLock$FairSync)
复制代码
而线程thread-test-f由于未获取到锁,而处于WAITING(parking)状态,且它等待的锁正是上文线程thread-test-e获取的锁(内存地址0x000000076af86810)
"thread-test-f" #18 prio=5 os_prio=31 tid=0x00007fefaa9b2800 nid=0x6603 waiting on condition [0x0000700002a3c000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076af86810> (a java.util.concurrent.locks.ReentrantLock$FairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.jasongj.demo.TestJstack.lambda$5(TestJstack.java:69)
at com.jasongj.demo.TestJstack$$Lambda$6/33524623.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
复制代码
读写锁
锁可以保证原子性和可见性。而原子性更多是针对写操作而言。对于读多写少的场景,一个读操作无须阻塞其它读操作,只需要保证读和写或者写与写不同时发生即可。此时,如果使用重入锁(即排它锁),对性能影响较大。Java中的读写锁(ReadWriteLock)就是为这种读多写少的场景而创造的。
实际上,ReadWriteLock接口并非继承自Lock接口,ReentrantReadWriteLock也只实现了ReadWriteLock接口而未实现Lock接口。ReadLock和WriteLock,是ReentrantReadWriteLock类的静态内部类,它们实现了Lock接口。
一个ReentrantReadWriteLock实例包含一个ReentrantReadWriteLock.ReadLock实例和一个ReentrantReadWriteLock.WriteLock实例。通过readLock()和writeLock()方法可分别获得读锁实例和写锁实例,并通过Lock接口提供的获取锁方法获得对应的锁。
读写锁的锁定规则如下:
- 获得读锁后,其它线程可获得读锁而不能获取写锁
- 获得写锁后,其它线程既不能获得读锁也不能获得写锁
public class ReadWriteLockDemo {
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
new Thread(() -> {
readWriteLock.readLock().lock();
try {
System.out.println(new Date() + "\tThread 1 started with read lock");
try {
Thread.sleep(2000);
} catch (Exception ex) {
}
System.out.println(new Date() + "\tThread 1 ended");
} finally {
readWriteLock.readLock().unlock();
}
}).start();
new Thread(() -> {
readWriteLock.readLock().lock();
try {
System.out.println(new Date() + "\tThread 2 started with read lock");
try {
Thread.sleep(2000);
} catch (Exception ex) {
}
System.out.println(new Date() + "\tThread 2 ended");
} finally {
readWriteLock.readLock().unlock();
}
}).start();
new Thread(() -> {
Lock lock = readWriteLock.writeLock();
lock.lock();
try {
System.out.println(new Date() + "\tThread 3 started with write lock");
try {
Thread.sleep(2000);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(new Date() + "\tThread 3 ended");
} finally {
lock.unlock();
}
}).start();
}
}
复制代码
执行结果如下
Sat Jun 18 21:33:46 CST 2016 Thread 1 started with read lock
Sat Jun 18 21:33:46 CST 2016 Thread 2 started with read lock
Sat Jun 18 21:33:48 CST 2016 Thread 2 ended
Sat Jun 18 21:33:48 CST 2016 Thread 1 ended
Sat Jun 18 21:33:48 CST 2016 Thread 3 started with write lock
Sat Jun 18 21:33:50 CST 2016 Thread 3 ended
复制代码
从上面的执行结果可见,thread 1和thread 2都只需获得读锁,因此它们可以并行执行。而thread 3因为需要获取写锁,必须等到thread 1和thread 2释放锁后才能获得锁。
条件锁
条件锁只是一个帮助用户理解的概念,实际上并没有条件锁这种锁。对于每个重入锁,都可以通过newCondition()方法绑定若干个条件对象。
条件对象提供以下方法以实现不同的等待语义
- await() 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。调用该方法外,当前线程会释放当前已经获得的锁(这一点与上文讲述的Java内置锁的wait方法一致),并且等待其它线程调用该条件对象的signal()或者signalAll()方法(这一点与Java内置锁wait后等待notify()或notifyAll()很像)。或者在等待期间,当前线程被打断,则wait()方法会抛出InterruptedException并清除当前线程的打断状态。
- await(long time, TimeUnit unit) 适用条件和行为与await()基本一致,唯一不同点在于,指定时间之内没有收到signal()或signalALL()信号或者线程中断时该方法会返回false;其它情况返回true。
- awaitNanos(long nanosTimeout) 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。nanosTimeout指定该方法等待信号的的最大时间(单位为纳秒)。若指定时间内收到signal()或signalALL()则返回nanosTimeout减去已经等待的时间;若指定时间内有其它线程中断该线程,则抛出InterruptedException并清除当前线程的打断状态;若指定时间内未收到通知,则返回0或负数。
- awaitUninterruptibly() 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。调用该方法后,结束等待的唯一方法是其它线程调用该条件对象的signal()或signalALL()方法。等待过程中如果当前线程被中断,该方法仍然会继续等待,同时保留该线程的中断状态。
- awaitUntil(Date deadline) 适用条件与行为与awaitNanos(long nanosTimeout)完全一样,唯一不同点在于它不是等待指定时间,而是等待由参数指定的某一时刻。
调用条件等待的注意事项
- 调用上述任意条件等待方法的前提都是当前线程已经获得与该条件对象对应的重入锁。
- 调用条件等待后,当前线程让出CPU资源。
- 上述等待方法结束后,方法返回的前提是它能重新获得与该条件对象对应的重入锁。如果无法获得锁,仍然会继续等待。这也是awaitNanos(long nanosTimeout)可能会返回负值的原因。
- 一旦条件等待方法返回,则当前线程肯定已经获得了对应的重入锁。
- 重入锁可以创建若干个条件对象,signal()和signalAll()方法只能唤醒相同条件对象的等待。
- 一个重入锁上可以生成多个条件变量,不同线程可以等待不同的条件,从而实现更加细粒度的的线程间通信。
signal()与signalAll()
- signal() 若有一个或若干个线程在等待该条件变量,则该方法会唤醒其中的一个(具体哪一个,无法预测)。调用该方法的前提是当前线程持有该条件变量对应的锁,否则抛出IllegalMonitorStateException。
- signalALL() 若有一个或若干个线程在等待该条件变量,则该方法会唤醒所有等待。调用该方法的前提是当前线程持有该条件变量对应的锁,否则抛出IllegalMonitorStateException。
public class ConditionTest {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println(new Date() + "\tThread 1 is waiting");
try {
long waitTime = condition.awaitNanos(TimeUnit.SECONDS.toNanos(2));
System.out.println(new Date() + "\tThread 1 remaining time " + waitTime);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(new Date() + "\tThread 1 is waken up");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
lock.lock();
try{
System.out.println(new Date() + "\tThread 2 is running");
try {
Thread.sleep(4000);
} catch (Exception ex) {
ex.printStackTrace();
}
condition.signal();
System.out.println(new Date() + "\tThread 2 ended");
} finally {
lock.unlock();
}
}).start();
}
}
复制代码
执行结果如下
Sun Jun 19 15:59:09 CST 2016 Thread 1 is waiting
Sun Jun 19 15:59:09 CST 2016 Thread 2 is running
Sun Jun 19 15:59:13 CST 2016 Thread 2 ended
Sun Jun 19 15:59:13 CST 2016 Thread 1 remaining time -2003467560
Sun Jun 19 15:59:13 CST 2016 Thread 1 is waken up
复制代码
从执行结果可以看出,虽然thread 2一开始就调用了signal()方法去唤醒thread 1,但是因为thread 2在4秒钟后才释放锁,也即thread 1在4秒后才获得锁,所以thread 1的await方法在4秒钟后才返回,并且返回负值。