多线程编程之Thread类的使用

若想在Java中进行多线程编程,我们不得不提到在Java标准库中,存在一个Thread类可以对线程进行一系列的操作
Thread类可以视为是java标准库提供的API.
创建好的Thrad实例,其实和操作系统中的线程是一一对应的关系,操作系统提供了一组关于线程的API(C语言风格),Java对这组APi进一步封装了一下,就变成了Thread类

🥭Thread类的使用

🍉 线程的创建

  1. 创建子类,继承Thread
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class TestDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

这里需要注意的start()方法,只有调用了这个方法,才是在系统创建了线程,在这之前系统是没有创建出线程的,下文我们会着重的去介绍start()方法,它与上列代码重写的run()方法有着区别

2.创建一个类,实现Runnable接口,在创建Runnable实例传给Thread实例

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello");
    }
}
public class TestDemo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
                thread.start();
    }
}

通过 Runnable来描述任务的内容
再进一步把描述好的任务传给Thread实例

3.使用匿名内部类(上两种版本的翻版)

关于匿名内部类的知识,博主也不是很了解,也没办法给大家讲解,大家可以点击下面的链接

🍎匿名内部类

public class TestDemo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println();
            }
        });
        thread.start();
    }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("love");
            }
        };
        thread.start();
    }
}
  1. lambda表达式
public class TestDemo5 {
    public static void main(String[] args) {
        Thread thread = new Thread(() ->{
            System.out.println("hello");
        });
        thread.start();
    }
}

🍉测试线程串行和并发执行的效率

有两个整数变量,分别要对这两个整数变量自增十亿次
我们分别使用一个线程和两个线程来进行测试线程执行的时间

  1. 串行
    public static void serial(){
        long beg = System.currentTimeMillis();
        int a = 0;
        for (int i = 0; i <count ; i++) {
            a++;
        }
        int b = 0;
        for (int i = 0; i <count ; i++) {
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println((end-beg)+"ms");
    }

串行执行的时间

2.并发

        long beg = System.currentTimeMillis();
        Thread thread1 = new Thread(() ->{
            int a = 0;
            for (int i = 0; i <count ; i++) {
                a++;
            }
        });
        thread1.start();
        Thread thread2 = new Thread(() ->{
            int b = 0;
            for (int i = 0; i <count ; i++) {
                b++;
            }
        });
        thread2.start();
        //此处不能直接这么记录结束时间,因为这段代码是在main函数中
        //main函数与线程1,2是并发执行的,此处t1,t2还没有执行完,这里就开始记录结束时间了,显然是错误的
        //所以这里应该加上一个等待进程结束的代码,即join()函数
        thread1.join();
        thread2.join();
        long end = System.currentTimeMillis();
        System.out.println((end-beg)+"ms");
    }

请添加图片描述

从以上结果不能看出,并发执行的效率要高于串行执行的效率,但是并不是高于串行的一倍,效率提升接近了50%
但是并不是说并发编程就一定比串行编程的效率一定就高,具体情况具体分析
例如count的值变为1_0000;如果是这样的话,你会发现,串行的速度要高于并发执行的速度,这是因为系统创建线程也需要时间,如果自增变量花费了100ms,但创建一个线程就花费了200ms,显然,并发执行所需要的时间就会相对提高
所以多线程编程适用于CPU高度密集,程序需要大量的计算,使用多线程就可以更充分地利用CPU的多核资源

🍉 start()方法与run()方法

⬆️上文我们提到了start()方法,start()是真的决定了系统中是否能真正的创建出进程,run方法和start方法所表现出的运行结果一致,但是两个方法的含义却截然不同.
🍉经典面试题****start与run方法的区别

run():只是一个普通的方法,描述了任务的内容
start():却是一个特殊的方法,内部会在系统中中创建出一个线程

🍌线程的中断

线程停下来的关键是让线程对应的run()方法执行完
对于main这个特殊的线程,得是main方法执行完,线程就完了

  1. 可以手动的创建一个标识位(自己创建的变量 boolean类型),来控制线程是否要执行结束
public class TestDemo7 {
    private static boolean isQuite = false;
    public static void main(String[] args) {
        Thread thread1 = new Thread(() ->{
            while(!isQuite){
                System.out.println("hello thread1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"thread t1");
        thread1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isQuite = true;
        System.out.println("终止t线程!");
    }

根据上述代码,不难看出在main线程中控制这个标志位,就能够终止线程,此处是因为多个线程共用一个虚拟地址空间,所以main中的isQuite和t线程的isQuite是一个值,显然这种自定义标志位的方法是不够严谨的,所以有以下方法

  1. 使用Thread中内置的一个标志位来进行判定
  1. Thread.interrupted() 这是一个静态的方法一般不用
  2. Thread.currentThread().isInterrupted 这是一个实例方法.其中currentThread能够获取当前的实例(🐶无脑用🐶)
public class TestDemo8 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });
        thread.start();
        try {
            thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

thread.interrupt()调用这种方法可能会产生两种情况

  1. 如果线程是就绪状态,就是设置标志位是true;
  2. 如果线程是堵塞状态(sleep休眠了)就会出发一个InterruptedException

🍎线程的等待

在并发编程中,处理器调度的顺序是不确定的,线程在执行的时候,可以看作是(随机,无序)的,显然这种情况是不好的,所以就需要我们人工来控制,线程等待就是其中一种

线程等待;此处的线程等待控制的是线程结束的时间,而非线程开始的时间

join方法
join方法就是我们线程等待的一种方法,哪个线程调用的join方法,哪个线程就会遭遇堵塞

Thread thread1 = new Thread(() ->{
            int a = 0;
            for (int i = 0; i <count ; i++) {
                a++;
            }
        });
        thread1.start();
        Thread thread2 = new Thread(() ->{
            int b = 0;
            for (int i = 0; i <count ; i++) {
                b++;
            }
        });
        thread2.start();
        //此处不能直接这么记录结束时间,因为这段代码是在main函数中
        //main函数与线程1,2是并发执行的,此处t1,t2还没有执行完,这里就开始记录结束时间了,显然是错误的
        //所以这里应该加上一个等待进程结束的代码,即join()函数
        thread1.join();
        thread2.join();

如上代码

thread1.join();此代码的含义是:调用这个方法的线程是主函数,针对thread1这个线程调用的,也就是说需要main线程等thread1执行完之后,才能执行

通过线程等待,就是控制t线程执行完之后,main线程才能执行.一定程度上干预了这两个线程的执行顺序.

join()的意思是死等,显然这是不合理的所以我们需要在括号中加入参数
join(1000)含义是:如果在一秒内线程结束了,此时join返回
如果一秒内线程还没有结束,join也返回(劳资不等了),在日常中这样的等待都不会是死等,都会有时间限制的

🍅 线程获取引用

这个知识点我们在此不做过多解释你只需要知道Thread.currentThread().getName()这个方法就足够,哪个线程使用这个方法,就能够获取到哪个线程的实例

🍑线程的休眠

所谓的线程休眠就是在线程中使用**sleep()**方法
调用这个方法后线程就会变为堵塞状态
进程中有多个线程,线程一般被双向链表穿起来成为就绪队列,一旦使用这个方法线程就会从就绪队列成为堵塞队列(一般sleep也会和join方法一样有时间限制)等到时间限制过了之后,线程就会从堵塞队列重新回到就绪队列

此处需要注意,在线程调用时,不会从堵塞队列里调用线程,只有就绪队列才有可能被调用

请添加图片描述
大家可以对照上图参考

以上就是Thread类使用的基础知识,在未来的博客中,我们还会对线程相关的知识点做详细的介绍

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值