ToB企服应用市场:ToB评测及商务社交产业平台

标题: Java---线程入门 [打印本页]

作者: 美食家大橙子    时间: 2022-9-17 08:33
标题: Java---线程入门
前置知识

什么是进程,什么又是线程?咱不是讲系统,简单说下,知道个大概就好了。
进程:一个可执行文件执行的过程。
线程:操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
什么是并行,什么是并发?这个也简单说下。
并行:cpu的两个核心分别执行两个线程。
并发:cpu的一个核心在两个(或多个)线程上反复横跳执行。
线程的创建

继承Thread
  1. // 声明
  2. class T extends Thread {
  3.     public void run() {
  4.         // do something
  5.     }
  6. }
  7. // 使用
  8. new T().start();
复制代码
实现Runnable
  1. // 声明
  2. class T implements Runnable {
  3.     public void run() {
  4.         // do something
  5.     }
  6. }
  7. // 使用
  8. new Thread(new T()).start();
复制代码
为什么是start,而不是run,其实run只是个很普通的方法,我们来看看start的源码。
  1. public synchronized void start() {
  2.     if (threadStatus != 0)
  3.         throw new IllegalThreadStateException();
  4.     group.add(this);
  5.     boolean started = false;
  6.     try {
  7.         start0(); // 这个才是开启线程
  8.         started = true;
  9.     } finally {
  10.         try {
  11.             if (!started) {
  12.                 group.threadStartFailed(this);
  13.             }
  14.         } catch (Throwable ignore) {
  15.             /* do nothing. If start0 threw a Throwable then
  16.                   it will be passed up the call stack */
  17.         }
  18.     }
  19. }
  20. // start0的实现
  21. private native void start0(); // 这是一个native方法,通常使用C/C++来实现
复制代码
多线程机流程(从启动到终止)

我们通过一个案例来说明。
顺便说说sleep()
  1. public class ThreadTest {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         new Thread(new T0(), "T0").start();
  4.         int cnt = 0;
  5.         while (cnt < 50) {
  6.             cnt ++;
  7.             System.out.println(Thread.currentThread().getName());
  8.             Thread.sleep(200); // 让当前线程停止200毫秒
  9.         }
  10.     }
  11. }
  12. class T0 implements Runnable {
  13.     int cnt;
  14.     @Override
  15.     public void run() {
  16.         while (cnt < 50) {
  17.             cnt ++;
  18.             System.out.println(Thread.currentThread().getName());
  19.             try {
  20.                 Thread.sleep(200); // 让当前线程停止200毫秒
  21.             } catch (InterruptedException e) {
  22.                 e.printStackTrace();
  23.             }
  24.         }
  25.     }
  26. }
复制代码
执行流程:
线程常用方法

线程终止

虽然Thread中提供了一个stop方法用来停止线程,但目前已经被废弃。那么我们如何停止线程呢?我们来看看下面这个例子。
  1. public class ThreadTest {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         T0 t0 = new T0();
  4.         new Thread(t0, "T0").start();
  5.         int cnt = 0;
  6.         while (cnt < 50) {
  7.             cnt ++;
  8.             System.out.println(Thread.currentThread().getName());
  9.         }
  10.         t0.flag = false; // 设置为false用以跳出T0线程的循环
  11.         /*
  12.                 在main线程执行了50次后退出T0线程,接着退出main线程
  13.          */
  14.     }
  15. }
  16. class T0 implements Runnable {
  17.     boolean flag = true; // 定义一个标记,用来控制线程是否停止
  18.     @Override
  19.     public void run() {
  20.         while (flag) {
  21.             System.out.println(Thread.currentThread().getName());
  22.         }
  23.     }
  24. }
复制代码
通过定义一个标记flag让线程退出。
线程中断

Thread中有一个interrupt方法,这个方法不是说中断线程的运行,而是中断线程当前执行的操作。我们来看下样例。
  1. public class ThreadTest {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         T0 t0 = new T0();
  4.         Thread thread0 = new Thread(t0, "T0");
  5.         thread0.start();
  6.         System.out.println("张三在划水。。。");
  7.         Thread.sleep(2000);
  8.         thread0.interrupt(); // 通过抛出一个InterruptedException异常来打断当前操作(sleep)
  9.     }
  10. }
  11. class T0 implements Runnable {
  12.     boolean flag = true;
  13.     @Override
  14.     public void run() {
  15.         System.out.println("李四在打盹。。。");
  16.         while (flag) {
  17.             try {
  18.                 Thread.sleep(20000); // 2秒后被interrupt中断
  19.             } catch (InterruptedException e) {
  20.                 flag = false;
  21.                 System.out.println("老板来了,张三摇醒了李四。。。"); // 由于main线程调用了interrupt,实际过了2秒就输出了
  22.             }
  23.         }
  24.     }
  25. }
