Java线程
runnable 创建线程
在一个单独线程中运行一个任务需要三步:
1.需要一个实现了Runnable
接口的类,将这个任务需要执行的代码放到这个类的run
方法中。
public class multithread implements Runnable{
public static void main(String[] args) {
multithread m = new multithread();
Thread thread = new Thread(m);
thread.start();
}
@Override
public void run() {
System.out.println(123);
}
}
因为Runnable
是函数式接口,所以可以用lambda表达式代替实现这个接口。
public class multithread{
public static void main(String[] args) {
Runnable r = () -> { System.out.println(123); };
Thread t = new Thread(r);
t.start();
}
}
2.从Runnable
构造Thread
对象。
3.启动线程。
下面让两个线程同时运行:
public class multithread{
public static void main(String[] args) {
Runnable r1 = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("thread 1: " + i);
}
};
Runnable r2 = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("thread 2: " + i);
}
};
new Thread(r1).start();
new Thread(r2).start();
}
}
结果是交替出现的,说明确实是并发运行。
thread 1: 0
thread 1: 1
thread 1: 2
thread 1: 3
thread 2: 0
thread 2: 1
thread 2: 2
thread 2: 3
thread 2: 4
thread 1: 4
继承thread创建线程
这是一种过时的创建方式,因为这种方法将要执行的任务和并行运行的机制绑定了,应该对二者解耦。
class MyThread extends Thread{
public void run(){
// task
}
}
调用thread或runnable的run方法
public class multithread{
public static void main(String[] args) {
Runnable r1 = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("thread 1: " + i);
}
};
Runnable r2 = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("thread 2: " + i);
}
};
r1.run();
new Thread(r1).start();
new Thread(r2).start();
}
}
这样做不会创建一个新的线程去执行任务,还是在原来的线程执行。
thread 1: 0
thread 1: 1
thread 1: 2
thread 1: 3
thread 1: 4 // 在main线程执行完r1的run才能执行后面的并行操作
thread 1: 0
thread 2: 0
thread 2: 1
thread 2: 2
thread 2: 3
thread 2: 4
thread 1: 1
thread 1: 2
thread 1: 3
thread 1: 4
线程状态
六种状态:
- New
- Runnable
- Blocked
- Waiting
- Timed waiting
- Terminated
下面对每种状态进行详细描述。
新建
new Thread(r)
之后对线程处于新建状态,没有开始运行。
可运行
调用start()
方法之后线程变成可运行的,但不一定正在运行。操作系统一般使用抢占式调度,根据优先级给每个线程一定的时间片来执行,时间片用完后剥夺运行权。
阻塞和等待
此时的线程是不活动的,也就是不运行代码,需要重新调度来激活。以下是几种导致线程进入阻塞或等待的原因:
- 当线程尝试获取一个被其他线程占有的锁,此线程就会被阻塞,直到其他线程都释放了这个锁且线程调度器允许该线程持有此锁。
- 当 条件
- 调用一些有超时参数的方法会让线程进入计时等待,如
Thread.sleep
、Object.wait
、Thread.join
当线程被重新激活时,调度器会检查它的优先级并安排运行。
终止线程
线程在完成run
方法后自然终止,或因没有捕获的异常意外终止。以前有些方法能暂停或终止线程,如stop
、suspend
、resume
,但这些方法现在都已经废弃。
线程属性
中断线程
除了已经被废弃的方法,Java不能强制终止进程,但是可以用interrupt
和异常机制来实现终止。
对一个线程对象调用interrupt
方法会设置线程的中断状态,每个线程都会不时的检查这个状态
thread.interrupt();
当线程被阻塞就无法检查中断状态,Java引入了InterruptedException
异常,在一个因为调用sleep
或wait
而阻塞的线程上调用interrupt
方法,那个阻塞调用就会被异常中断。被中断的线程可以自己处理异常,比如继续执行或终止线程。
Runnable r = () -> {
try{
// task
}
catch(InterruptedException e){
// thread is interrupted during sleep or wait
}
finally{
// clean up, if require
}
};
如果线程执行的代码中有Thread.sleep()
,那么必须给用try catch捕捉InterruptedException
异常或将异常抛出到run
方法,原因就是上面说的,这些阻塞调用会被异常中断,所以我们需要在代码中处理。
守护线程
守护线程是给其他线程提供服务的,比如计时器线程、清空过期缓存项的线程等。当进程中只剩下守护线程,虚拟机就会退出。在线程启动之前调用setDaemon(true)
可以把线程转为守护线程。
线程名
在线程转储时,线程的名字可能比较有用,可以用setName("name")
来设置。
线程优先级
默认,一个线程会继承构造它的线程的优先级,可以用setPriority
设置线程优先级,最小Thread.MIN_PRIORITY
为1,最大为10。但这个优先级依赖于系统的实现,所以实践中并不一定有效。