(必须掌握的多线程知识点)认识线程,创建线程,使用Thread的常见方法及属性,以及线程的状态和状态转移的意义

一.认识线程

1.1 概念

我们设想如下场景: 一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会
忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分
别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一
家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队
执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。
那这个和多进程又有什么区别的,最大的区别就是这些执行流之间是否有资源的共享。比如之前的多进程例子中,每
个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他人取走了么。而上面
我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是一家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最大区别。

进程是系统分配资源的最小单位,
线程是系统调度的最小单位。
一个进程内的线程之间是可以共享资源的。
每个进程至少有一个线程存在,即主线程(此处主线程为C语言的入口函数级别的线程)。

  • 我们以前都是写完代码然后直接运行得到结果,那么当你run或者debug时,是启动了什么程序?你知道真实的java进程的运行过程吗?
    |
    |
    .首先运行main方法(本质是调用java包名.类名 来启动java程序——》启动之后就相当于java进程运行起来了)

    那么接下来进程直接执行代码行输出结果吗??

    当然不是!!

    比如此时java程序是在windows上运行,这个java.exe是用C语言编写的,此时java命令代码执行是启动系统main方法,一般是C语言的main入口函数(系统级别的main线程,也就是系统级别的主线程),

执行过程:

在这里插入图片描述

1.2 创建线程

方法1: 继承 Thread 类

可以通过继承 Thread 来创建一个线程类,该方法的好处是 this 代表的就是当前线程,不需要通过
Thread.currentThread() 来获取当前线程的引用。

class MyThread extends Thread {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
  }
} 
MyThread t = new MyThread();
t.start(); // 线程开始运行 

方法二:实现 Runnable 接口
通过实现 Runnable 接口,并且调用 Thread 的构造方法时将 Runnable 对象作为 target 参数传入来创建线程对象。
该方法的好处是可以规避类的单继承的限制;但需要通过 Thread.currentThread() 来获取当前线程的引用

class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "这里是线程运行的代码");
  }
} 
Thread t = new Thread(new MyRunnable());
t.start(); // 线程开始运行 

方法三:其他变形 方法

// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
  }
};
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
  }
});
// 使用 lambda 表达式创建 Runnable 子类对象
  Thread t3 = new Thread(() ->  System.out.println("使用匿名类创建 Thread 子类对象"));
  Thread t4 = new Thread(() -> {
  System.out.println("使用匿名类创建 Thread 子类对象");
}); 

二.Thread 类及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述
一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理

2.1 Thread 的常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group,Runnable target线程可以被用来分组管理,分好的组即使线程组,这个目前我们了解即可
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2.2 Thread 的几个常见属性

属性获取方法
IDgetId
名称getName
状态getState
优先级getPriority
是否后台线程isDaemon
是否存活isAlive
是否被中断isInterrupted
  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况,下面我们会进一步说明
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了
  • 线程的中断问题,下面我们进一步说明
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
  System.out.println(Thread.currentThread().getName()   + ": 我还活着");
Thread.sleep(1 * 1000);
   } catch (InterruptedException e) {
e.printStackTrace();
  }
 } 
  System.out.println(Thread.currentThread().getName()    + ": 我即将死去");
 });
  System.out.println(Thread.currentThread().getName()
  + ": ID: " + thread.getId());
  System.out.println(Thread.currentThread().getName()
  + ": 名称: " + thread.getName());
  System.out.println(Thread.currentThread().getName()
  + ": 状态: " + thread.getState());
  System.out.println(Thread.currentThread().getName()
  + ": 优先级: " + thread.getPriority());
  System.out.println(Thread.currentThread().getName()
  + ": 后台线程: " + thread.isDaemon());
  System.out.println(Thread.currentThread().getName()
  + ": 活着: " + thread.isAlive());
  System.out.println(Thread.currentThread().getName()
  + ": 被中断: " + thread.isInterrupted()); 
  thread.start();
  while (thread.isAlive()) {
  System.out.println(Thread.currentThread().getName()
  + ": 状态: " + thread.getState());
    }
   }
} 

2.3 启动一个线程

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行
了。

  • 覆写 run 方法是提供给线程要做的事情的指令清单

  • 线程对象可以认为是把 李四、王五叫过来了

  • 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了
    面试题: 一定要注意 run 方法和 start 方法是不同的,启动线程必须要调用 start 方法。

    !!此处要认识到start是启动某个线程,run是执行线程内部的代码(即线程要做的事情),线程没有启动(start),run方法不会执行到,因为线程只有start才能进入就绪态,等待系统调度,调度到时线程进入运行态,才能执行run方法。

