线程--Thread使用

文章详细介绍了Java中的Thread类,包括如何创建线程,线程的状态,如NEW,RUNNABLE,TERMINATED等,以及线程的休眠、join方法的使用。此外,还讨论了Runnable和Callable接口的区别,以及FutureTask在处理返回值时的作用。通过示例代码展示了线程的执行顺序和效率差异,强调了多线程在CPU密集型和IO密集型任务中的应用。
摘要由CSDN通过智能技术生成

Thread类

什么是Thread类??
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).
基于上述API:
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装. 也就是这个Thread实例和操作系统的线程是一 一对应的。(有一种类似引用类型的效果,通过这个东西我能操作到另一个东西)
java的跨平台性:JVM封装操作系统很多功能,不同的操作系统对应不同的JVM,java提供很多API让我们去学习。

main线程和t线程执行演示

创建Thread实例并不创建线程,还得start启动一下才是真正在操作系统创建线程PCB,由新线程执行run方法;run方法执行完,这个新的线程就自然销毁。但是Thread实例并不能做到同步销毁,得等到它指向为空才会gc回收。
创建过程:(了解即可)
Java用start()启动线程,start()调用start0()本地方法(通过JNI调用),继而调用Linux中pthread_create()创建线程(这里调用操作系统API。内核创建PCB,并把要执行指令交给这个PCB),Java线程中的run()作为参数传入pthread_create() (当PCB调度到CPU上执行的时候,也就执行到run的代码)。

class mythread extends Thread{
    @Override
    public void run() {
        System.out.println("hello world");
    }
}
public class tesssss{//创建线程的方法1
    public static void main(String[] args) {//主线程
  Thread t=new mythread();
    t.start();
    }
}

这个创建的线程打印hello world和直接用打印hello world是用不同区别。。直接打印它是使用java进程里一个主线程

我们用Thread.sleep休眠1秒分别用main线程和t线程打印试试看