复制代码
输出结果:
  1. 张三在划水。。。
  2. 李四在打盹。。。
  3. 老板来了,张三摇醒了李四。。。
复制代码
线程让步

Thread类中提供了yield方法,用来礼让cpu资源。礼让了资源就会执行其他线程吗?我们看看这个例子
  1. public class ThreadTest {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         T0 t0 = new T0();
  4.         Thread thread0 = new Thread(t0, "T0");
  5.         thread0.start();
  6.         int cnt = 0;
  7.         while (cnt < 5) {
  8.             cnt ++;
  9.             System.out.println(Thread.currentThread().getName());
  10.             Thread.yield(); // 放弃当前的cpu资源,让cpu重新分配资源。
  11.         }
  12.     }
  13. }
  14. class T0 implements Runnable {
  15.     int cnt;
  16.     @Override
  17.     public void run() {
  18.         int cur = 0; // 连续吃的包子数
  19.         while (cnt < 5) {
  20.             cnt ++;
  21.             System.out.println(Thread.currentThread().getName());
  22.             Thread.yield(); // 放弃当前的cpu资源,让cpu重新分配资源。
  23.         }
  24.     }
  25. }
复制代码
输出结果:
  1. main
  2. T0
  3. main
  4. main
  5. main
  6. main
  7. T0
  8. T0
  9. T0
  10. T0
复制代码
可以看到两个线程互相礼让,如果yield方法会强制执行其他线程的话,那线程应该会交替执行,而不是有连续执行同一个线程的情况。所以证明了yield并不是强制礼让。
线程插队

Thread类提供了join方法,可以指定一个线程优先执行。
  1. public class ThreadTest {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Thread thread0 = new Thread(new T0(), "T0");
  4.         thread0.start();
  5.         Thread thread1 = new Thread(new T0(), "T1");
  6.         thread1.start();
  7.         int cnt = 0;
  8.         while (cnt < 2) {
  9.             cnt ++;
  10.             System.out.println(Thread.currentThread().getName());
  11.             thread0.join(); // 让线程thread0插队,执行完thread0的所有任务后回到当前线程。
  12.         }
  13.     }
  14. }
  15. class T0 implements Runnable {
  16.     int cnt;
  17.     @Override
  18.     public void run() {
  19.         int cur = 0;
  20.         while (cnt < 2) {
  21.             cnt ++;
  22.             System.out.println(Thread.currentThread().getName());
  23.         }
  24.     }
  25. }
复制代码
输出结果:
  1. main
  2. T1
  3. T1
  4. T0
  5. T0
  6. main
复制代码
总体上main线程确实被T0插队了,但为啥T1在T0的前面被执行?因为当前是多核CPU的环境,其它的核心在执行剩下的线程,执行T1线程的核心比T0的快所以T1在T0之前被输出。不止有并发,还有并行。在单纯并发的条件下,就变成了T0的所有任务都执行完毕后,才会执行其他线程。当前的main与T0是并发的,与T1是并行。
守护线程

Thread类提供setDaemon方法,可以设置目标线程为当前线程的守护线程,当前线程终止时,目标(守护)线程也随之终止。
  1. public class ThreadTest {
  2.     public static void main(String[] args) throws InterruptedException {
  3.         Thread thread0 = new Thread(new T0(), "T0");
  4.         thread0.setDaemon(true);
  5.         thread0.start();
  6.         System.out.println("张三 --> 新一天的工作开始了");
  7.         int time = 0;
  8.         while (time < 8) { // 张三每天工作八个小时。。。
  9.             time ++;
  10.         }
  11.         System.out.println("张三 --> 下班了,回家吃老婆做的饭咯");
  12.         System.out.println("小红 --> 我老公张三下班了,今天就到这了");
  13.         System.out.println("小红 --> 守护线程YYDS");
  14.     }
  15. }
  16. class T0 implements Runnable {
  17.     int cnt;
  18.     @Override
  19.     public void run() {
  20.         System.out.println("小红 --> 李四来我家甜蜜双排王者荣耀");
  21.         while (true);
  22.     }
  23. }
复制代码
输出结果:
  1. 张三 --> 新一天的工作开始了
  2. 小红 --> 李四来我家甜蜜双排王者荣耀
  3. 张三 --> 下班了,回家吃老婆做的饭咯
  4. 小红 --> 我老公张三下班了,今天就到这了
  5. 小红 --> 守护线程YYDS
复制代码
当main线程终止时,T0线程也终止。
线程的状态

5种状态是OS的线程状态。而6种则说的是JVM的线程状态。以下是状态图。

OS的线程状态为粗体
JVM的线程状态为英文
线程同步机制

想看个经典问题。
多线程售票问题

