Java并发之线程状态及创建(1)
一、状态解读
Java线程状态迁移.png1、New 初始状态
创建线程后,未运行
2、Runnable 可运行状态
可能在运行也可能在等待CPU时间片
同时也包含了操作系统线程的Ready 和 Running
着重说一下Thread.yield()
:
Thread.yield()
方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。
yield()
做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的
目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()
达到让步目的,因为让步的线程还有可能被
线程调度程序再次选中。
结论:yield()
从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()
将导致线程从运行状态转到可运行状态,但有可能没有效果。
3、Blocked 阻塞
等待获取一个Synchronized锁,如果抢占到锁就会离开此状态。
4、Waiting 等待
等待其它线程显示的唤醒,否则一直等待下去。
进入 | 退出 |
---|---|
没有设置Time out 参数的Object.wait() |
Object.notify() 或 Object.notifyAll() |
没有设置Time out 参数的Thread.join() |
被调用的线程执行完毕 |
LockSupport.park() | LockSupport.unpark(Thread thread) |
5、Timed Waiting 超时等待
进入 | 退出 |
---|---|
Object.wait(Long) | Object.notify() 或 Object.notifyAll() |
Thread.join(Long) | 超时退出 或 被调用的线程执行完毕 |
Thread.sleep(Long) | 超时退出 |
LockSupport.parkNanos(Long) | LockSupport.unpark() |
LockSupport.parkUntil(Long) | LockSupport.unpark() |
6、Terminated 终止状态
当线程的run()
方法执行完毕或者主线程的main()
方法完毕,就终止了,也许在这一刻,该线程还是存活的,但是它就要被终止了。
在一个终止的线程上调用start()
方法会抛出异常java.lang.IllegalThreadStateException
,线程一旦被终止,就无法再次运行。
二、创建
Java线程的创建分为四种方式:
1)继承Tread类
public class MyThread extends Thread {
public void run() {
System.out.println("我是继承于Thread的线程类");
}
}
==================分割线====================
@Slf4j
public class JunitTest {
@Test
public void test() throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(3000);
}
}
继承Thread
类,重写其run()
方法,然后通过start()
方法调用即可,如果直接调用run()
方法,那么就相当于调用同步方法,只有调用start()
才是启动线程的正解。
2)实现Runnable接口
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("我是实现Runnable接口的线程类");
}
}
==================分割线====================
@Slf4j
public class JunitTest {
@Test
public void test() throws InterruptedException {
MyThread myThread = new MyThread();
// 传入MyThread实例
Thread thread = new Thread(myThread);
thread.start();
Thread.sleep(3000);
}
}
实现Runnable
接口,重写其run()
方法。
限于第一种方式,受继承的限制,第二种比第一种方式能好一些
3)实现Callable接口
public class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("我是实现Callable接口的线程类");
return "ok";
}
}
==================分割线====================
@Test
public void test() throws Exception {
Callable<String> callable = new MyThread();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
Thread.sleep(3000);
}
覆写call()
方法,且支持返回值!
4)通过线程池
可以通过Executors来创建几种不同类型的线程池。Executors是一个线程池工厂,提供了创建线程池的很多方法。
1)SingleThreadPoolExecutor
只有一个线程的线程池。多个任务被提交到该线程池,先被缓存到队列(队列长度为Integer.MAX_VALUE
),按照FIFO
先进先出的原则进行任务处理。
2)FixedThreadPool
固定大小的线程池,内部存储的线程数量是固定的,和上面的处理流程极为类似,但是该线程池可以处理多个任务,多个任务被提交到线程池,按照以下过程进行处理:
1)如果线程的数量未达到设置的数量,线程池会创建线程来处理任务。
2)如果线程池里的线程数量达到了设置的数量,则取出空闲的线程执行任务
3)如果没有空闲线程,则将任务缓存到队列(队列长度为Integer.MAX_VALUE
),当有空闲线程时,按照FIFO
的原则进行任务处理
严格按照 1 -> 2 -> 3的过程进行处理。
3)CachedThreadPool
创建带缓存的线程池,这种线程池可以回收空闲线程,当任务到达后,直接使用空闲线程,没有空闲线程时,新建线程。线程池核心长度为0,最大长度为Integer.MAX_VALUE
。
用户可以传入线程池的核心线程数(最小线程数),最大线程数量,保持时间,时间单位,阻塞队列这些参数,最大线程数设置为jvm可用的cpu数为宜。
4)ScheduledThreadPool
一个可定期或者延时执行任务的线程池
5) WorkStealingPool
创建拥有足够线程数的线程池来维持相应的并行级别,通过使用队列来减少竞争,默认为CPU的数量。它会通过工作窃取的方式,使得多核CPU不会闲置,总会有活着的线程让CPU去运行。
所谓工作窃取,指的是闲置的线程去处理本不属于它的任务。
三
在实际项目里,我们很少成规模的手动创建线程,一般都是使用线程池(池技术)将线程的创建、销毁、保留及其它管理工作交给线程池,用户无需知道其内部细节,即可使用。