class mythread extends Thread{
    @Override
    public void run() {
        while (true) {
            System.out.print("2");
            try {
                Thread.sleep(1000);//让当前线程休眠1秒
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class tesssss{//创建线程的方法1
    public static void main(String[] args) throws InterruptedException {//主线程
  Thread t=new mythread();
  t.start();

  while (true){
      System.out.print("1");
Thread.sleep(1000);
  }
    }
}

执行结果:谁先谁后是说不准的
在这里插入图片描述
我们的操作系统调度线程是抢占式执行,哪个线程先调度是不确定,这取决于操作系统调度器。。这里的执行顺序是“随机的”;虽然这里能设置优先级,在内核里并非是随机的。但是干扰因素太多,到我们看到的效果就只能认为是随机的。所以我们的更改线程优先级的方法作用并不常用。

查看线程

C:\Program Files\Java\jdk1.8.0_192\bin jdk找到我们bin目录,打开jconsole.exe.
在这里插入图片描述
选择创建的tesssss进程,然后连接—不安全连接
在这里插入图片描述
小疑问:main线程结束销毁后,Thread-0会销毁吗?这里涉及到守护线程与非守护线程,后面细说.

创建线程的方法

1:继承Thread 重写run

class mythread extends Thread{
    @Override
    public void run() {
 //
    }
}
public class tesssss{
    public static void main(String[] args) throws InterruptedException {//主线程
  Thread t=new mythread();
  t.start();

2:实现 Runnable 接口;重写run方法

//2:实现 Runnable 接口
class Myrunable implements Runnable  {
    
    @Override
    public void run() {
        System.out.println("1");
    }
}

public class test0 {

    public static void main(String[] args) {
        Runnable r1=new Myrunable(); //描述了这个任务
        Thread t1=new Thread(r1);    //把任务交给线程执行
        t1.start();
    }

}

3:使用Lambda 表达式
本质:实现了Runnable接口的匿名类的实例,并将其作为参数传递给Thread对象。

public class test {
    public static void main(String[] args) {
      Thread t=new Thread(()-> {

          System.out.println(123);
      });
      t.start();
      }
        
    }

4:Callable 接口
Callable 是一个 interface . 相当于把线程封装了一个 “返回值”. 方便程序猿借助多线程的方式计算结果
Runnable的run方法描述的任务返回值是void;而使用Callable的call方法描述任务的返回值是泛型参数:有时候我们需要线程计算一个结果返回;需要使用Callable创建线程比较合适。

代码示例:Runnable的run方法

static class Result {
    public int sum = 0;
    public Object lock = new Object();
}
public static void main(String[] args) throws InterruptedException {
    Result result = new Result();
    Thread t = new Thread() {
        @Override
        public void run() {
            int sum = 0;
            for (int i = 1; i <= 1000; i++) {
                sum += i;
           }
            synchronized (result.lock) {
                result.sum = sum;
                result.lock.notify();
           }
       }
   };
    t.start();
    synchronized (result.lock) {
        while (result.sum == 0) {
            result.lock.wait();
       }
        System.out.println(result.sum);
   }
}

这个代码很繁琐;得先阻塞main线程;等待t1线程通知回来执行得到结果。

代码示例:Callable的call方法
1:创建一个匿名内部类, 实现 Callable 接口. Callable 带有泛型参数. 泛型参数表示返回值的类型.
重写 Callable 的 call 方法, 完成累加的过程. 直接通过返回值返回计算结果.
2:把 callable 实例使用 FutureTask 包装一下.
3:创建线程, 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的
call 方法, 完成计算.;计算结果就放到了 FutureTask 对象中
4:在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果

Callable<Integer> callable = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 1000; i++) {
            sum += i;
       }
        return sum;
   }
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println(result);

futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.

为什么不能直接把call实例直接传入到Thread里呢?
Thread的构造方法签名接受的是Runnable;为了能接收Callable类型的任务,需要借助ExecutorService接口以及Future和FutureTask等类。FutureTask实现了RunnableFuture接口,而RunnableFuture同时实现了Runnable和Future接口,从而可以将其传递给Thread的构造方法。

为什么Callable一般搭配FutureTask来使用
搭配FutureTask来使用:Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定;FutureTask 就可以负责这个等待结果出来的工作

Thread类用法

在这里插入图片描述

名字对你调试有帮助,这个名字是系统的线程名。线程默认的名字是thread-0,1,2,3等

Thread常见的属性

在这里插入图片描述

1:线程ID

1:ID :getid() 线程唯一标识,不同线程不会重复

2:线程名称

2:名称 ; 获取名字getname,创建名字

3:线程状态

3:状态:java的线程状态比操作系统要丰富很多,后面细说

4:线程优先级

4:优先级:作用并不大,,具体到操作系统很多影响因素;把控还是不稳

5:前后台线程

5:是否后台线程
//JVM会在一个进程所有的前台线程结束后才结束运行,手动创建和main都是默认前台,JVM自带的是后台。用setDaemon设置为后台线程
//前台线程:会阻止进程结束,前台线程的工作没做完,进程是不会结束的
//后台线程:不会阻止进程结束,后台线程工作没做完,进程是可以结束的

setDaemon(false) 改前台线程。(怎么记这个方法其它和后台是false还是true:你就想前台;是不是有个人在站着收银;F是不是就站着的)
setDaemon (true) 改后台线程 当把除了main都改成后台时,main结束后,进程就结束。就像下面的代码,打印了几个1后main结束,进程就直接结束,不会等到你t线程的run方法执行完

6:是否存活isAlive

6:是否存活;也就是run方法是否执行完
判断线程是否在内核干活,干完活就消失,重新变为false,没创建的时候也是false。。内核把run执行完,线程销毁,pcb释放,Thread t这个对象不一定被释放。t这个对象得到它引用不指向任何对象,才回收。
在这里插入图片描述

7:线程中断

7:中断:(让线程干一半活就不干了);不是让线程立即停止,而是通知线程你该停止,是否真的要停止取决于具体的代码写法。为什么不设置一终止就立即停止呢,而是使用标志位呢? 其实两个线程是并发执行,mian调用t终止,main并不知道t执行到哪了。这样终止达不到我们想要的效果。
1:使用标志位来控制线程是否要停止

public class test1 {
    public static Boolean flag=true;
    public static int count=0;
    public static void main(String[] args) throws InterruptedException {

        Thread t=new Thread(()->{
		while (flag) {
		    System.out.print("a");
		   count++;
		}

        });

		t.start();
		Thread.sleep(1000);
		flag=false;
        System.out.println(count);

    }
}
//我在打印1秒的a后我修改flag让它停止

2:但是第一种的方式,可能会不能及时响应;为什么说不及时呢?因为如果线程在等待、睡眠就没法检测到你这个自定义的标志位的状态变了。使用Thread自带的标志位进行判定

public class test2 {
   public static int count =0;
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
//      while (!Thread.currentThread().isInterrupted())
//          //Thread.currentThread()是Thread类的静态方法,获取到当前调用这个方法线程的引用
//          //isInterrupted(),true表示被终止,false表示未被终止,还要继续

//            System.out.print(1);
               int i=0 ;
               while (i<1000){
                System.out.print(Thread.currentThread().isInterrupted());
                i++;
                count++;
            }
       });
			t.start();
			Thread.sleep(1);
			t.interrupt();//终止t线程,main调用,
        //虽然我们调用这个方法阻止了线程但是并没有真真正正的阻止线程,
        // 而是把Thread.currentThread().isInterrupted()变成ture,线程还在执行。
        // 如果我们把这个条件换在我们要阻止的语句上使run方法直接结束,就终止了(注释掉的写法)
        System.out.println(count);
    }
}

运行的结果是这样得注意;我们在t.start立马就执行221次循环,然后休眠的1毫秒过程,我们的主线程直接往下执行大概率比调度t现在执行打印速度快,所以就会先打印221,因为这个执行速度是非常快的,但是我们代码并没有设置能用这个标志终止的写法,所以还会继续执行下去。
interrupt这个方法还能唤醒sleep,如果线程在休眠(比如我在t线程休眠10秒;然后在main线程调用这个方法),此时调用这个方法会把线程唤醒,让sleep提前返回,在我们抛异常的触发异常结束。sleep唤醒还会清空标志位。
在这里插入图片描述
在这里插入图片描述



public class ThreadDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt();
    }
}

