更快的执行
在多核处理器上,将一个程序的分为多个片段,在每个单独的处理上运行每个片段。利用上更多的处理器显然会使程序运行的更加快。
但并发通常是提高运行在单核处理器上的程序性能。理论上,单处理器上运行并发任务增加了上下文切换的时间,这会看起来比顺序执行开销更大。
但实际上,阻塞的出现让情况变得不同。如果程序中某个任务出现线程阻塞(通常是I/O),这时候使用并发编写程序,可以保证其他任务可以继续执行。
基本线程机制
代表所执行的对象的 Runnable
实现 Runnable
接口 唯一 public abstract void run()
方法,代表所须执行的命令。
public class Demo implements Runnable {
private int countDown = 10;
@Override
public void run() {
while (countDown > 0){
System.out.println(countDown--);
Thread.yield(); //让步
}
}
}
带返回值的 Callable
实现 Callable
唯一方法 V call()
方法。
public class CallDemo implements Callable<String> {
@Override
public String call() throws Exception {
return "CallDemo" + super.toString();
}
}
Future<?> submit(Runnable task)
方法返回了 Future<?>
接口的实例。
我们可以使用 isDone 来检验 Future
对象是否执行完异步的任务,也可以不检查直接使用 get()
获取异步任务的返回对象,这个过程也会阻塞到结果异步任务执行完。
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(new CallDemo());
executorService.shutdown();
System.out.println(future.get());
}
}
FutureTask
FutureTask<V>
实现了 RunnableFuture<V>
接口。而 RunnableFuture<V>
多重继承了 Runnable
和 Future<V>
方法。
public class FutureTask<V> implements RunnableFuture<V>{...}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
所以 FutureTask
可以直接在构造器中声明所执行的异步任务,即 Runnable
对象。在提交到执行器(ExecutorService
)之后,可以通过自己的 get()
方法获取异步任务的返回值。
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
FutureTask<String> futureTask = new FutureTask<>(new CallDemo());
executorService.submit(futureTask);
System.out.println(futureTask.get());//忽略了 try catch
executorService.shutdown();
}
}
Thread
Thread.yield()
的调用是对线程调度器的一个建议,表示自己已经执行完了重要的代码,可以切换到其他任务。
public static void main(String[] args) {
Thread thread = new Thread(new Demo());
thread.start();
}
Thread.start()
方法为该线程执行必须的初始化操作。然后在新的线程中调用 Runnable.run()
方法。
Executor
Executor
允许你管理异步任务的执行,而无需显示的管理线程的生命周期。类命令模式的设计使其只能提供了一个 excute()
方法。
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.execute(new Demo());
}
executorService.shutdown();
System.out.println("main end");
}
}
3 3 2 3 3 3 2 1 2 2 2 1 main end 1 1 1
Process finished with exit code 0
shutDown()
方法保证在这之后有新的任务提交给 Executor
,而后在 Executor
执行完成任务后尽快退出。
- CachedThreadPool
CachedThreadPool
会为创建所属数量的线程,只有在回收线程时停止创建新的线程,而 maximumPoolSize
值为 Integer.MAX_VALUE
,有可能因线程数过多而倒是 OOM。
- FixedThreadPool
FixedThreadPool 可以限制线程数量。一次性的预先执行代价高昂的线程分配,而不用为每个任务都付出创建线程的开销。
- SingleThreadExecutor
SingleThreadExecutor
是线程数为 1 的 FixedThreadPool
的串行队列。maximumPoolSize
值为 1
, 会因为任务堆积而导致 OOM。
Sleep
sleep()
使任务中止运行一段时间。
public class Demo implements Runnable {
private int countDown = 3;
@Override
public void run() {
try {
while (countDown > 0) {
System.out.println(countDown--);
TimeUnit.MILLISECONDS.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
权重(Priority)
Thread.currentThread().setPriority(priority);
可以为任务设置优先级。确保线程调度机制能优先运行最高优先级的任务。
让步(yield)
在完成一次迭代后,可以给线程调度机制一个建议,调用 yield()
方法让步,表示这个时刻可以运行 具有相同优先级的 的任务。
后台线程(Daemon)
后台(daemon)线程指程序在运行时在后台提供一种通用服务的线程,并且这种线程不属于程序中不可或缺的部分。当所有非后台线程结束后,程序也中止了。
Thread daemon = new Thread(new Demo());
daemon.setDaemon(true);
daemon.start();
Process finished with exit code 0 (不执行 runnable 中的任务)
线程工厂(ThreadFactory)
我们可以继承 ThreadFactory
并实现其 newThread()
方法,来配置 ExecutorService 所创建线程的属性。
public class DaemonThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread();
thread.setDaemon(true);
return thread;
}
}
使用 ThreadFactory
来构建 newCachedThreadPool
public class Main {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool(new DaemonThreadFactory());
service.execute(new Demo());
service.shutdown();
}
}