慢慢来就很简单的线程学习(1)Java线程的基本操作

文章目录

    • 1. 线程是什么, 为什么需要线程, 怎么创建一个线程
        • 1. take is cheap, show me code;
        • 2. 怎么创建一个线程
        • 3. 线程的基本操作
        • 4. 传说中的睡眠排序 SleepSort

何以解忧

1. 线程是什么, 为什么需要线程, 怎么创建一个线程

  1. 在线程之前, 首先我先了解一下什么事进程
    1. 在 OS(操作系统) 中每个独立运行的程序都能称为一个进程
    2. 你可以打开你的任务管理器, 里面运行的就是进程
    3. 你可以一边听音乐, 一遍打游戏, 这就是并发执行. 但实际上单核的 cpu 能同时运行多个程序, 是因为在不停的切换进程, 切换的足够快就能产生同时运行的感觉
  2. 线程是什么
    1. 每个运行的程序都是一个进程, 在一个进程还可以有多个执行单元同时运行, 这就是线程
    2. 进程是资源分配的基本单位,线程是调度的基本单位。
    3. 每个进程里至少有一个线程, 当一个 Java 程序启动时, 就会创建一个线程用来运行我们 main 方法中的代码
  3. 为什么需要线程
    1. 一般线程用来抢占 cpu 的运行资源, 提升效率的, 比如你 1 个线程运行 16s, 可以改为 4 个线程运行 4s(雾).
    2. 某些需要用到异步的场景, 比如你烧水的时候, 不应该等水烧开了再去泡茶, 你可以在水烧开的时候去看电视~.
  4. 怎么创建一个线程, 看下面的例子

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. 怎么创建一个线程

  1. 继承 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();
  1. 实现 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. 线程的基本操作

  1. 给线程起个名字

    1. new Thread(tmpThread, "后台线程"); 在创建是直接赋值
    2. thread.setName(); 设置名字
    3. 获取名字 Thread.currentThread().getName()
  2. 假如想在主线程结束的时候, 其他线程也结束怎么办?

    1. 当我们创建一个线程的时候, 默认是前台线程, 当前台线程都结束是, 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 后台线程等.

  1. 线程的生命周期

    1. 线程的整个生命周期可以分为 5 个状态, 新建 (new), 阻塞 (Blocked), 就绪 (Runnable), 运行 (Running), 死亡 (Terminated);
      慢慢来就很简单的线程学习(1)Java线程的基本操作_第1张图片
    2. 线程新建和死亡不用多说, 主要是 阻塞, 就绪和运行这三个状态的变化.
      1. 很简单理解, 单核 cpu 一次只能执行一个线程, 有些在等着被 cpu 宠幸, 有些被打入冷宫没机会~
      2. 线程创建之后, 调用 start() 就会进入就绪状态 (也称可运行状态), 等系统调度获得 cpu 的使用权.
      3. 运行状态
        1. 只有是就绪状态的才有机会变成运行状态
        2. 为了同时运行, 一个线程不可能长时间占有 cpu, 让线程变为就绪状态, 然后再宠幸一个线程
      4. 阻塞 (被打入冷宫啦 雾), 只有当引发线程阻塞的原因接触后, 才能加入到就绪队列, 被阻塞的原因可能有
        1. 获取不到同步锁时
        2. 调用了阻塞的 IO 方法
        3. 调用了 wait() 方法
        4. sleep() 睡觉了
        5. join() 加入另一个线程之后, 等另一个线程执行完后, 才会结束阻塞.
  2. 线程的优先级

    Thread minPriority = new Thread(new Thread1(), "高优先级");
    Thread maxPriority = new Thread(new Thread2(), "低优先级");
    minPriority.setPriotity(Thread.MIN_PRIORITY);
    maxPriority.setPriotity(10);
    // 当高优先级的运行完之后, 低优先级才运行
    // 值得注意的是, 这种方式并不是很靠谱, 有些 OS 并不太支持
  1. 线程休眠
    1. Thread.sleep(1000); // 毫秒值, 当前运行的线程休眠
  2. 线程让步 yield() 让 cpu 重新选择一次执行的线程
  3. 线程插队 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(() -> {
                try {
                    Thread.sleep(arr[finalI]);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.print(arr[finalI] + ", ");
            }).start();
        }
    }

    public static void main(String[] args) {
        new SleepSort().doSort();
    }
}

你可能感兴趣的:(thread)