唤醒sleep,标志被清除,还会继续往下执行;在catch加个break就能解决,当抛异常就结束循环,不给你清除标记又继续循环的机会。这样子写也能说是标志位起作用了;让休眠的代码异常结束;走catch的代码进而结束循环。
在这里插入图片描述

等待线程:t.join

等待线程:因为我们的线程是随机调度,所以谁先执行完和谁后执行完是不确定的,等待线程能让你这个线程等另一个执行完再继续往下执行。

class myrunnable implements Runnable{//测试等待线程,Runnable接口写法
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("123");
    }
}

public class test3 {

    public static void main(String[] args) throws InterruptedException {
  Runnable r1=new myrunnable();
  Thread t=new Thread(r1);
  t.start();

  t.join();//在谁那里调用这个方法,就是谁进入阻塞、这里main线程里面调用;所以main线程先阻塞直到这个t线程执行完
        System.out.println("abc");
    }
}

如果开始执行join时,我的线程已经结束了是什么情况呢,比如我在t.start后面休眠个5秒,t线程结束两秒后才进行t.join。
那么join不会阻塞,直接返回。

join方法:无参版本,等到你执行完
有参版本,你可以设置一个时间,指定最大等待时间时间
设置一个等待时间,就像等不到的人还会再等吗?当然把等待的时间放在学习上(main线程利用时间继续执行下面的代码)

class myrunnable implements Runnable{//测试等待线程,Runnable接口写法
    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("123");
    }
}

public class test3 {

    public static void main(String[] args) throws InterruptedException {
  Runnable r1=new myrunnable();
  Thread t=new Thread(r1);
        t.start();
        t.join(2000);
        System.out.println("abc");//如果abc先执行,等两秒我就不等了,先打印abc后,然后你t线程再自己慢慢执行
         //多线程之间执行的顺序有变数,但是单个线程之间,顺还是固定的,从上到下

    }
}

获取当前线程引用 currentThread

currentThread()方法;获取当前线程引用;返回Thread类型,静态的类方法(public static Thread currentThread();)在哪个线程调用,就能获得哪个线程的。

public class test6 {
    public static void main(String[] args) {

        Thread t=new Thread(()->{
            Thread t2=Thread.currentThread();
            System.out.println(t2.getName());

        },"liao");
        Thread t1=Thread.currentThread();
        System.out.println(t1.getName());

        t.start();
     // System.out.println(Thread.currentThread());//直接打印得到的结果是toString方法写的结果,引用类型


    }
}

输出结果是main liao

休眠方法sleep

本质让这个线程不参与调度,不去cpu上执行。从就绪队列加入到阻塞队列进入阻塞状态。
就绪队列:执行的时候,cpu在这里选一个去调度执行
阻塞队列:休眠就是加入到这里来。这里的队列并不是数据结构的队列,只是一个名称,pcb底层是一个以一系列列表为核心的数据结构,不是简单的链表,可能是链表或者红黑树。。。
sleep(1000)实际上会大于1000毫秒,因为你在唤醒之后并不是就立即被执行,还等等候cpu的调度执行。

线程的状态

java对线程状态进行细划;
1.NEW 创建Thread对象,但是还没调用start(还没在操作系统创建对应线程 PCB)

2.RUNNABLE 可运行的;两种情况(操作系统区分不了到底是哪两种,即便我们知道是哪一种,你也做不了什么改变,这里就不关你什么事)
a情况:正在cpu上执行
b情况:在就绪队列,随时可以去cpu上执行
阻塞:但是阻塞,下面345因为不同原因的阻塞
3.WAITING
4.TIMED_WAITING:当你在线程中加个sleep,并且刚好执行到休眠的时候就是这个状态
5.BLOCKED
6.TERMINATED 操作系统的PCB已经创建完成,Thread也还在。(当这个线程PCB销毁了,代码的t对象虽然还没立即被回收,因为做不到java和操作系统线程同步回收,但是这个t也是失去它的意义和作用,就需要这样一个状态标识这个t无效)

