线程的创建:
常用的有三种方法:
方法一:直接使用Tread,重写run方法
// 创建线程对象
Thread t = new Thread(){
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
方法二:使用 Runnable 配合 Thread
- Thread 代表线程
- Runnable 可运行的任务(线程要执行的代码)
这种方法将线程和任务分离开
Runnable runnable = new Runnable() {
public void run(){ // 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread( runnable,"t2" );
// 启动线程
t.start();
Java 8 以后可以使用 lambda 精简代码
方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
用 Runnable 更容易与线程池等高级 API 配合
用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
new Thread(()->{
System.out.println("ok");
},"t2").start();
因为Runnable接口只有一个抽象方法:
//Runnable源码
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
方法三:FutureTask 配合 Thread
FutureTask<Integer> task=new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1;
}
});
new Thread(task).start();
int res=task.get();
线程的常用API:
1.start()
启动一个新线 程,在新的线程 运行 run 方法 中的代码。
注意:
start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException
注意:
启动线程必须使用此方法,直接调用run()方法是同步的,不能启动线程
2.join()
等待线程运行结束
- join(long n)
等待线程运行结 束,多等待 n 毫秒
4.isInterrupted()
判断是否被打断,不会清楚打断标记
5.interrupted()
判断当前线程是 否被打断 会清除打断标记
6.currentThread()
获取当前正在执 行的线程
7.sleep(long n)
让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程
8.yield()
提示线程调度器 让出当前线程对 CPU的使用,
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程 。
但是不能保证有效果,因为优先级的缘故
9.interrupt()
打断线程
10.线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它 如果
- cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
补充:
对比:
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException ,有一个特点 (如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除打断标记,park方法是不会清楚打断标记的而且如果标记为true的话park会失效;如果打断的正在运行的线程,则会设置打断标记 )
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器
守护线程的使用:
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守 护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
log.debug("开始运行...");
Thread t1 = new Thread(() -> {
log.debug("开始运行...");
sleep(2);
log.debug("运行结束...");
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
sleep(1);
log.debug("运行结束...");
输出
08:26:38.123 [main] c.TestDaemon - 开始运行…
08:26:38.213 [daemon]c.TestDaemon - 开始运行…
08:26:39.215 [main] c.TestDaemon - 运行结束…
注意
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求
有一些过时方法已经不再使用:
//以下代码都容易破坏同步代码块,造成锁得不到释放
//阻塞唤醒方法我们使用更好的wait和notify来解决
stop();
suspend();
resume();
线程的状态:
Java 线程6种状态
1. 新建(NEW):创建后尚未启动。
2.运行(RUNNABLE):该状态包含了操作系统线程状态中的 Running 和 Ready;即可能正在运行,也可能正在等待 CPU 时间片。
3.无限期等待(WAITING):等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
4.限期等待(TIMED_WAITING):无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
5.阻塞(BLOCKED):等待获取一个排它锁,如果其线程释放了锁就会结束此状态。
6.结束(TERMINATED):线程结束任务之后自己结束,或者产生了异常而结束。