文章目录
一、线程创建方式
1.1 常见的四种线程创建方式、使用案例
参考博客线程创建常用的四种方式
- 继承Thread类创建线程
- 实现Runnable接口创建线程【推荐】 场景:临时使用,内部方法,不被外部对象使用
- 使用Callable和Future创建线程
- 使用线程池例如用Executor框架
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class ThreadTest_2 {
//继承Thread
public static class MyThread extends Thread {
@Override
public void run () {
System.out.println("1 继承Thread类 方式");
}
}
//实现Runnable
public static class MyRunnable implements Runnable {
@Override
public void run () {
System.out.println("2.1 实现Runnable接口 方式");
System.out.println("2.1 " + Thread.currentThread().getName());
}
}
//实现Callable接口
public static class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
System.out.println("3 实现Callable接口 方式");
System.out.println("3 " + Thread.currentThread().getName());
return 200;
}
}
public static void main(String[] args) {
//1.继承Thread类
new MyThread().start();
//2.1 实现Runnable接口(完整版)
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
//2.2 实现Runnable接口(缩写版)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("2.2 实现Runnable接口 方式");
}
}).start();
//3.1 实现Callable接口(完整版)
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
Thread threadFuture = new Thread(futureTask, "threadFuture完整版");
threadFuture.start();
try {
System.out.println(futureTask.get());//get()方法会阻塞,直到子线程执行结束才返回
} catch (Exception e){
System.out.println(e);
}
//3.2 实现Callable接口(缩写版)
new Thread(new FutureTask<Integer>(new MyCallable()), "threadFuture缩写版").start();
// ############### 线程池 ###############
//4.1 线程池方式——固定线程数
ExecutorService executorService1 = Executors.newFixedThreadPool(5);
executorService1.submit(myRunnable);
//4.2 线程池方式——单1线程
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
executorService2.submit(myRunnable);
//4.3 线程池方式——缓存短期线程
ExecutorService executorService3 = Executors.newCachedThreadPool();
executorService3.submit(myRunnable);
//4.4 线程池方式——调度型线程
ExecutorService executorService4 = Executors.newScheduledThreadPool(5);
executorService4.submit(myRunnable);
//4.4 线程池方式——多任务队列线程
ExecutorService executorService5 = Executors.newWorkStealingPool(5);
executorService5.submit(myRunnable);
}
}
输出结果
1.2 区别总结
差别:
- Callable规定(重写)的方法是call(),call()可抛出异常,任务执行后可返回值;Runnable规定(重写)的方法是run(),run()不能抛出异常,任务不能返回值。
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果;
- 继承Thread类:线程类无法再继承其他父类(Java单继承性质)。
- 前三种的线程如果创建关闭频繁会消耗系统资源影响性能;而使用线程池可以重复使用线程
二、Thread和Runnable源码
可以注意到:
- Runnable 和 Thread 都使用了注解@FunctionalInterface,该注解提示编译器去检查接口是否仅包含一个抽象方法,即,是否符合函数式编程的定义。
2.1 源码——Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
2.2 源码——Thread类
public class Thread implements Runnable {
...
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
private boolean single_step;
private boolean daemon = false;
private boolean stillborn = false;
private Runnable target;
private ThreadGroup group;
private ClassLoader contextClassLoader;
private AccessControlContext inheritedAccessControlContext;
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
/* 重要! */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* 重要! This map is maintained by the InheritableThreadLocal class.*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private long stackSize;
private long nativeParkEventPointer;
/* Thread ID */
private long tid;
/* For generating thread ID */
private static long threadSeqNumber;
private volatile int threadStatus = 0;
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
volatile Object parkBlocker;
private volatile Interruptible blocker;
private final Object blockerLock = new Object();
void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
}
/** 最低优先级 */
public final static int MIN_PRIORITY = 1;
/** 默认优先级 */
public final static int NORM_PRIORITY = 5;
/** 最大优先级 */
public final static int MAX_PRIORITY = 10;
public static native Thread currentThread();
public static native void yield();
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException("nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
...
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
...
}
三、等待多个线程执行完
背景:最近做了一个需求,要求返回某个企业所有“品牌入驻”任务的完成情况,这些入驻完成情况需要调用其他服务器的接口服务来检测,有如下两种做法:
- 直接顺序执行:在一条线程里挨个检测“所有入驻任务”完成情况,此时接口服务调用是按照顺序执行的。
【缺点:任务较多时,执行时间会增大。】 - 多线程:让每个线程去检测“入驻任务”完成情况,而主线程等待所有任务完成后再组合返回。
【疑问:怎么让某个线程等待它开启的所有线程执行完后再执行呢?】
3.1 CountDownLatch 计数法
原因:
- countDownLatch.await()会阻塞当前线程,直到countDownLatch的值等于0
- countDownLatch不可能重新初始化或者修改CountDownLatch对象内部计数器的值,一个线程调用countdown方法happen-before另外一个线程调用await方法
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest_3 {
public static void main(String[] args) {
int total = 5;
final CountDownLatch countDownLatch = new CountDownLatch(total);
for (int i = 0; i < total; i++) {
new Thread(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currentThread().getName() +
",countDownLatch当前值" + countDownLatch.getCount());
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
System.out.println("全部线程执行完成");
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
结果输出
3.2 CyclicBarrier 同步屏障
- cyclicBarrier.await()会阻塞当前线程,直到cyclicBarrier的值等于0 即所有线程都到到了await状态,才会继续执行。
- 有两种需求,一种是不需要汇总,一种需要汇总。如下。
3.2.1 到达屏障不汇总
- 当最后一个线程到达时,不进行汇总,屏障直接消除,所有线程继续执行。
- 案例:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest_4 {
public static void main(String[] args) {
int total = 5;
final CyclicBarrier cyclicBarrier = new CyclicBarrier(total);
for (int i = 0; i < total; i++) {
new Thread(new Runnable(){
@Override
public void run(){
try {
System.out.println(Thread.currentThread().getName() + "前");
cyclicBarrier.await();//到达屏障
System.out.println("全部线程都到达了屏障");
System.out.println(Thread.currentThread().getName() + "后");
} catch (Exception e) {
System.out.println(e);
}
}
}).start();
}
try {
System.out.println(Thread.currentThread().getName() + ",cyclicBarrier当前值" + cyclicBarrier.getNumberWaiting());
} catch (Exception e) {
System.out.println(e);
}
}
}
输出结果:
3.2.2 到达屏障进行汇总
- 当所有线程到达屏障时,先释放主线程,再进行汇总操作,汇总完后,屏障直接消除,所有线程继续执行
- 案例:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest_4 {
public static void main(String[] args) {
int total = 5;
final CyclicBarrier cyclicBarrier = new CyclicBarrier(total, new Runnable() {
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + ",全部线程都到达了屏障");
}
});
for (int i = 0; i < total; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "前");
cyclicBarrier.await();//到达屏障
System.out.println(Thread.currentThread().getName() + "后");
} catch (Exception e) {
System.out.println(e);
}
}
}).start();
}
try {
System.out.println(Thread.currentThread().getName() +
",cyclicBarrier当前值" + cyclicBarrier.getNumberWaiting());
} catch (Exception e) {
System.out.println(e);
}
}
}
输出结果
四、怎么知道设置多少线程合适?
-
通常,使用固定线程池限制最大线程数。
-
在并发编程领域,提升性能的本质,就是提升硬件的利用率: (1)CPU利用率、(2)I/O利用率。
-
线程数设置多少合适?如下:
-
对于CPU密集型场景:如圆周率计算、视频高清解码等,全靠CPU运算能力这种计算密级型任务越多,消耗在任务切换的时间就越多。
(1). 理论上最合适的是,线程数量=CPU核数。
(2). 但是在工程上通常设置,线程数量=CPU核数+1。原因:当线程偶尔发生内存页失效或其他现象导致阻塞时,这个额外的线程可以顶上,从而保证CPU的利用率。==
-
对于 I/O 密集型场景:如网络IO、硬盘IO、Web应用等,这类任务消耗CPU小,大部分时间都在等待IO操作完成。任务越多,CPU效率越高。
单核最佳线程数=1+(I/O耗时/CPU耗时),针对多核CPU,目前见过两种比较合理的公式:
(1). 多核最佳线程数=CPU核数×100%×[1+(I/O耗时/CPU耗时)]
(2). 多核最佳线程数=CPU核数×目标CPU利用率×(1+平均等待时间/平均工作时间)案例:假设有n个任务,每个任务I/O耗时10s,每个任务CPU耗时1s。如果CPU的核数是4核,则最佳线程数=4x(1+10/1)=44。
可以理解为,在一个核上,10个任务同时执行的耗时与一个任务的执行耗时是相同的,都是10s。
-
CPU与I/O理解案例:假设程序按照CPU计算和I/O操作交叉执行的方式运行,而且CPU计算和I/O操作的耗时比例是1:1。
- 如下图,如果只有一个线程,执行CPU计算时,I/O设备空闲;执行I/O操作时,CPU空闲。所以CPU的利用率和I/O设备的利用率都是50%
- 如果有两个线程,如下图所示,当线程A执行CPU计算的时候,线程B执行I/O操作;当线程A执行I/O操作的时候,线程B执行CPU计算,这样CPU的利用率和I/O设备的利用率就都达到了100%