欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java中多线程实现的三种方式及线程的常用操作

程序员文章站 2022-03-29 23:01:43
文章目录前言一、进程、线程、并行、并发二、多线程的实现方式1.多线程实现的方式一2.多线程实现的方式二3.多线程实现的方式三三、多线程中常用操作1.获取和设置线程对象名称2.线程调度及获取和设置线程优先级3.线程休眠4.守护线程5.中断线程6.线程礼让前言首先我们来看下面一段代码,了解单线程的概念。public class 多线程的引入 { public static void main(String[] args) { System.out.println("程序开始执行了...


前言

首先我们来看下面一段代码,了解单线程的概念。

public class 多线程的引入 {
    public static void main(String[] args) {
        System.out.println("程序开始执行了");//1
        test();//2
        show();//6

    }
    private  static void show(){
        System.out.println("show方法执行了");//7
    }
    private  static void test(){
        System.out.println("test方法执行");//3
        test2();//4
    }

    private static void test2() {
        System.out.println("test2方法执行");//5
    }
}

Java中多线程实现的三种方式及线程的常用操作

  • 单线程:代码的执行路径只有一条。代码在一条主线中执行。
  • 单线程的弊端:当某个节点中,存在耗时操作。那么下面的代码,都需要等待该节点执行结束。
  • 我们可以将其想象成医院只开了一个窗口,只有当前患者完成咨询,后面的患者才可以咨询。
  • 所以我们引出多线程,也就是开启多个"窗口“,耗时”窗口“,并不影响其他”窗口“的运行。
  • 多线程:代码的执行路径有多条。

一、进程、线程、并行、并发

  1. 进程:就是正在运行的程序,是系统进行资源分配和调用的独立单位。
  2. 线程:正在执行的进程,可能运行很多任务,其中一个任务就是一个线程。
  • 举例:
    比如我们打开了word软件,一个任务正在进行文字录入,一个任务是进行自动保存。这两个任务就可以看作是两条线程。

线程依赖进程,一个进程至少有一个线程。

  • 我们现在的计算机,是可以执行多个进程,也就是说可以同时打开多个应用程序。但是我们要知道,在某个时刻,单核CPU并不是同时执行多个进程,单核CPU在某个时刻只能执行一个进程,我们之所以感觉多个进程是同时执行的,是因为CPU会在多个进程间进行高速切换。

那么线程是同时执行的吗?首先我们需要了解两个概念——并发和并行。

  1. 并行:真正意义上的多个任务同时执行。(齐头并进
  2. 并发:是多个任务高速交替执行,某个时刻只能执行一个任务。(交替执行

进程是拥有资源的基本单位,线程是CPU调度的基本单位。CPU直接调度的是线程,不是调度进程。

二、多线程的实现方式

首先我们先要了解Java程序运行原理

  • java.exe命令启动JVM,等于启动了一个应用程序,也就是进程。
  • 该进程会自动启动一个”主线程“,然后主线程会调用某个类的main方法。所以main方法运行在主线程中。
  • JVM是多线程的,因为至少启动了垃圾回收线程和主线程。
  • JAVA封装好了Thread类,我们只需调用该类中的方法,由它调用底层资源去开启线程。

1.多线程实现的方式一

  1. 自己定义一个类,继承Thread类
  2. 重写Thread类中的run()方法——因为run()方法的代码是由线程来执行的。
  3. 创建自定义类的对象
  4. 调用start()方法开启线程
public class 多线程的实现方式1 {
    public static void main(String[] args) {
        MyThread th = new MyThread();
        //th.run();并不是开启线程,只是普通的new对象,调了个方法而已。
        th.start();
        //th.start();多次开启线程是非法的。
        MyThread th2 = new MyThread();
        th2.start();
        //th和th2两个线程并发执行,并不会阻塞主线程的执行。

    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        //为什么要重写run()方法?是因为子线程开启后,run()方法中的代码是由子线程来执行的。
        //一般耗时的操作代码,就可以放到run()方法内,让线程来执行。
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

2.多线程实现的方式二

  1. 定义一个类,实现Runable接口,重写Runable接口中的run()方法
  2. 创建实现这个接口的子类对象
  3. 创建Thread类的对象,把Runable子类对象传进去
  4. 调用start()方法,开启线程
public class 多线程是实现方式2 {
    public static void main(String[] args) {
        MyRunable myRunable = new MyRunable();
        Thread th = new Thread(myRunable);
        th.start();
    }
}
class MyRunable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() +" "+i);
        }

    }
}

3.多线程实现的方式三

  1. 创建一个类实现Callable接口,重写call方法
  2. 建一个FutureTask类将Callable接口的子类对象作为参数传进去
  3. 创建thread类,将FutureTask对象作为参数传进去
  4. 调用start()方法开启线程
public class 多线程的实现方式3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Mycallable mycallable = new Mycallable();
        FutureTask<Object> task = new FutureTask<>(mycallable);
        Thread th = new Thread(task);
        th.setName("线程1");
        th.start();
        //获取子线程执行完后的返回结果
        Object o=task.get();
        System.out.println(o);

    }
}
class Mycallable implements Callable<Object>{
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+"  "+i);
        }
        return 100;
    }
}
/*
前两种方法,主线程中无法获取子线程执行的结果,因为线程中的run()方法,并没有返回值。
但是会有需求,需要得到子线程的结果值。
所以该方法实现多线程的一个优势:
    线程执行完有返回值。
 */

