一、线程创建
- 第一种方式:继承Thread类,覆写run( )
- run( )是线程操作的核心方法,是每个线程的入口
- run()是由JVM创建完本地操作系统级线程后回调的方法,不可手工调用,否则就是普通方法
- 通过调用Thread类的start()启动线程,一个线程只能调用一次start( ),否则会抛出线程状态异常
- 通过start( )调用start0():private native void start0();native声明的方法没有方法体,只有方法声明,但本地方法不是抽象方法,而是Java调用c语言
- registerNatives():包含所有与线程有关的操作系统级方法
- 代码实现:
package one;
class MyThread1 extends Thread{
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("线程1 "+i);
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("线程2 "+i);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
myThread1.start();
myThread2.start();
}
}
- 第二种方式:实现Runnable接口,覆写run()(推荐)
- Thread类提供了构造方法接收Runnable接口的对象
- Runnable接口中仅有一个方法,run()
- 用此方法可以避免单继承局限
- 当子类实现Runnable接口,此时子类与Thread类就是典型的代理设计模式,其中,子类负责真实线程业务操作,Thread类负责资源调度与创建线程来辅助
- 代码实现:方式1
package one;
class MyThread3 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("线程1 "+i);
}
}
}
class MyThread4 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("线程2 "+i);
}
}
}
public class Test1 {
public static void main(String[] args) {
//方式1
MyThread3 myThread1 = new MyThread3();
MyThread4 myThread2 = new MyThread4();
new Thread(myThread1).start();
new Thread(myThread2).start();
}
}
5. 代码实现:方式2:Runnable接口中仅有一个函数,所以可以使用函数式编程
public class Test1 {
public static void main(String[] args) {
//方式2:Runnable接口中仅有一个函数,函数式编程
Runnable runnable = () -> {
System.out.println("hello");
};
new Thread(runnable).start();
}
}
输出"hello"。
6. 代码实现:方式3:匿名内部类
public class Test1 {
public static void main(String[] args) {
//方式3:匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}).start();
}
}
输出"hello"。
前两种方式(继承Thread类和实现Runnable接口)的区别:
- 实现Runnable接口可以避免单继承局限;
- 实现Runnable接口可以更好的体现程序共享的概念
- 第三种方式:覆写Callable接口(JDK1.5新增的接口)
- 核心方法:call(),有返回值;
- 该方式的特点:call()有返回值,可以查看线程执行完的一些返回结果
- 由于run( )是启动线程的核心,而Thread类仅提供了接收Runnable接口对象的构造方法,所以需要将Callable接口的对象与Runnable接口联系起来
- FutureTask类就是连接两个接口的关键,FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnabe接口,至此连接起了Runnable接口;而FutureTask类又提供了可以接收Callable接口对象的方法,至此连接起了Callable接口;综上所诉,即用FutureTask类接收Callable接口对象,再用Thread类接收FutureTask类对象,启动线程。
- 实现:模拟卖票过程
package one;
//覆写Callable
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread5 implements Callable<String>{
private int ticket = 20;
@Override
public String call() throws Exception {
while(ticket > 0) {
System.out.println(ticket--);
}
return "ticket:0";
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> callable = new MyThread5();
//联系
//FutureTask类实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口
//FutureTask类可以接收Callable接口对象
FutureTask<String> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
System.out.println(futureTask.get());//获取call()返回信息
new Thread(futureTask).start();
}
}
在线程执行完返回ticket还有0张的情况
- 第四种方式:线程池(JDK1.5)
1、线程池就是多个线程封装在一起操作
2、三大优点:
1)降低资源消耗,通过重复利用与创建的线程降低线程创建与销毁带来的消耗
2)提高响应速度,当任务到达时,不需要等待线程创建就可立即执行
3)提高线程的可管理性,使用线程池可以统一进行线程分配、调度与监控
3、线程池分类
1)普通线程池:通过Executor(线程的执行机制)的工具类Exectors,可以取得3种普通线程池
- FixedThreadPool(int nThread):固定大小线程池;适用于为了满足资源管理需求而需要限制当前线程数量的场合;适用于负载比较重的服务器;
- SingleThreadPoolExecutor:单线程池;适用于需要保证顺序执行各个任务的场景;
- CachedThreadPool:缓存线程池;当提交任务速度 >= 线程池中任务处理速度时,缓冲线程池会不断创建线程;适用于执行很多短期的异步小程序以及负载较轻的服务器
2)调度线程池:ScheduleExecutorService
- newScheduledThreadPool(int nThread)
- scheduleAtFixedRate(command, initialDelay, period, unit)
- command-要执行的任务
- initialDelay-任务延迟多少单元后执行
- period-执行周期
- unit-周期单元单位
4、线程池组成:
1)corePool:核心线程池
2)BlockingQueue:阻塞队列
3)MaxPool:线程池容纳的最大线程数量
5、工作方式:
1)如果当前运行的线程数 < corePoolSize;则创建新的线程执行任务,而后将此线程放入corePool
2)如果当前线程数 >= corePoolSize;则将任务放入阻塞队列等待调度执行(95%:大多都是此类情况)
3)如果阻塞队列已满,则试图创建新的线程来执行(需要全局锁)
4)如果创建线程后,总线程数 > maxPoolSize;则任务被拒接,调用拒绝策略返回给用户
6、工作线程:线程池创建线程时,将线程封装为Worker,Worker在执行完后,还会循环从工作队列中取得任务来执行
7、手工创建普通线程池
ThreadPoolExecutor tpe = new ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
)
1)corePoolSize
线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务
即使其他空闲的基本线程能够执行任务也会创建新线程,
直到当前线程池中的线程数量 > 基本大小才不会创建
2)BlockingQueue
任务队列,用于保存等待执行任务的阻塞队列
a、ArrayBlockingQueue:基于数组的有界阻塞队列,按照FIFO对元素进行排序
b、LinkedBlockingQueue:基于链表结构的无界阻塞队列,吞吐量高于ArrayBlockingQueue
FixedThreadPool()、singleThreadPool()都采用此队列
c、synchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程移除操作,否则插入操作一直处于阻塞状态,吞吐量 > LinkedBlockingQueue;CachedThreadPool()采用此队列
d、priorityBlockingQueue:具有优先级的无界阻塞队列
3)keepAliveTime(线程活动时间)
线程池的工作线程空闲后,保持存活的时间。
4)TimeUnit:keepAliveTime的时间单位
5)RejectedExecutionHandler(饱和策略):线程池满时无法处理新任务的执行策略,
线程池默认采用AbortPolicy(抛出异常)-可以省略此参数
8、线程池操作
1)向线程池提交任务
- execute(Runnable run):用于提交不需要返回值的任务,所以无法判断处任务是否被线程池执行成功;
- submit(Runnable/Callable):返回Future对象,用于提交需要返回值的任务,可以通过future对象判断任务是否被执行成功
future.get()会阻塞当前线程,知道任务全部执行完毕
2)手工关闭线程池
- shotdown():停止所有没有正在执行任务的线程(推荐)
- shotdownNow():停止所有正在执行以及空闲线程
二、线程状态的转换