获取线程状态:t.getState();

线程状态转换

在这里插入图片描述

代码:可以看到结束之后的状态是TERMNATED ,约定一个线程只能start一次,也没法再通过多线程完成其它任务。
但是你还能利用这个对象去调用它一些方法属性

public class test6 {
    public static void main(String[] args) throws InterruptedException {

        Thread t1=new Thread(()->{

   

        },"liao");
        System.out.println("启动之前状态" +t1.getState());
    //启动之前状态NEW
        t1.start();
         System.out.println("启动中"+t1.getState());
        //启动中RUNNABLE
        t1.join();
        System.out.println("启动之后状态" +t1.getState());
//启动之后状态TERMINATED


    }
}

感受一下多线程与单线程的执行效率区别

有cpu密集(大量加减乘除等算术运算)和io密集(读写文件、控制台、网络),先体验一下cpu密集。体验一下在大量运算下两者区别:
两个变量自增1000w次 (为了更好体现;使用currentTimeMillis 获取到当前系统的 ms 级时间戳.)
a方案:一个线程先对a自增,然后对b自增
b方案:两个线程,分别对a和b自增

public class test7 {//测试在大量的运算下多线程与单线程的差别,1000万次不够
   static int a1=0;
 static    int b1=0;
   static int a2=0;
 static    int b2=0;
    public static void main(String[] args) {


            long start1=System.currentTimeMillis();
            test1();
            long end1=System.currentTimeMillis();//获取系统时间的毫秒数
            System.out.print(end1-start1+"毫秒");
        long start2=System.currentTimeMillis();
        test2();
        long end2=System.currentTimeMillis();
        System.out.print(end2-start2+"毫秒");

    }

    public static void test1(){


        for (long i = 0; i <1000_000_0000L ; i++) {
            //100亿次,得用long,数值得后面加个大写或者小写L
            a1++;
        }
        for (long i = 0; i <1000_0000000L ; i++) {
            b1++;
        }
    }
    public static void test2(){
        Thread t1=new Thread(()->{
            for (long i = 0; i <1000_000_0000L ; i++) {
                a2++;
            }


        });
        Thread t2=new Thread(()->{
            for (long i = 0; i <1000_000_0000L ; i++) {
                b2++;
            }


        });

    }
}

可以看到光在时间上就相差如此之巨大。其实这里有点问题,所以这里两个线程时间并不是正确的,因为我们的线程是并发执行,可能你在执行两个线程的运算时,我main线程就趁这段时间把后面的start和end的时间戳计算完。所以这里两个线程的实现代码不能这么写,得等两个线程执行完才计算时间戳
在这里插入图片描述

public class test7 {//测试在大量的运算下多线程与单线程的差别,1000万次不够
   static int a1=0;
 static    int b1=0;
   static int a2=0;
 static    int b2=0;
    public static void main(String[] args) throws InterruptedException {


            long start1=System.currentTimeMillis();
            test1();
            long end1=System.currentTimeMillis();
            System.out.println(end1-start1+"毫秒");

        test2();


    }

    public static void test1(){


        for (long i = 0; i <1000_000_0000L ; i++) {
            //100亿次,得用long,数值得后面加个大写或者小写L
            a1++;
        }
        for (long i = 0; i <1000_0000000L ; i++) {
            b1++;
        }
    }
    public static void test2() throws InterruptedException {
        long start2=System.currentTimeMillis();
        Thread t1=new Thread(()->{
            for (long i = 0; i <1000_000_0000L ; i++) {
                a2++;
            }


        });
        Thread t2=new Thread(()->{
            for (long i = 0; i <1000_000_0000L ; i++) {
                b2++;
            }


        });
        t1.start();
        t2.start();
        t1.join();//main方法等待他们线程的执行完再记时间戳
        t2.join();

        long end2=System.currentTimeMillis();
        System.out.print(end2-start2+"毫秒");

    }
}

修改后的结果,计时得在方法里面计,还得等待线程执行完,不然main线程不老实,老是会先把时间戳先计算完了。两线程并不意味着时间缩短一半,因为调度可能是并行(在两个核心上执行)也可能是并发(在一个核心上执行)。我们join两个等待,它会两个线程一起执行,如果t2先执行完,还会等待t1执行完再返回。t1,t2谁先谁后执行完是不知道,但是main一定是等待他们两执行完再继续开始后面的语句。
在这里插入图片描述

IO密集上:程序进行了一些耗时的 IO 操作,阻塞了界面的响应这种情况下使用多线程也是可以有效改善的.(一个线程负责 IO,另一个线程用来响应用户的操作)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

20计科-廖雨旺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值