在一个单独线程中运行一个任务需要三步:
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
这是一种过时的创建方式,因为这种方法将要执行的任务和并行运行的机制绑定了,应该对二者解耦。
class MyThread extends Thread{ public void run(){ // task } }
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 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。但这个优先级依赖于系统的实现,所以实践中并不一定有效。