举例使用线程实现方式3,获取子线程的执行结果。

public class 多线程的实现方式3举例 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       //任务1:获取1-100的和
        Mycallable1 mycallable1 = new Mycallable1(100);
        FutureTask task = new FutureTask<>(mycallable1);
        Thread th = new Thread(task);
        th.start();
            //获取子线程的执行结果
        System.out.println(task.get());

        //任务2:获取1-1000的和
        Mycallable1 mycallable11 = new Mycallable1(1000);
        FutureTask<Integer> task1 = new FutureTask<>(mycallable11);
        Thread th1 = new Thread(task1);
        th1.start();
        System.out.println(task1.get());

    }
}
class Mycallable1 implements Callable<Integer> {
    private int num;

    public Mycallable1(int num) {
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        //需求:计算累加和
        int sum=0;
        for (int i = 0; i <= num; i++) {
            sum+=i;
        }
        return sum;
    }
}
/*
实现callable接口,相较于实现runable接口,方法具有返回值,并且可以抛出异常
执行callable方式,需要FutureTask实现类的支持,用于接收运算结果。
 */

三、多线程中常用操作

1.获取和设置线程对象名称

  1. public final String getName()获取线程名称,存在默认值
  2. public final void setName(String name)给线程设置名字
  3. public static Thread currentThread()获取当前正在执行的线程对象
public class 获取和设置线程对象名称 {
    public static void main(String[] args) {
        //3.获取主线程的名称
        Thread thread = Thread.currentThread();//Thread.currentThread()获取当前正在执行的线程对象
        System.out.println(thread.getName());//主线程默认名字为main

        MyTread th = new MyTread();
        //2.setName()给线程设置名字
        th.setName("线程1");
        th.start();

    }
}
class MyTread extends  Thread{
    @Override
    public void run() {
        //该this代表线程对象
        System.out.println(this);
        for (int i = 0; i < 100; i++) {
            //1.getName()获取线程名称,存在默认值
            System.out.println(Thread.currentThread().getName()+"  "+i);

        }

    }
}

2.线程调度及获取和设置线程优先级

  1. 线程的两种调度模型
  • 分时调度模型:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间片(平均分配
  • 抢占式调度模型:优先级高的线程相比较优先级低的线程,获取cpu的时间片相对多一些。(java是抢占式调度模型
    Java中多线程实现的三种方式及线程的常用操作
  1. public final int getPriority()获取线程的优先级,优先级范围是1-10,默认值为5,最低为1,最高为10。
  2. public final void setPriority(int newPriority),设置线程优先级,可使用数字直接设置
public class 线程调度模型及优先级设置 {
    public static void main(String[] args) {
        Mythread1 th1 = new Mythread1();
        th1.setName("线程1");
        //1.getPriority()获取线程的优先级,默认值为5
        System.out.println(th1.getPriority());
        Mythread1 th2 = new Mythread1();
        th2.setName("线程2");
        //setPriority(),设置线程优先级
        //可以使用数字,或者以下方式
        th2.setPriority(Thread.MAX_PRIORITY);
        th1.start();
        th2.start();
    }
}
class Mythread1 extends  Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+"   "+i);

        }
    }
}

