线程:
进程:
并行:多个线程同时执行,一手画圆,一手画方
并发:每个线程分配一定的时间执行,一会画圆,一会画方
同步:需要等待返回结果,才能继续运行
异步:不需要等待返回结果,就能继续运行
异步的小例子,主线程不需要等待另外一个线程执行结束就可以直接向下执行:
public static void main(String[] args) throws ClassNotFoundException {
new Thread(() -> {
try {
long start = System.currentTimeMillis();
Thread.sleep(2000);
System.out.println("我执行完了, 执行时间: " + (System.currentTimeMillis() - start) + " ms");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
System.out.println("hello");
}
参考我之前写的:Java 创建线程的三种方式
这里补充一些细节:
task.get()
会使当前线程阻塞,一直等到结果返回才继续往下执行。public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("线程执行");
Thread.sleep(2000);
return 200;
}
});
new Thread(task, "线程1").start();
log.debug("{}", task.get());
log.debug("执行结束!");
}
简化版:
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<Integer>(() -> {
log.debug("线程执行");
Thread.sleep(2000);
return 200;
});
new Thread(task, "线程1").start();
log.debug("{}", task.get());
log.debug("执行结束!");
}
Linux:
Java:
每个线程启动 JVM 会分配给该线程一块虚拟机栈内存,每个线程只能有一个活动栈帧,对应当前执行的方法。
切换时机:
上下文切换需要记录当前线程的状态,JVM 实现此方法的就是程序计数器。
线程不是越多越好,频繁的上下文切换也会导致性能下降。
start()
:启动一个新线程,让一个线程进入就绪,不一定会立刻运行(CPU 时间片还没分配)run()
:线程调用 start() 后会自动调用该方法,不能手动调用,手动调用就相当于一个普通方法来使用join()
:等待线程运行结束join(long n)
:等待线程运行结束,最多等待 n 毫秒setPriority(int)
:设置优先级,默认为 5,最高为 10getState()
: 获取线程状态,Java 中共有 6 种状态public enum State {
NEW, // 新建
RUNNABLE, // 可运行线程的线程状态。处于可运行状态的线程正在JVM中执行
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 限时等待
TERMINATED; // 终止
}
isInterrupted()
:是否被打断,不清除打断标记isAlive()
: 线程是否存活interrupt()
:打断线程,若被打断的线程正在 sleep,wait,join,则会导致被打断的线程抛出 InterruptException
,并清除打断标记interrupted()
:判断当前线程是否被打断,清除打断标记sleep(long n)
:当前线程休眠 n 毫秒,让出 cpu 的时间片给其他线程yield()
:提示线程调度器让出当前线程对 CPU 的使用,主要为了测试和调试public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<>(() -> {
log.debug("t1 线程执行完毕!");
return 200;
});
new Thread(task, "t1").run();
log.debug("main 执行完毕!");
}
主动调用 run()
就相当于调用一个普通方法一样,不会以多线程的方式去执行。
正确的方式应该调用 start()
方法,然后让系统自动调用 run()
方法。
start()
不能被调用两次,会抛出 IllegalThreadStateException
。
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<>(() -> {
log.debug("t1 线程执行完毕!");
return 200;
});
Thread t1 = new Thread(task, "t1");
t1.start();
t1.start();
log.debug("main 执行完毕!");
}
start()
之前是 NEW
状态start()
之后是 RUNNABLE
状态。sleep:
正在运行
进入 TIMED_WAITING
interrupt()
方法打断正在睡眠的线程会抛出 InterruptedException
public static void main(String[] args) throws InterruptedException {
FutureTask<Integer> task = new FutureTask<>(() -> {
try {
log.debug("t1 进入睡眠...");
Thread.sleep(2000);
} catch (Exception e) {
log.debug("t1 醒来");
e.printStackTrace();
return 201;
}
return 200;
});
Thread t1 = new Thread(task, "t1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
log.debug("main 执行完毕!");
}
4. ★建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
TimeUnit.SECONDS.sleep(2); // 表示当前线程睡 2 秒
yield:
正在运行
进入 RUNNABLE
,然后调度执行其他同等优先级的线程,若没有同等优先级的线程,则不能保证让当前线程暂停防止 while(true)
导致 CPU 空转,浪费资源。可以使用 yield 或者 sleep 来让出 CPU 的使用权给其他程序,即使死循环 sleep(1) 也能大大减少 CPU 的利用。
在当前线程中调用另外一个线程的 join()
方法,表示等待另外一个线程结束后,当前线程才继续往下执行。
@Slf4j
public class InitTest {
static int a = 0;
public static void main(String[] args) throws InterruptedException {
FutureTask<Integer> task = new FutureTask<>(() -> {
log.debug("t1 线程开始执行...");
TimeUnit.SECONDS.sleep(1);
a = 10;
return 0;
});
Thread t1 = new Thread(task, "t1");
t1.start();
// 调用 join 方法同步等待 t1 线程执行结束
t1.join();
log.debug("a 的值为: {}", a);
}
}
若以异常的方式打断 wait(), sleep(), join(),会把打断标记置为 false
:
@Slf4j
public class InitTest {
public static void main(String[] args) throws InterruptedException {
FutureTask<Integer> task = new FutureTask<>(() -> {
try {
log.debug("t1 进入睡眠...");
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
log.debug("t1 醒来");
e.printStackTrace();
}
return 200;
});
Thread t1 = new Thread(task, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
log.debug("是否打断: {}", t1.isInterrupted());
}
}
正常打断(可以用于正常停止线程):
@Slf4j
public class InitTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted) {
log.debug("线程 t1 被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
log.debug("t1 线程是否被打断: {}", t1.isInterrupted());
}
}
两阶段停止:
在一个线程 T1 中如何 “优雅” 停止一个 T2?
stop()
方法(已弃用),如果 T2 锁住共享资源,那么 T2 被杀死后将无法释放锁System.exit(int)
方法停止线程,该方法会让整个程序都停止/**
* 后台监控程序, 两阶段提交
*/
@Slf4j(topic = "c.InitTest")
public class InitTest {
public static void main(String[] args) throws InterruptedException {
MonitorThread monitorThread = new MonitorThread();
monitorThread.start();
TimeUnit.SECONDS.sleep(5);
monitorThread.stop();
}
}
@Slf4j(topic = "c.MonitorThread")
class MonitorThread {
private Thread monitor;
// 启动监控
public void start() {
monitor = new Thread(() -> {
while (true) {
Thread thread = Thread.currentThread();
if (thread.isInterrupted()) {
log.debug("准备退出......");
break;
}
try {
TimeUnit.SECONDS.sleep(2); // case 1 :该处被打断, 打断标记还是 false
log.debug("执行监控记录"); // case 2 : 该处被打断, 打断标记置为 true
} catch (InterruptedException e) {
log.debug("睡眠时间被打断, 清除打断标记");
e.printStackTrace();
thread.interrupt(); // case 2 : 重新设置打断标记
}
}
});
monitor.start();
}
// 停止监控
public void stop() {
monitor.interrupt();
}
}
打断 park() 中的线程,park()
方法若被打断,再次 park()
将执行失效,如果想再次 park()
可以使用 Thread.interrupted()
清除标记:
@Slf4j(topic = "c.InitTest")
public class InitTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("park......");
LockSupport.park();
log.debug("unpark.....");
log.debug("当前线程是否被打断: {}", Thread.currentThread().isInterrupted());
LockSupport.park(); // 若被打断就无法再次 park
log.debug("unpark.....");
});
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
}
}
以下方法不推荐使用,容易破坏同步代码块,造成线程死锁:
stop()
:停止线程运行suspend()
:挂起(暂停)线程运行resume()
:恢复线程运行