Java多线程详解

Java多线程详解

实现线程的三种方式

1.1继承Thread

public class MyThread {

    public static void main(String[] args) {
        Thread1 mTh1=new Thread1("A");
        Thread1 mTh2=new Thread1("B");
        mTh1.start();
        mTh2.start();

    }
}
//继承Thread实现多线程
class Thread1 extends Thread{
    private String name;
    public Thread1(String name) {
        this.name=name;
    }
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行  :  " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wE5umljI-1665369990611)(Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%A6%E8%A7%A3.assets/image-20220812160953787.png)]

也可能出现这种运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C4X4HME4-1665369990612)(Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%A6%E8%A7%A3.assets/image-20220812161028829.png)]

说明

程序在启动main函数时,Java虚拟机就已经启动了一个主线程来运行main函数,在调用到mTh1mTh2的start方法时,就相当于有三个线程在同时工作了,这就是多线程的模式,进入了mTh1子线程,这个线程中的操作,在这个线程中有sleep()方法,Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。

实际上所有的线程执行顺序都是不确定的,CPU资源的获取完全是看两个线程之间谁先抢占上谁就先运行,当mTh1抢占上线程后,运行run方法中的代码,到sleep()方法进入休眠状态,也就是阻塞状态,然后CPU资源会被释放,AB再次进行抢占CPU资源操作,抢占上的继续运行。在运行的结果中你也可以看到这个现象。

注意:

一个实例的start()方法不能重复调用,否则会出现java.lang.IllegalThreadStateException异常。

1.2实现Runnable接口

package com.hjt.multithreading;

class Thread2 implements Runnable{
    private String name;

    public Thread2(String name) {
        this.name=name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行  :  " + i);
            try {
                Thread.sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}
public class MyRunnable {

    public static void main(String[] args) {
        new Thread(new Thread2("C")).start();
        new Thread(new Thread2("D")).start();
    }

}

结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A7S2xCXC-1665369990612)(Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%A6%E8%A7%A3.assets/image-20220812161606060.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4cKtwZ0x-1665369990613)(Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%A6%E8%A7%A3.assets/image-20220812161620461.png)]

说明:

Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target)构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Threadstart()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

Thread类和Runnable接口的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runablecallable类线程,不能直接放入继承Thread的类

提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实习在就是在操作系统中启动了一个进程。

1.3 Callable实现返回值

package com.hjt.multithreading;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class MyCallable {
    public static void main(String[] args) throws Exception {

        MyThread1 myThread1 = new MyThread1();
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread1());
        new Thread(futureTask).start();//开启线程
        System.out.println(futureTask.get());//获取返回值

    }
}


class  MyThread1 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int count = 0;
        for (int i = 1;i<100;i++){
            if (i%3==0){
                count++;
            }
        }
        return count;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KGMfDGr7-1665369990614)(Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%A6%E8%A7%A3.assets/image-20220812163009650.png)]

1.4多线程运行过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lp0sKTvW-1665369990614)(Java%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%A6%E8%A7%A3.assets/image-20220812163159153.png)]

1:新建状态(New):new Thread(),新创建了一个线程;

2:就绪状态(Runnable):新建完成后,主线程(main()方法)调用了该线程的start()方法,CPU目前在执行其他任务或者线程,这个创建好的线程就会进入就绪状态,等待CPU资源运行程序,在运行之前的这段时间处于就绪状态;

3:运行状态(Running):字面意思,线程调用了start()方法之后并且抢占到了CPU资源,运行run方法中的程序代码;

4:阻塞状态(Blocked):阻塞状态时线程在运行过程中因为某些操作暂停运行,放弃CPU使用权,进入就绪状态和其他线程一同进行下次CPU资源的抢占。

当发生如下情况时,线程将会进入阻塞状态

① 线程调用sleep()方法主动放弃所占用的处理器资源

② 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

③ 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的知识、后面将有深入的介绍

④ 线程在等待某个通知(notify)

⑤ 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

当前正在执行的线程被阻塞之后,其他线程就可以获得执行的机会。被阻塞的线程会在合适的时候重新进入就绪状态,注意是就绪状态而不是运行状态。也就是说,被阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度它。

解除阻塞

针对上面几种情况,当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态:

① 调用sleep()方法的线程经过了指定时间。

② 线程调用的阻塞式IO方法已经返回。

③ 线程成功地获得了试图取得的同步监视器。

④ 线程正在等待某个通知时,其他线程发出了个通知。

⑤ 处于挂起状态的线程被调甩了resdme()恢复方法(会导致死锁,尽量避免使用)。

5:死亡状态(Dead):线程程序执行完成或者因为发生异常跳出了run()方法,线程生命周期结束。

1.5多线程如何正确优雅的中断线程

1、在catch子句中,调用Thread.currentThread.interrupt()来设置中断状态(因为抛出异常后中断标示会被清除)

让外界通过判断Thread.currentThread().isInterrupted()标示来决定是否终止线程还是继续下去,应该这样做

    void mySubTask() {   
        ...   
        try {   
            sleep(delay);   
        } catch (InterruptedException e) {   
            Thread.currentThread().interrupted();   
        }   
        ...   
    }  

2、或者,更好的做法就是,不使用try来捕获这样的异常,让方法直接抛出

    void mySubTask() throws InterruptedException {   
        ...   
        sleep(delay);   
        ...   
    }  

个人搭建项目代码地址:
https://github.com/hongjiatao/spring-boot-anyDemo

欢迎收藏点赞三连。谢谢!有问题可以留言博主会24小时内无偿回复。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有点东西且很多

你的鼓励是我最大的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值