3.线程休眠

线程休眠的实际应用: 比如,视频开始之前,播放60秒广告。

  • public static void sleep(long millis) 线程休眠
public class 线程休眠 {
    public static void main(String[] args) {
        Mythread2 th1 = new Mythread2();
        th1.setName("线程1");
        th1.start();
    }
}
class Mythread2 extends Thread{
    @Override
    public void run() {
        //this.sleep(),出现编译器异常,不能抛异常只能抓
        // (因为父类方法如果没有抛出异常,子类重写该方法不能抛出异常)
        try {
            this.sleep(1000*3);//单位是毫秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "   " + i);
        }
    }
}

4.守护线程

  1. 用户线程和守护线程
    用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。
  2. 用户线程和守护线程的适用场景
    由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
  3. public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
    该方法必须在启动线程前调用。
public class 守护线程 {
    public static void main(String[] args) {
        //三个线程并发执行,主线程循环次数少,率先执行完。
        //需求:主线程(用户线程)执行完,子线程(守护线程)就要死亡掉
        Thread.currentThread().setName("刘备");
        MyThread4 th1 = new MyThread4();
        th1.setName("关羽");
        MyThread4 th2 = new MyThread4();
        th2.setName("张飞");
        //将子线程设置为守护线程
            //在线程开启之前setDaemon(true)
        th1.setDaemon(true);
        th2.setDaemon(true);
        th1.start();
        th2.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+i);

        }

    }
}
class MyThread4 extends MyThread{
    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
            System.out.println(Thread.currentThread().getName() + "   " + i);
        }
    }
}

5.中断线程

  1. public final void stop(): 停止线程的运行
    代码示例:
/*
需求:子线程执行3秒后,停止执行
*/
public class 停止线程 {
    public static void main(String[] args) throws InterruptedException {
        Mythread5 th1 = new Mythread5();
        Mythread5 th2 = new Mythread5();
        th1.setName("线程1");
        th2.setName("线程2");
        th1.start();
        //先让主线程休息3秒,那么子线程会执行3秒
        Thread.sleep(1000*3);
        //跳入该行,子线程停止运行
        th1.stop();
        th2.start();

    }
}
class Mythread5 extends Thread{
    @Override
    public void run() {
        //停止线程的方法this.stop();
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);

        }
    }
}
  1. public void interrupt():中断线程,当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
    代码示例:
public class 中断线程 {
    public static void main(String[] args) throws InterruptedException {
        Mythread6 th1 = new Mythread6();
        th1.setName("线程1");
        th1.start();
        //主线程休息两秒,即子线程执行两秒,(先休息两秒)
        Thread.sleep(1000*2);
        //接着唤醒线程1,即线程1只休息了2秒
        th1.interrupt();

    }
}

class Mythread6 extends Thread {
    @Override
    public void run() {
        try {
            //线程休眠10秒,让线程处于一种阻塞的状态
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);

        }
    }
}

6.线程礼让

  • public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
    代码示例:
public class 线程礼让 {
    public static void main(String[] args) {
        Mythread8 th1 = new Mythread8();
        Mythread8 th2 = new Mythread8();
        th1.setName("大哥");
        th2.setName("小弟");
        th1.start();
        th2.start();

    }
}

class Mythread8 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            //线程礼让:理想状态是两个线程交替执行
            Thread.yield();
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

效果图:
Java中多线程实现的三种方式及线程的常用操作

7.线程加入

  • public final void join() 等待该线程执行完毕以后,其他线程才能再次执行。
/*
需求:将三个并发执行的线程,改为串行
*/
public class 线程加入 {
    public static void main(String[] args) throws InterruptedException {
        //三个子线程并发执行
        Mythread7 th1 = new Mythread7();
        Mythread7 th2 = new Mythread7();
        Mythread7 th3 = new Mythread7();
        th1.setName("大哥");
        th2.setName("二弟");
        th3.setName("三弟");
        //join()设置线程加入,可以让多个线程从并发执行,变为串行
        //注意代码书写顺序,是在开启执行之后加入
        th1.start();
        th1.join();
        th2.start();
        th2.join();
        th3.start();
        th3.join();
    }
}
class Mythread7 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

本文地址:https://blog.csdn.net/m0_46988935/article/details/112847702