多线程的创建方式
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
}
实现Runnable
接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable");
}
}
实现Callable
接口
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTaskA = new FutureTask<String>(new ImplementsCallable());
new Thread(futureTaskA,"implementsCallable-A").start();
String s = futureTaskA.get();
System.out.println(s);
}
public static class ImplementsCallable implements Callable<String> {
@Override
public String call() throws Exception {
return UUID.randomUUID().toString().substring(0,8);
}
}
}
线程池
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。 —————《阿里巴巴Java开发手册》泰山版第一章第七节并发处理第3点。
【强制】线程池不允许使用Executors去创建,而是通过
ThreadPoolExecutor
的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors
返回的线程池对象的弊端如下: 1)FixedThreadPool
和SingleThreadPool
: 允许的请求队列长度为Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致OOM。 2)CachedThreadPool
: 允许的创建线程数量为Integer.MAX_VALUE
,可能会创建大量的线程,从而导致OOM。————《阿里巴巴Java开发手册》泰山版第一章第七节并发处理第4点。
我们在实际开发环境中,建议使用线程池的方式创建线程。
public class ThreadPool
{
private static int POOL_NUM = 10;
public static void main(String[] args)
{
ExecutorService executorService = new ThreadPoolExecutor(
5,
5,
1l,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for(int i = 0; i<POOL_NUM; i++)
{
RunnableThread thread = new RunnableThread();
executorService.execute(thread);
}
}
}
class RunnableThread implements Runnable
{
private int THREAD_NUM = 10;
public void run()
{
for(int i = 0; i<THREAD_NUM; i++)
{
System.out.println("线程" + Thread.currentThread() + " " + i);
}
}
}
线程池参数说明
-
corePoolSize 核心线程数
-
maximumPoolSize 最大线程数
-
keepAliveTime 超过核心线程数以后开启的线程没有作业的时候能够存活的时间
-
TimeUnit keepAliveTime的时间单位
-
BlockingQueue 任务队列,当任务过多的时候先到任务队列,然后才会开启大于核心线程数
-
ThreadFactory 线程工厂
-
RejectedExecutionHandler 任务过多的处理器
AbortPolicy 该策略是默认的拒绝策略,该策略将抛出未检查的RejectedExecutionException,我们可以捕获这个异常,然后根据需求做相应的处理
DiscardPolicy 当新提交的任务无法保存到队列中等待执行时,该策略会直接丢掉这个任务并且不会有任何异常,源码如下,实际就是对线程不执行操作 DiscardOldestPolicy 字面意思就是丢弃最旧的,该策略会抛弃下一个将被执行的任务,然后尝试重新提交新的任务(如果工作队列是一个优先级队列,那么“抛弃最旧”,就意味着丢弃优先级最高的任务,因此最好不要将此策略和优先级队列放在一起使用) CallerRunsPolicy 该策略既不抛弃任务,也不抛出异常。就是任务添加到线程池失败,那么主线程会自己去执行该任务
线程的生命周期
线程一共有五个状态
- 新建(new): 当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。例如:Thread t1 = new Thread() 。
- 可运行(runnable): 线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权。例如:t1.start() 。
- 运行(running): 线程获得 CPU 资源正在执行任务(#run() 方法),此时除非此线程自动放弃 CPU 资源或者有优先级更高的线程进入,线程将一直运行到结束。 死亡(dead):当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
- 自然终止: 正常运行完 #run()方法,终止。 异常终止: 调用 #stop() 方法,让一个线程终止运行。
- 堵塞(blocked): 由于某种原因导致正在运行的线程让出 CPU 并暂停自己的执行,即进入堵塞状态。直到线程进入可运行(runnable)状态,才有机会再次获得 CPU 资源,转到运行(running)状态。阻塞的情况有三种: 正在睡眠: 调用 #sleep(long t) 方法,可使线程进入睡眠方式。 一个睡眠着的线程在指定的时间过去可进入可运行(runnable)状态。 正在等待: 调用 #wait() 方法。 调用 notify() 方法,回到就绪状态。 被另一个线程所阻塞: 调用 #suspend() 方法。 调用 #resume() 方法,就可以恢复。
thread.wait();和notify的是要先得到监视器对象不然会java.lang.IllegalMonitorStateException
注意点
- wait和notify/notifyAll方法只能在同步代码块里用
- wait不会释放锁,sleep会释放锁
- LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。
- unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。
LockSupport例子
public static void main(String[] args) throws Exception {
Thread A = new Thread(() -> {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
LockSupport.park();
System.out.println(sum);
});
A.start();
//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
// Thread.sleep(1000);
LockSupport.unpark(A);
}
线程的优先级
每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低。 优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。
线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。
线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。
Thread类中包含的成员变量代表了线程的某些优先级。如Thread.MIN_PRIORITY(常数1),Thread.NORM_PRIORITY(常数5), Thread.MAX_PRIORITY(常数10)。其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1) 到Thread.MAX_PRIORITY(常数10) 之间,在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)。
学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理
线程的终止
thread.isInterrupted() 不会清楚标志位
Thread.interrupted() 会清楚标志位
使用Thread.interrupted()线程的例子
package car.kafka;
public class Run {
public static void main(String args[]){
Thread thread = new MyThread();
thread.start();
try {
Thread.sleep(2);
thread.interrupt();
System.out.println(thread.isInterrupted());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static class MyThread extends Thread {
@Override
public void run(){
super.run();
for(int i=0; i<500000; i++){
if(interrupted()) {
System.out.println("线程已经终止, for循环不再执行");
break;
}
System.out.println("i="+(i+1));
}
}
}
}
能停止的线程--异常法 有了前面学习过的知识点,就可以在线程中用for语句来判断一下线程是否是停止状态,如果是停止状态,则后面的代码不再运行即可:
public class MyThread extends Thread {
public void run(){
super.run();
try {
for(int i=0; i<500000; i++){
if(this.interrupted()) {
System.out.println("线程已经终止, for循环不再执行");
throw new InterruptedException();
}
System.out.println("i="+(i+1));
}
System.out.println("这是for循环外面的语句。因为有InterruptedException,所以不会被执行");
} catch (InterruptedException e) {
System.out.println("进入MyThread.java类中的catch了。。。");
e.printStackTrace();
}
}
}
使用Run.java运行的结果如下:
...
i=203798
i=203799
i=203800
线程已经终止, for循环不再执行
进入MyThread.java类中的catch了。。。
java.lang.InterruptedException
at thread.MyThread.run(MyThread.java:13)
在沉睡中停止 如果线程在sleep()状态下停止线程,会是什么效果呢?
public class MyThread extends Thread {
public void run(){
super.run();
try {
System.out.println("线程开始。。。");
Thread.sleep(200000);
System.out.println("线程结束。");
} catch (InterruptedException e) {
System.out.println("在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:" + this.isInterrupted());
e.printStackTrace();
}
}
}
使用Run.java运行的结果是:
线程开始。。。
在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at thread.MyThread.run(MyThread.java:12)