2.4 中断一个线程

例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉
及到我们的停止线程的方式了。

目前常见的有以下两种方式:
1. 通过共享的标记来进行沟通
2. 调用 interrupt() 方法来通知

实例1:

此处使用共享标记来实现中断线程(也就是设置布尔变量)

public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
 @Override
 public void run() {
 while (!isQuit) {
  System.out.println(Thread.currentThread().getName()
  + ": 别管我,我忙着转账呢!");
 try {
  Thread.sleep(1000);
  } catch (InterruptedException e) {
  e.printStackTrace();
   }
  } 
 System.out.println(Thread.currentThread().getName()
 + ": 啊!险些误了大事");
  }
 } 
public static void main(String[] args) throws    InterruptedException {
  MyRunnable target = new MyRunnable();
  Thread thread = new Thread(target, "李四");
  System.out.println(Thread.currentThread().getName()
  + ": 让李四开始转账。");
  thread.start();
  Thread.sleep(10 * 1000);
  System.out.println(Thread.currentThread().getName()
  + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
  target.isQuit = true;
  }
} 

这种方法虽然可以实现中断线程,但是当线程调用了sleep方法,并且休眠时间较长,就无法及时中断线程了。所以不太提倡。

实例2:

public class Thread2 {
private static class MyRunnable implements Runnable {
@Override
public void run() {
// 两种方法均可以
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
  System.out.println(Thread.currentThread().getName()
  + ": 别管我,我忙着转账呢!");
try {
   Thread.sleep(1000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  System.out.println(Thread.currentThread().getName()
 + ": 有内鬼,终止交易!");
break;
    }
  }
   System.out.println(Thread.currentThread().getName()
  + ": 啊!险些误了大事");
   }
 }
 public static void main(String[] args) throws      InterruptedException {
  MyRunnable target = new MyRunnable();
  Thread thread = new Thread(target, "李四");
  System.out.println(Thread.currentThread().getName()
  + ": 让李四开始转账。");
  thread.start();
  Thread.sleep(10 * 1000);
  System.out.println(Thread.currentThread().getName()
  + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
   thread.interrupt();
  }
} 

重点说明下第二种方法:

  1. 通过 thread 对象调用 interrupt() 方法通知该线程停止运行

  2. thread 收到通知的方式有两种:
    2.1. 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除
    中断标志
    2.2. 否则,只是内部的一个中断标志被设置,thread 可以通过

    2.2.1. Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
    2.2.2. Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

第二种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到

2.5等待一个线程-join()

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决
定是否存钱,这时我们需要一个方法明确等待线程的结束。

public class Thread2 {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
     System.out.println(Thread.currentThread().getName()
    + ": 我还在工作!");
   Thread.sleep(1000);
 } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
 System.out.println(Thread.currentThread().getName() + ": 我结束了!");
   };
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始工作");
thread1.start();
thread1.join();
System.out.println("李四工作结束了,让王五开始工作");
thread2.start();
thread2.join();
System.out.println("王五工作结束了");
  }
} 

附:

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理,但可以更高精度

2.6 获取当前线程引用

这个方法之前已经使用过了,相信大家一定有了一定的认识了吧

方法说明
public static Thread currentThread();返回当前线程对象的引用
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
  }
} 

2.7 休眠当前线程

有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证休眠时间是大于等于休眠时间的

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis 毫秒
public static void sleep(long millis, int nanos) throws InterruptedException可以更高精度的休眠
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
  }
} 

3. 线程的状态

3.1观察线程的所有状态

线程的状态是一个枚举类型 Thread.State

public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
  }
 } 
} 
N
EW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

3.2线程状态和状态转移的意义

在这里插入图片描述

大家不要被这个状态转移图吓到,我们重点是要理解状态的意义以及各个状态的具体意思。

在这里插入图片描述

刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态

当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行工作人员开始接
待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;
当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入 BLOCKED 、 WATING 、 TIMED_WAITING 状态
如果李四、王五已经忙完,为 TERMINATED 状态。
所以,之前我们学过的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。

在这里插入图片描述

后序继续更新奥!!!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值