文章目录
何以解忧
1. 线程是什么, 为什么需要线程, 怎么创建一个线程
- 在线程之前, 首先我先了解一下什么事进程
- 在 OS(操作系统) 中每个独立运行的程序都能称为一个进程
- 你可以打开你的任务管理器, 里面运行的就是进程
- 你可以一边听音乐, 一遍打游戏, 这就是并发执行. 但实际上单核的 cpu 能同时运行多个程序, 是因为在不停的切换进程, 切换的足够快就能产生同时运行的感觉
- 线程是什么
- 每个运行的程序都是一个进程, 在一个进程还可以有多个执行单元同时运行, 这就是线程
- 进程是资源分配的基本单位,线程是调度的基本单位。
- 每个进程里至少有一个线程, 当一个 Java 程序启动时, 就会创建一个线程用来运行我们 main 方法中的代码
- 为什么需要线程
- 一般线程用来抢占 cpu 的运行资源, 提升效率的, 比如你 1 个线程运行 16s, 可以改为 4 个线程运行 4s(雾).
- 某些需要用到异步的场景, 比如你烧水的时候, 不应该等水烧开了再去泡茶, 你可以在水烧开的时候去看电视~.
- 怎么创建一个线程, 看下面的例子
1. take is cheap, show me code;
public class Example1 {
static class TmpThread implements Runnable{
@Override
public void run() {
while (true) {
try {
// 让这个线程睡眠 1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("TmpThread -> 线程正在运行...");
}
}
}
public static void main(String[] args) {
new Thread(new TmpThread()).start();
System.out.println("主线程运行结束...");
}
}
这个例子的运行结果可能出乎的意料, 如果你在此之前没有接触过线程概念的话;
主线程运行结束...
TmpThread -> 线程正在运行...
TmpThread -> 线程正在运行...
TmpThread -> 线程正在运行...
TmpThread -> 线程正在运行...
在 main(String[] args) 方法运行完后, TmpThread#run() 方法依然在运行. 直到你手动终止这个 Java 程序. 这就是线程, 能单独的运行.
他的运行逻辑如下, 因为死循环, 所以程序不会终止.
start
|
|
main() 被执行
|------- 创建了一个线程 ---------|
| TmpThread#run() 被执行, 死循环打印
end
关于第二个线程执行的生命周期, 放在后面来说明
2. 怎么创建一个线程
- 继承 Thread 这个类, 并重写 run() 方法, 像这样:
class TmpThread2 extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("TmpThread -> 线程正在运行...");
}
}
}
// 运行这个线程只需要用对象执行 start() 方法就行
new TmpThread2().start();
- 实现 Runnable 接口, 实现 run() 方法, 像这样:
class TmpThread implements Runnable{
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("TmpThread -> 线程正在运行...");
}
}
}
// 运行这个线程需要用实现的对象创建 Thread 实例
new Thread(new TmpThread()).start();
- 这两种创建线程的区别并不是太大, Runnable 的实现类能共享一些属性, 而继承的 thread.start() 每个域都是单例的域. 一般常用 implement Runnable 的方式创建线程, (这样也更面对对象, 且灵活一点)
- 注意: 线程的 run() 方法是在线程创建后被执行的逻辑, 而不是创建这个线程并运行, start() 才时. 如果你单独运行 TmpThread#run(), 那和你执行对象里面的方法没有任何区别.
3. 线程的基本操作
-
给线程起个名字
new Thread(tmpThread, "后台线程");
在创建是直接赋值thread.setName();
设置名字- 获取名字
Thread.currentThread().getName()
-
假如想在主线程结束的时候, 其他线程也结束怎么办?
- 当我们创建一个线程的时候, 默认是前台线程, 当前台线程都结束是, Java 程序才会结束, 而后台线程会在前台线程都结束时自动终止掉. 我们只需要将我们创建的线程设置为后台线程就行了.
class TmpThread implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前执行的线程的名字
System.out.println(Thread.currentThread().getName() + "-> 线程正在运行...");
}
}
}
public static void main(String[] args) throws InterruptedException {
TmpThread tmpThread = new TmpThread();
Thread thread = new Thread(tmpThread, "后台线程"); // 将线程设置名字
thread.setDaemon(true);
thread.start();
System.out.println("此线程是后台线程吗?" + thread.isDaemon());
Thread.sleep(5000); // 让主线程睡 5s, 防止直接执行结束看不到子线程的执行
System.out.println("主线程运行结束...");
}
可以看到线程执行了一会之后, 就程序结束了.
此线程是后台线程吗? true
TmpThread -> 线程正在运行...
TmpThread -> 线程正在运行...
TmpThread -> 线程正在运行...
TmpThread -> 线程正在运行...
主线程运行结束...
在 Java 程序里面除了执行 main 方法的前台线程以外, 还有 gc 后台线程等.
-
线程的生命周期
- 线程的整个生命周期可以分为 5 个状态, 新建 (new), 阻塞 (Blocked), 就绪 (Runnable), 运行 (Running), 死亡 (Terminated);
- 线程新建和死亡不用多说, 主要是 阻塞, 就绪和运行这三个状态的变化.
- 很简单理解, 单核 cpu 一次只能执行一个线程, 有些在等着被 cpu 宠幸, 有些被打入冷宫没机会~
- 线程创建之后, 调用 start() 就会进入就绪状态 (也称可运行状态), 等系统调度获得 cpu 的使用权.
- 运行状态
- 只有是就绪状态的才有机会变成运行状态
- 为了同时运行, 一个线程不可能长时间占有 cpu, 让线程变为就绪状态, 然后再宠幸一个线程
- 阻塞 (被打入冷宫啦 雾), 只有当引发线程阻塞的原因接触后, 才能加入到就绪队列, 被阻塞的原因可能有
- 获取不到同步锁时
- 调用了阻塞的 IO 方法
- 调用了 wait() 方法
- sleep() 睡觉了
- join() 加入另一个线程之后, 等另一个线程执行完后, 才会结束阻塞.
- 线程的整个生命周期可以分为 5 个状态, 新建 (new), 阻塞 (Blocked), 就绪 (Runnable), 运行 (Running), 死亡 (Terminated);
-
线程的优先级
Thread minPriority = new Thread(new Thread1(), "高优先级");
Thread maxPriority = new Thread(new Thread2(), "低优先级");
minPriority.setPriotity(Thread.MIN_PRIORITY);
maxPriority.setPriotity(10);
// 当高优先级的运行完之后, 低优先级才运行
// 值得注意的是, 这种方式并不是很靠谱, 有些 OS 并不太支持
- 线程休眠
Thread.sleep(1000); // 毫秒值
, 当前运行的线程休眠
- 线程让步
yield()
让 cpu 重新选择一次执行的线程 - 线程插队
join()
thread1.joio()
将当前线程加入到 thread1 之后, 等 thread1 执行完之后, 才执行此线程
4. 传说中的睡眠排序 SleepSort
/**
* 强吧, 常数级别的时间复杂度, hhh
*/
public class SleepSort {
private int[] arr = new int[]{9, 200, 1, 2, 10, 10, 56, 23, 9537};
private void doSort() {
for (int i = 0; i < arr.length; i++) {
int finalI = i;
new Thread(() -> {
ThreadUtil.sleep(arr[finalI]);
System.out.print(arr[finalI] + ", ");
}).start();
}
}
public static void main(String[] args) {
new SleepSort().doSort();
}
}