文章目录
- Thread 类及常见方法
- 启动一个线程
- 中断一个线程
- 变量型中断
- 调用 interrupt() 方法来通知
- 观察标志位是否被清除
- 等待一个线程
- 获取当前线程引用
- 休眠当前线程
- 线程的状态
- 观察线程的所有状态
- 观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的切换
- 多线程带来的风险
- 为什么会这样?
Thread 类及常见方法
Thread类是我java给提供的一个线程类其内部包含了很多帮助我们的方法。除了上次讲述的初始化方法外还有哪些呢?请看下面的内容
启动一个线程
我们知道了通过复写run方法创建一个线程对象(在我的上一篇文章中说明过)但是线程被创建出来并不代表线程已经开始了运行。因此只有调用了start方法才是真正的创建了一个线程用法如下
public class Main {
public static void main(String[] args) throws InterruptedException{
Thread t=new Thread(()->{
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("我是lambda表达式创建出的线程");
}
});
t.start();
while(true){
Thread.sleep(1000);
System.out.println("Hello world!");
}
}
}
中断一个线程
线程在执行的过程中不仅要知道如何进行启动,中断也同样重要因为线程的执行只有当线程这个任务彻底完成后才会中断但是这个机制是不好的因为这时候就会导致一些有问题的线程我们没法立刻进行中断,那么现在主要有哪些中断线程的方法呢?
变量型中断
使用一个自定义变量进行中断线程请看如下代码
class Mythread extends Thread{
public void run(){
while(Main.flag){
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("正常交易中");
}
System.out.println("有内鬼停止交易");
}
}
public class Main {
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException{
Thread t=new Mythread();
t.start();
int cnt=0;
while(true){
Thread.sleep(1000);
if(cnt<5)
System.out.println("令线程正常交易");
cnt++;
if(cnt>=5){
System.out.println("通知线程有内鬼");
flag=false;
break;
}
}
}
}
请看上面这个代码这个代码很明显就是用了一个flag变量来控制这里面的while循环是否可以继续执行但是这个方法非常的不优雅,感觉有种很土的感觉,那难道我们的java就没有官方提出来一些方法来使用嘛?当然是有的。那就是下面三个方法
调用 interrupt() 方法来通知
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置 调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联线程的标志位是否设置调用后不清除标志位 |
那么这些方法该怎么使用呢?请看如下代码
class Mythread extends Thread{
public void run() {
while(!Thread.interrupted()){
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正常交易中");
}
System.out.println("有内鬼停止交易");
}
}
public class Main {
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException{
Thread t=new Mythread();
t.start();
int cnt=0;
while(true){
Thread.sleep(1000);
if(cnt<5)
System.out.println("令线程正常交易");
cnt++;
if(cnt>=5){
System.out.println("通知线程有内鬼");
t.interrupt();
break;
}
}
}
}
在这个代码运行截图中我们可以看到即使抛出了异常但是这个代码仍然没有终止那么这是为什么呢?因为我们看一下上面的对这些的方法的介绍。interrupted()方法介绍说了调用本方法可以查看此时的标志位并且在查看过后就会清除标志位,那么按照这个代码的逻辑我们来看一下。首先我们调用了interrupt()方法设置了我们的标记位,并且检测到线程此时正在休眠因此我们以异常的形式进行了抛出,之后当调用interrupted()方法进行判断的时候此时标志位未被设置因此循环将会继续执行,那么难道就没有办法了吗?当然是有的,第一个办法就是将异常抛出(因为我们学过当我们将异常抛出的时候那么这个方法就不会继续往后执行了)(第二个办法就是在循环内部加一个break)
thread 收到通知的方式有两种:
- 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通
知,清除中断标志
当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择
忽略这个异常, 也可以跳出循环结束线程. - 否则,只是内部的一个中断标志被设置,thread 可以通过
Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
观察标志位是否被清除
观察标志位是否被清除,我们来讲述一下那两个方法
首先标志位大家可以理解是什么呢?其实就是我们第一种方法定义的一个boolean的变量一样那么清除标志位就相当于把这个标志位重新设置成了false那样子,设置标志位就相当于将其设置为true,那么带着这种理解我们来看一下这两种方法首先是public static boolean interrupted()这个方法就像是一个自动开关一样,当你检查的时候相当于把灯打开了,当你检查完毕后又把灯给重新随手关上了public boolean isInterrupted()而这个则是不会关闭。
等待一个线程
等待一个线程,什么是等待一个线程呢?其实就是等待一个线程结束,我们刚刚说过线程是并发执行的,但是有些时候我们希望这个线程是顺序的,有时又可以是并发那么该怎么做呢?那就需要线程的等待了。顾名思义线程的等待其实就是等待一个线程的任务进行完毕后再去执行接下来的代码,因此就实现了线程的顺序执行,那么该怎么完成呢?那就是用join方法请看下面的代码
class Mythread extends Thread{
public void run() {
int i=0;
while(i<=6){
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
i++;
System.out.println("正常交易中");
}
//System.out.println("有内鬼停止交易");
}
}
public class Main {
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException{
Thread t=new Mythread();
t.start();
t.join();
System.out.println("线程已经结束");
}
}
代码运行截图
这样我们就可以看的很清楚了线程是顺序执行的。
获取当前线程引用
这个方法就比较简单了可以了解一下代码如下
public class Main {
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException{
Thread t=Thread.currentThread();
System.out.println(t);
}
}
休眠当前线程
休眠当前线程其实就是我们用的sleep方法关于这个方法呢我们要知道实际的休眠时间是要大于你设置的休眠时间的。
线程的状态
线程的状态是一个枚举类型,Thread.State,那么都有哪些状态呢?我们来看一下以下代码
public class Main {
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException{
for(Thread.State state:Thread.State.values()){
System.out.println(state);
}
}
}
那么我们接下来,来描述以下现成的 这些状态的含义
状态类型 | 状态的含义 |
NEW | 线程被创建出来但并没有开始行动 |
RUNNABLE | 可以工作的又分为正在工作或者即将开始工作 |
BLOCKED | 排队等着其他的事情 |
WAITING | 排队等着其他的事情 |
TIMED_WAITING | 排队等待其他的事情 |
TERMINATED | 工作已经完成 |
观察线程的所有状态
观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的切换
使用isAlive进行线程状态的观察
public class Main {
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException{
Thread t=new Thread(()->{
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("正在运行中");
}
});
t.start();
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(t.getState());
}
}
}
在运行截图中我们可以看到这里的线程状态有的时候是TIMED_WAITING有时候却又变成了RUNNABLE那么这是为什么会这样呢?真的是非常的奇怪其实原因很简单因为我们加的有sleep语句当我们调用state方法进行查看此时线程的状态的时候那么这个线程可能正在执行任务也有可能是正在sleep因此才会出现这种现象
多线程带来的风险
首先多线程会带来怎样的风险呢?我们来看一下下面的这个例子
class MyRun{
public int run=0;
public void Run(){
run++;
}
}
public class Main {
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException{
final MyRun m=new MyRun();
Thread t1=new Thread(()->{
for (int i=0;i<5000;i++)m.Run();
System.out.println("t1执行完毕"+m.run);
});
Thread t2=new Thread(()->{
for (int i=0;i<5000;i++)m.Run();
System.out.println("t2执行完毕"+m.run);
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(m.run);
}
}
他的运行结果是怎么样的呢看下图
另外我在写代码的时候有一个非常傻子的错误那就是把join没写上导致打印结果是0这里原因是因为忽略了线程的并发执行,有同样问题的同学可以共勉。
为什么会这样?
为什么会这样呢?按道理来说不应该是一万吗?为什么会发生这种情况呢?相信大部分同学都会冒出这样的疑问那么这是为什么呢?其实原因很简单因为对于++来说你看到的是一个操作其实他的底层是三个指令也就是三个操作正因为是三个操作这就导致了这个操作不是原子的,那么就可能出现我们数据库中类似于脏读的情况。