我们来看看案例。
  1. public class ThreadTest {
  2.     public static void main(String[] args) {
  3.         T0 t0 = new T0();
  4.         Thread thread0 = new Thread(t0, "张三");
  5.         Thread thread1 = new Thread(t0, "李四");
  6.         thread0.start();
  7.         thread1.start();
  8.     }
  9. }
  10. class T0 implements Runnable {
  11.     int ticket = 100;
  12.     @Override
  13.     public void run() {
  14.         while (ticket > 0) {
  15.             try {
  16.                 Thread.sleep(10);
  17.             } catch (InterruptedException e) {
  18.                 e.printStackTrace();
  19.             }
  20.             System.out.printf("%s买了一张票,还剩%d张%n", Thread.currentThread().getName(), -- ticket);
  21.         }
  22.     }
  23. }
复制代码
输出结果:
  1. ...
  2. 张三买了一张票,还剩3张
  3. 张三买了一张票,还剩2张
  4. 李四买了一张票,还剩1张
  5. 张三买了一张票,还剩0张
  6. 李四买了一张票,还剩0张
复制代码
出现了重复卖票,造成这种现象的原因是线程不安全。那如何让线程安全呢。这就是接下来要介绍的互斥锁。
互斥锁

java提供了synchronized关键字用以开启锁。看看如何使用锁解决上面的线程安全问题。
  1. public class ThreadTest {
  2.     public static void main(String[] args) {
  3.         T0 t0 = new T0();
  4.         Thread thread0 = new Thread(t0, "张三");
  5.         Thread thread1 = new Thread(t0, "李四");
  6.         thread0.start();
  7.         thread1.start();
  8.     }
  9. }
  10. class T0 implements Runnable {
  11.     int ticket = 100;
  12.     @Override
  13.     public synchronized void run() { // 我们将锁加在run方法上
  14.         while (ticket > 0) {
  15.             try {
  16.                 Thread.sleep(10);
  17.             } catch (InterruptedException e) {
  18.                 e.printStackTrace();
  19.             }
  20.             System.out.printf("%s买了一张票,还剩%d张%n", Thread.currentThread().getName(), -- ticket);
  21.         }
  22.     }
  23. }
复制代码
输出结果:
  1. ...
  2. 张三买了一张票,还剩4张
  3. 张三买了一张票,还剩3张
  4. 张三买了一张票,还剩2张
  5. 张三买了一张票,还剩1张
  6. 张三买了一张票,还剩0张
复制代码
所有的输出都在线程张三上,这显然不是我们想要的。
首先,为什么会有这种现象发生?其实,被synchronized修饰的方法或代码块会被上锁,并发环境下先进入该方法或者代码块的线程将获得锁并执行这部分代码,而其他线程则处于阻塞状态直到获得锁的线程执行完被上锁的所有代码后,其他线程才有机会去争夺锁。
上述现象的原因是synchronized修饰了整个方法,所以当张三拿到锁时会执行完所有的循环后释放锁,这时李四就什么都输出不了了,和单线程一样,除了多了个一直阻塞的线程,性能低下。
那么如何保证线程安全的前提下,保证并发的性能呢?第一次尝试解决。
  1. public class ThreadTest {
  2.     public static void main(String[] args) {
  3.         T0 t0 = new T0();
  4.         Thread thread0 = new Thread(t0, "张三");
  5.         Thread thread1 = new Thread(t0, "李四");
  6.         thread0.start();
  7.         thread1.start();
  8.     }
  9. }
  10. class T0 implements Runnable {
  11.     int ticket = 100;
  12.     @Override
  13.     public /*synchronized*/ void run() { // 我们将锁加在run方法上
  14.         while (ticket > 0) {
  15.             try {
  16.                 Thread.sleep(10);
  17.             } catch (InterruptedException e) {
  18.                 e.printStackTrace();
  19.             }
  20.             synchronized(this) { // 存在两个线程执行时都满足ticket>0,仍然有线程安全问题  
  21.                 System.out.printf("%s买了一张票,还剩%d张%n", Thread.currentThread().getName(), -- ticket);
  22.             }
  23.         }
  24.     }
  25. }
复制代码
输出结果:
  1. ...
  2. 李四买了一张票,还剩3张
  3. 李四买了一张票,还剩2张
  4. 张三买了一张票,还剩1张
  5. 张三买了一张票,还剩0张
  6. 李四买了一张票,还剩-1张
复制代码
虽然两个线程恢复了并发,但线程安全问题也随之出现。
第二次尝试解决。
[code]public class ThreadTest {    public static void main(String[] args) {        T0 t0 = new T0();        Thread thread0 = new Thread(t0, "张三");        Thread thread1 = new Thread(t0, "李四");        thread0.start();        thread1.start();    }}class T0 implements Runnable {    int ticket = 100;    @Override    public /*synchronized*/ void run() { // 我们将锁加在run方法上        while (ticket > 0) {            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            synchronized(this) {                if (ticket




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4