1.线程的创建和运行
① 直接使用thread
Thread t1 = new Thread("thread1") {
@Override
public void run() {
// todo
}
};
t1.start();
② 使用runable
Runnable r1 = new Runnable() {
public void run() {
// todo
}
};
new Thread(r1).start();
// lambada简化后
new Thread(() -> {
// todo
}).start();
③ 使用callable+FutureTask
callable有返回值,会抛异常
FutureTask<String> task1 = new FutureTask<>(() -> {
// todo
return "complete";
});
new Thread(task1).start();
try {
System.out.println(task1.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
2.线程与栈帧
每个线程启动后,虚拟机会为其分配一块栈内存。线程每次调用方法时会生成一块栈帧,栈帧包含局部变量表、操作数栈、返回地址、锁记录等信息。
当发生线程上下文切换时,会保存当前线程状态(线程的程序计数器和每个栈帧信息),再次获得执行权时恢复。
3.线程常见方法
① run和start:run只是调用一次,并没有启动新线程。
② sleep:线程进入TimedWaiting状态,让出cpu资源。可被其它线程的interrupt方法打断,被打断后抛出InterruptedException。睡眠结束后变成Runnable状态,不一定得到执行。
③ yeild:让出当前线程cpu使用权,由Running状态转换为Runnable。
④ setPriority:设置线程优先级,优先级高的线程得到执行的概率比较高。
⑤ join:等待join线程执行完毕再向下执行,同步;可使用时间参数,限制最大等待毫秒数。
join源码可以看到,是调用了wait方法,wait是Object类的方法,需要synchronized先上锁,然后在锁对象的等待区等待被唤醒。这里非静态方法上锁,锁对象是线程对象本身。例如:main线程调用t1.join(),main线程就进入t1线程的等待区等待,由于线程执行完毕后会唤醒在其等待区等待的所有线程,main线程就会在t1线程执行完毕后继续向下执行。
⑥ interrupt:打断sleep、wait、join中的线程,并清除打断标记(因此需采取两阶段打断模式:在被其它线程打断并捕获InterruptedException后再次调用当前线程的interrupt方法将自己的打断标记置为true,后续通过判断打断标记料理锁资源释放等后事)。
打断正常运行线程或LockSupport.park中的线程时只会修改打断标记为true。
⑦ isInterrupted:返回线程打断标记。
⑧ interrupted:返回线程打断标记,并将打断标记置为false。
⑨ setDaemon:将线程设置为守护线程,当没有其它线程运行时守护线程会立即终止。
4.线程的状态
Thread类的内部枚举State规定了Java线程的6种状态
① NEW:创建但未启动
② RUNNABLE:得到执行的线程,也可能在等待处理器分配资源(包含运行中、可运行和操作系统层面的阻塞(如BIO))
③ BLOCKED:阻塞,等待monitor锁去进入/重入临界代码块
④ WAITING:无时限等待,如无参的wait/join/LockSupport.park
⑤ TIMED_WAITING:有时限的等待,含超时参数的sleep/wait/join/LockSupport.parkNanos/LockSupport.parkUnitil
⑥ TERMINATED:终止,执行完毕