Java多线程还不会的进来吧,为你量身打造

news2024/10/5 13:06:10

💗推荐阅读文章💗

  • 🌸JavaSE系列🌸👉1️⃣《JavaSE系列教程》
  • 🌺MySQL系列🌺👉2️⃣《MySQL系列教程》
  • 🍀JavaWeb系列🍀👉3️⃣《JavaWeb系列教程》
  • 🌻SSM框架系列🌻👉4️⃣《SSM框架系列教程》

🎉本博客知识点收录于🎉👉🚀《JavaSE系列教程》🚀—>✈️12【多线程、锁机制、lock锁】✈️

文章目录

  • 一、多线程概念
    • 1.1 程序的并发与并行
      • 1.1.1 程序的并行
      • 1.1.2 程序的并发
    • 1.2 进程与线程
      • 1.2.1 进程
      • 1.2.2 线程
      • 1.2.3 多线程并发就一定快吗?
  • 二、Java中的多线程
    • 2.1 Java线程体验
      • 2.1.1 线程初体验
      • 2.1.2 线程执行流程
    • 2.2 线程类
      • 2.2.1 常用方法
      • 2.2.2 使用Runnable创建线程
      • 2.2.3 Thread和Runnable的区别
      • 2.2.4 使用匿名内部类创建线程
        • 1)回顾匿名内部类:
        • 2)使用匿名内部类创建线程
      • 2.2.5 使用Lambda表达式创建线程
    • 2.3 线程的操作
      • 2.3.1 线程的休眠
      • 2.3.2 线程的加入
        • 1)join方法示例
        • 2)join方法的应用场景
        • 3)join方法注意事项
      • 2.3.3 守护线程
      • 2.3.4 线程优先级
      • 2.3.5 线程礼让
      • 2.3.6 线程中断
        • 1)interrupt中断线程
        • 2)中断线程的其他情况
      • 2.3.7 线程的其他方法
        • 1)线程退出
        • 2)线程挂起
    • 2.4 Callable实现线程
      • 2.4.1 Callable的使用
      • 2.4.2 Callable案例

一、多线程概念

在实际应用中,多线程非常有用。例如,一个浏览器可以同时下载几幅图片,一个WEB浏览器需要同时服务来自客户端的请求,我们的电脑管家也可以一边杀毒一边清理垃圾再一边进行电脑体检等任务,这些都是多线程的应用场景。

1.1 程序的并发与并行

1.1.1 程序的并行

程序的并行指的是多个应用程序真正意义上的同时执行,CPU分配多个执行单元共同执行这些任务,效率高,但这依赖于CPU的硬件支持,需要CPU多核心的支持,单核处理器的CPU是不能并行的处理多个任务的。

1.1.2 程序的并发

程序的并发指的是多个应用程序交替执行,CPU分配给每个应用程序一些“执行时间片”用于执行该应用程序,由于CPU的处理速度极快,并且分配个每个线程的“执行时间片”极短,给人们造成视觉上的误感,让人们以为是“同时”执行,其实是交替执行
需要注意的是:虽然是交替执行,但是程序的并发解决了多个程序之间不能“同时”执行的问题,并且程序的并发利用了CPU的空余时间,能将CPU的性能较好的发挥,另外并发不受CPU硬件的限制,实际开发中,并发往往使我们考虑的重点。

Tips:程序并行执行需要依赖于CPU的硬件支持,而并发却不需要;


1.2 进程与线程

1.2.1 进程

  • 进程:是指一个内存中运行的应用程序,我们开启的应用如QQ、微信、google浏览器、idea开发工具等都是一个应用,一个应用最少具备一个进程,也有可能有多个进程,每个进程都有一个独立的内存空间,进程是系统运行程序的基本单位;

Tips:多个进程的执行可以是并行也可以是并发;

1.2.2 线程

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,是一个程序内部的一条执行路径,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序;


关于进程和线程的概念我们理解即可,上图中电脑管家的“首页体检”、“病毒查杀”等功能也有可能是一个进程来完成,关于病毒查杀功能下面可能还有其他小功能,有可能是线程完成,也有可能还是一个独立的进程来完成;

1.2.3 多线程并发就一定快吗?

我们知道,并发本质上其实是多条线程交替执行,线程在交替过程中需要损耗一部分性能,由于CPU分配给这些线程执行的时间片非常短,线程交替也非常频繁,因此线程交替是一个比较消耗性能的步骤;
在大部分情况下,多线程的并发能够提升我们程序的执行速度,如:

  • 当应用程序需要同时处理多个任务时,每一个任务都需要花费大量的时间,这个时候我们可以开辟多条程序执行线路来并发的"同时"处理多个任务;
  • 但是当任务处理时间很短,这个时候根本不需要开启多个线程来"同时"处理多个任务,因为任务处理时间非常短暂,还没等CPU切换到其他线程任务就执行完毕了,这个时候多线程反而使得程序效率低;

这就好比如我们的任务是"烧水",我们需要烧开10壶水,每一壶水的烧开都是一个漫长的时间过程。

  • 在单线程环境中:在水烧开的过程中,CPU只能干等着,等第一壶水烧开了后,才可以烧第二壶水,以此类推…这样效率非常慢
  • 在多线程环境中:在水烧开的过程中,CPU去分配时间去其他的线程,让其他的线程也来烧水,这样可以让多个水壶同时烧水,效率快;

这样下来,多线程效率更高;
但是现在我们的任务如果变为了"拍蒜",我们需要拍10个蒜,拍一瓣蒜的速度非常快;

  • 在单线程环境中:拿起一把刀拍一个蒜,然后马上拍另一瓣蒜…拍10个蒜的时间花费8秒。
  • 在多线程环境中:拿起一把刀拍一个蒜,然后马上换另一把刀拍一个蒜…拍10个蒜的时间花费15秒。

这样下来,单线程效率更高;

Tips:在上述案例中,不管是"烧水"还是"拍蒜"都是一个人(CPU核心)在操作多个器具(调度多个线程),如果出现了多个人来同时操作多个器具那就不属于并发的范畴了,而是属于并行;

二、Java中的多线程

2.1 Java线程体验

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序代码。
继承Thread类都将变为线程类,调用Thread类中的start()方法即开启线程;当线程开启后,将会执行Thread类中的run方法,因此我们要做的就是重写Thread中的run方法,将线程要执行的任务由我们自己定义;

2.1.1 线程初体验

  • 定义线程类:
package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro: 继承Thread类称为线程类
 */
public class MyThread extends Thread {
    public MyThread() {
    }
    /**
     * 重写父类的构造方法,传递线程名称给父类
     *
     * @param name
     */
    public MyThread(String name) {
        super(name);
    }
    /*
            重写run方法,当线程开启后,将执行run方法中的程序代码
         */
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(getName() + "线程正在执行: " + i);
        }
    }
}
  • 测试类:
package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        MyThread thread = new MyThread("线程1");
        // 开启新的线程
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main线程正执行: " + i);
        }
    }
}

运行结果:

运行测试代码,观察是否交替执行;如果没有,可能是因为执行任务太少,CPU分配的一点点时间片就足以将线程中的任务全部执行完毕,可以扩大循环次数;观察效果;

2.1.2 线程执行流程

首先程序运行开启main线程执行代码,执行start()方法时开启一条新的线程来执行任务,新的线程与main线程争夺CPU的执行权在交替执行;

2.2 线程类

2.2.1 常用方法

构造方法:

  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

常用方法:

  • public String getName():获取当前线程名称。
  • public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run():此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用。

我们前面定义线程时说到过,run方法中规定了线程执行的任务,因此我们重写run方法即可;
现在我们翻开run方法的源码看看:

public class Thread implements Runnable {
    private volatile String name;
    private int            priority;
    private Thread         threadQ;
	....
    /* What will be run. */
    private Runnable target;
 
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    public Thread(String name) {
        init(null, null, name, 0);
    }
    ...
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    ...
}

发现执行的是Runnable对象的run方法,我们打开Runnable查看源码:

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

发现Runnable是个接口,并且只有一个抽象方法run()

@FunctionalInterface:标注此注解的接口只有一个抽象方法,也被称为函数式接口;

2.2.2 使用Runnable创建线程

我们前面翻阅源码得知,Thread执行的run方法实质就是执行Runnable接口中的run方法,因此我们可以传递一个Runnable对象给Thread,此Runnable封装了我们要执行的任务;
采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程;
  • 定义Runnable接口:
package com.dfbz.demo02;
/**
 * @author lscl
 * @version 1.0
 * @intro: 创建一个类实现Runnable接口
 */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            // 获取当前线程对象的引用
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName() + "执行: " + i);
        }
    }
}
  • 测试类:
package com.dfbz.demo02;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 任务对象
        MyRunnable runnable = new MyRunnable();
        // 将任务对象传递给线程执行
        Thread thread = new Thread(runnable,"线程1");
        // 开启线程
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main线程执行: " + i);
        }
    }
}

运行结果:

2.2.3 Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

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

2.2.4 使用匿名内部类创建线程

1)回顾匿名内部类:

  • 定义吃辣接口:
package com.dfbz.demo03;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public interface Chili {
    void chili();
}
  • 定义人类来实现接口并且重写方法:
package com.dfbz.demo03;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Person implements Chili {
    @Override
    public void chili() {
        System.out.println("贵州煳辣椒~");
    }
}
  • 测试类(不适用匿名内部类):
package com.dfbz.demo03;
/**
 * @author lscl
 * @version 1.0
 * @intro:  不使用匿名内部类
 */
public class Demo01 {
    public static void main(String[] args) {
        // 需要自己创建一个真实的类(Person),然后重写抽象方法(chili)
        Chili chili=new Person();
        chili.chili();
    }
}
  • 使用匿名内部类:

格式如下:

接口名 xxx=new 父类名或者接口名(){
    // 方法重写
    @Override
    public void method() {
        // 执行语句
    }
};

测试代码:

package com.dfbz.demo03;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02 {
    public static void main(String[] args) {
        /*
        相当于:
        class Abc(匿名) implements Chili{
            @Override
            public void chili() {
                System.out.println("余干辣椒~");
            }
        }
        // 多态
        Chili abc=new Abc();
         */
        // 返回的一个Chili的子类(相当于定义了一个匿名的类,并且创建了这个匿名类的实例对象)
        Chili abc = new Chili() {         // abc是Chili接口的子类对象
            // 重写抽象方法
            @Override
            public void chili() {
                System.out.println("余干辣椒~");
            }
        };
        // 调用重写的方法
        abc.chili();
    }
}

我们发现可以直接new接口的方式重写其抽象方法,返回一个该接口的子类(该子类是匿名的);

2)使用匿名内部类创建线程

package com.dfbz.demo03;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03 {
    public static void main(String[] args) {
        /**
         相当于:
         public class Xxx implements Runnable{
            @Override public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("线程1执行: " + i);
                }
            }
         }
         Runnable runnable = new Xxx();
         */
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("线程1执行: " + i);
                }
            }
        };
        // 创建一个线程类,并传递Runnable的子类
        Thread thread = new Thread(runnable);
        // 开启线程
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main线程: " + i);
        }
    }
}

2.2.5 使用Lambda表达式创建线程

  • 示例代码:
package com.dfbz.demo03;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02 {
    public static void main(String[] args) {
        // 使用Lambda表达式获取Runnable实例对象
        Runnable runnable = () -> {
            for (int i = 0; i < 1000; i++) {
                System.out.println("线程1: " + i);
            }
        };
        Thread thread = new Thread(runnable);
        thread.run();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main线程: " + i);
        }
    }
}

2.3 线程的操作

2.3.1 线程的休眠

  • public static void sleep(long millis):让当前线程睡眠指定的毫秒数

测试代码:

package com.dfbz.demo04;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        // 使用匿名内部类开启1个线程
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    //当i等于50的时候让当前线程睡眠1秒钟(1000毫秒)
                    if (i == 50) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }.start();
        // 使用匿名内部类开启第2个线程
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }.start();
    }
}

在JDK1.5退出了TimeUnit类,该类可以根据时间单位来对线程进行睡眠操作;
示例代码:

public static void main(String[] args) {
    new Thread("线程A"){
        @Override
        public void run() {
            try {
                // jdk1.5推出的新的睡眠方法
                TimeUnit.SECONDS.sleep(1);
                System.out.println("线程A....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }.start();
    System.out.println("main..");
}

2.3.2 线程的加入

多条线程时,当指定线程调用join方法时,线程执行权交给该线程,必须等到调用join方法的线程执行完全部任务后才会释放线程的执行权,其他线程才有可能争抢到线程执行权;

  • public final void join():让调用join方法的线程在当前线程优先执行,直至调用join方法的线程执行完毕时,再执行本线程;
  • public final void join(long millis):让线程执行millis毫秒,然后将线程执行权抛出,给其他线程争抢

1)join方法示例

【示例代码】:

package com.dfbz.demo04;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        //创建线程1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("线程1:" + i);
                }
            }
        });
        //创建线程2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("线程2:" + i);
                    if (i == 500) {
                        try {
                            //当i等于500的时候,让t1线程加入执行,直至执行完毕
//                            t1.join();
                            //当i等于500的时候,让t1线程加入执行,执行10毫秒之后交出执行权
                            t1.join(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        t1.start();
        t2.start();
    }
}

2)join方法的应用场景

【join方法小案例】:

static int num = 0;
public static void main(String[] args) {
    Thread t1 = new Thread() {
        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num=10;
        }
    };
    t1.start();
    
    System.out.println(num);            // ?
}

我们在main线程中开启了一个新的线程(t1),t1线程对num进行赋值,然后再main线程中进行打印,很显然num的值为0,因为t1线程的阻塞不会让main线程也阻塞,当t1线程阻塞时,main线程会继续往下执行;
【使用join方法改造】:

static int num = 0;
public static void main(String[] args) {
    Thread t1 = new Thread() {
        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num = 10;
        }
    };
    try {
        // 必须让t1线程执行完毕才能执行下面的代码
        t1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(num);            // 10
}

Tips:join方法一般应用于线程2依赖于线程1执行的返回结果时;

3)join方法注意事项

【注意事项1】:当线程执行join方法传递时间参数时,如果join线程任务执行完毕,则不必等待join时间结束;

static int count = 0;
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count = 10;
        }
    };
    long startTime = System.currentTimeMillis();
    t1.start();
    // 让t1线程执行完毕
//    t1.join();
    // 让t1线程执行1s,然后代码继续往下执行
    t1.join(1000);
    // 让t1线程执行3s,但如果t1线程执行完毕了,该方法也会结束
//        t1.join(3000);
    long endTime = System.currentTimeMillis();
    // count【10】,time【2011】
    System.out.printf("count【%s】,time【%s】", count, (endTime - startTime));
}
  • 执行效果如下:
t1.join();
count【10】,time【2003】
----------------------------------------
t1.join(1000);
count【0】,time【1005】
----------------------------------------
t1.join(3000);
count【10】,time【2006】

【注意事项2】:当线程执行join方法时,优先执行join线程的任务,等到join线程任务执行完毕时才会执行本线程,但如果还有其他线程与执行join方法的线程同时存在时,则其他线程与join线程交替执行;

public static void main1(String[] args) {
    Thread t1 = new Thread("t1") {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    };
    Thread t2 = new Thread("t2") {
        @Override
        public void run() {
            while (true) {
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    Thread t3 = new Thread("t3") {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    };
    t1.start();
    t2.start();
    t3.start();
}

执行代码,发现t1和t3线程交替执行;

2.3.3 守护线程

当用户线程(非守护线程)运行完毕时,守护线程也会停止执行,但由于CPU运行速度太快,当用户线程执行完毕时,将信息传递给守护线程,会有点时间差,而这些时间差会导致还会执行一点守护线程;

Tips:不管开启多少个线程(用户线程),守护线程总是随着第一个用户线程的停止而停止,例如JVM的垃圾回收器线程就是一个守护线程;

  • public final void setDaemon(boolean on):设置线程是否为守护线程

示例代码:

package com.dfbz.demo04;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 2000; i++) {
                    System.out.println("守护线程1: " + i);
                }
            }
        });
        //将t1设置为守护线程
        t1.setDaemon(true);
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("用户线程2: " + i);
                }
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("用户线程3: " + i);
                }
            }
        });
        //开启三条线程,不管是t2还是t3线程执行完毕,守护线程都会停止
        t1.start();
        t2.start();
        t3.start();
    }
}

2.3.4 线程优先级

默认情况下,所有的线程优先级默认为5,最高为10,最低为1。优先级高的线程更容易让线程在抢到线程执行权;
通过如下方法可以设置指定线程的优先级:

  • public final void setPriority(int newPriority):设置线程的优先级。

示例代码:

package com.dfbz.demo04;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("线程1: " + i);
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("线程2: " + i);
                }
            }
        });
        //设置优先级
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}

2.3.5 线程礼让

在多线程执行时,线程礼让,告知当前线程可以将执行权礼让给其他线程,礼让给优先级相对高一点的线程,但仅仅是一种告知,并不是强制将执行权转让给其他线程,当前线程将CPU执行权礼让出去后,也有可能下次的执行权还在原线程这里;如果想让原线程强制让出执行权,可以使用join()方法

  • public static void yield():将当前线程的CPU执行权礼让出来;

示例代码:

package com.dfbz.demo04;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("线程1: " + i);
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i == 10) {
                        //当i等于10的时候该线程礼让(礼让之后有可能下次线程执行权还被线程2抢到了)
                        Thread.yield();
                    }
                    System.out.println("线程2: " + i);
                }
            }
        });
        t1.start();
        t2.start();
    }
}

2.3.6 线程中断

1)interrupt中断线程

  • public void interrupt():将当前线程中断执行,并且将线程的中断标记设置为true;但是需要注意,如果被中断的线程正在sleep、wait、join等操作,那么将会出现InterruptedException异常,并且清空打断标记(此时打断标记还是false);
  • public boolean isInterrupted():获取当前线程的中断标记;

示例代码:

package com.dfbz.demo01;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo13_线程中断 {
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(() -> {
            while (true) {
 				System.out.println("t1: " + Thread.currentThread().isInterrupted());
            }
        }, "t1");
        t1.start();
        Thread.sleep(10);
        t1.interrupt();         // 中断线程,将中断状态设置为true
        System.out.println(t1.isInterrupted());         // true
    }
}

Tips:中断线程并且不是将线程停止,只是将线程的中断标记设置为true;

借助中断标记,我们可以采用如下的方式来优雅的停止线程:

public static void main(String[] args) throws Exception {
    Thread t1 = new Thread(() -> {
        while (true) {
            // 获取当前线程的中断标记
            boolean interrupted = Thread.currentThread().isInterrupted();
            if (interrupted) {
                System.out.println("线程被中断【" + interrupted + "】....");
                System.out.println("释放资源....");
                break;
            } else {
                System.out.println("执行任务【" + interrupted + "】.....");
            }
        }
    }, "t1");
    t1.start();
    Thread.sleep(10);
    t1.interrupt();         // 中断线程,将中断状态设置为true
}

2)中断线程的其他情况

需要注意的是,被中断的线程如果正在处于sleep、wait、join等操作中,将会抛出InterruptedException异常,然后清空打断标记(此时打断标记还是false);

public static void main(String[] args) throws Exception {
    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "t1");
    // 启动线程
    t1.start();
    Thread.sleep(50);
    // 中断t1线程,将中断标记设置为true(但此时t1线程正在sleep,因此线程会出现异常,并且中断标记还是false)
    t1.interrupt();
    System.out.println(t1.isInterrupted());
}

2.3.7 线程的其他方法

1)线程退出

  • public final void stop():退出当前线程

示例代码:

package com.dfbz.demo04_线程的其他操作;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_线程的退出 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                System.out.println("hello【" + i + "】");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");
        t1.start();
        Thread.sleep(2000);
        // 退出线程
        t1.stop();
        System.out.println("end");
    }
}

2)线程挂起

  • public final void suspend():暂停当前线程的执行;
  • public final void resume():恢复被暂停的线程;

示例代码:

package com.dfbz.demo04_线程的其他操作;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_线程的挂起与恢复 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                System.out.println("hello【" + i + "】");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");
        t1.start();
        Thread.sleep(2000);
        // 挂起线程
        t1.suspend();
        System.out.println("线程挂起...");
        Thread.sleep(2000);
        t1.resume();
        System.out.println("线程恢复....");
    }
}

2.4 Callable实现线程

2.4.1 Callable的使用

我们前面学习过,Thread是Java中的线程类,Runnable接口封装了线程所要执行的任务;当线程开启后(调用start方法)则会执行Runnable中的run方法;Callable适用于执行某个任务后需要有返回值响应的情况。例如发送短信是否成功、订单是否更新成功、发起远程调用响应的结果等…

  • Callable使用示例:
package com.dfbz.demo02;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) throws Exception {
        // 创建一个Callable任务
        MyCallable myCallable = new MyCallable();
        // 封装成task(线程要执行的任务,最终会执行task里面封装的Callable里面的任务)
        FutureTask<String> task1 = new FutureTask<>(myCallable);
        FutureTask<String> task2 = new FutureTask<>(myCallable);
        // 开启线程执行任务
        new Thread(task1).start();
        new Thread(task2).start();
        // 获取任务执行结果(会造成线程阻塞,必须等线程任务完全执行完毕才会有结果返回)
        Object result_1 = task1.get();
        Object result_2 = task2.get();
        System.out.println("执行结果:【" + result_1 + "】");
        System.out.println("执行结果:【" + result_2 + "】");
    }
}
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "【" + i + "】");
        }
        return "执行任务成功!";
    }
}

2.4.2 Callable案例

创建API类,分别提供发送短信方法、文件下载方法;使用异步(使用多线程)和非异步方式(不使用多线程),查看执行效率;

package com.dfbz.demo02;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) throws Exception {
        // 没有使用多线程异步调用
        sync();
//        async();
    }
    // 异步调用
    public static void async() throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        Api api = new Api();
        // 发送短信的任务
        Callable<String> msgCallable = new Callable<String>() {
            @Override
            public String call() {
                String result = api.sendMsg();
                return result;
            }
        };
        // 下载文件的任务
        Callable<String> uploadCallable = new Callable<String>() {
            @Override
            public String call() {
                String result = api.sendMsg();
                return result;
            }
        };
        // 封装成Task
        FutureTask<String> msgTask = new FutureTask<String>(msgCallable);
        FutureTask<String> uploadTask = new FutureTask<String>(uploadCallable);
        // 执行任务
        new Thread(msgTask).start();
        new Thread(uploadTask).start();
        // 获取线程任务执行的结果集
        String msgResult = msgTask.get();
        String uploadResult = msgTask.get();
        System.out.println("发送短信:【" + msgResult + "】");
        System.out.println("下载文件:【" + uploadResult + "】");
        long endTime = System.currentTimeMillis();
        System.out.println("花费时间:【" + (endTime - startTime) + "】");
    }
    // 同步调用
    public static void sync() {
        long startTime = System.currentTimeMillis();
        Api api = new Api();
        // 发送短信
        String msgResult = api.sendMsg();
        // 下载文件
        String uploadResult = api.upload();
        System.out.println("发送短信:【" + msgResult + "】");
        System.out.println("下载文件:【" + uploadResult + "】");
        long endTime = System.currentTimeMillis();
        System.out.println("花费时间:【" + (endTime - startTime) + "】");
    }
}
class Api {
    /**
     * 模拟发送短信
     *
     * @return
     */
    public String sendMsg() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "短信发送成功!";
    }
    /**
     * 模拟下载文件
     *
     * @return
     */
    public String upload() {
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "文件下载成功!";
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/393917.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Anaconda的安装及使用

Anaconda集成了常用的扩展包&#xff0c;能够方便地对这些扩展包进行管理&#xff0c;比如安装和卸载包&#xff0c;这些操作都需要依赖conda。conda是一个在Windows、Mac OS和Linux上运行的开源软件包管理系统和环境管理系统&#xff0c;可以快速地安装、运行和更新软件包及其…

升压模块直流隔离低压转高压稳压电源5v12v24v转50V100V110V150V200V250V400V500V600V800V1000V

特点效率高达80%以上1*2英寸标准封装单电压输出价格低稳压输出工作温度: -40℃~85℃阻燃封装&#xff0c;满足UL94-V0 要求温度特性好可直接焊在PCB 上应用HRB W2~40W 系列模块电源是一种DC-DC升压变换器。该模块电源的输入电压分为&#xff1a;4.5~9V、9~18V、及18~36VDC标准&…

计算机网络--网络基础

目录 一.互联网的组成 ​编辑 1.互联网的边缘部分 1.1客户-服务器方式 1.2对等连接方式 ​编辑 2.互联网的核心部分 2.1电路交换 2.2分组交换 2.3报文交换 二.计算机网络的类别 1.按网络的作用范围进行分类 2.按网络的使用者进行分类 3.用来把用户接入互联…

I.MX6ULL_Linux_系统篇(23) busybox文件系统构建

Linux“三巨头”已经完成了 2 个了&#xff0c;就剩最后一个 rootfs(根文件系统)了&#xff0c;本章我们就来学习一下根文件系统的组成以及如何构建根文件系统。这是 Linux 移植的最后一步&#xff0c;根文件系统构建好以后就意味着我们已经拥有了一个完整的、可以运行的最小系…

零代码工具我推荐Oracle APEX

云原生时代零代码工具我推荐Oracle APEX 国内的低码开发平台我也看了很多&#xff0c;感觉还是不太适合我这个被WEB抛弃的老炮。自从看了Oracle APEX就不打算看其它的了。太强大了&#xff0c;WEB服务器都省了&#xff0c;直接数据库到WEB页面。功能很强大&#xff0c;震撼到我…

快速将小程序生成APP八步走!

在开始之前&#xff0c;让我们一起来了解一下Finclip吧&#xff01; Finclip是凡泰极客公司研发的一款在开发体验、学习门槛、应用场景、生态能力等方面都体现巨大优势的小程序容器。不论是移动APP&#xff0c;还是电脑、电视、车载主机等设备&#xff0c;在集成Finclip小程序…

代码随想录算法训练营day52 | 动态规划之子序列 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组

day52300.最长递增子序列1.dp[i]的定义2.状态转移方程3.dp[i]的初始化4.确定遍历顺序5.举例推导dp数组674. 最长连续递增序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组718. 最长重复子数组1…

Linux驱动交叉编译把驱动文件放入开发板,以及printk函数打印级别

上一篇介绍了一个最简单的驱动程序和驱动程序大体结构&#xff0c;但那还是用本地编译只能在Ubuntu上运行&#xff0c;我们该怎么编译一个能加载到开发板上呢&#xff0c;就需要交叉编译&#xff0c;交叉编译通常都是在嵌入式开发中使用到的。 交叉编译 理解交叉编译前先了解…

TypeScirpt 入门与实战 学习笔记

文章目录求一键三连前言了解TS的前世今生基本实现数据类型枚举类型 enum&#xff08;用的少&#xff09;顶端类型&#xff08;通用类型&#xff09;anyunknown对比never数组只读 &#xff1a;readonly元组 类型objectObject和Object&#xff08;&#xff09;区分命名空间&#…

论文阅读 :Masked Autoencoders As Spatiotemporal Learners

NeurIPS2022——Masked Autoencoders As Spatiotemporal Learners Keywords&#xff1a; Videos&#xff1b;object detection&#xff1b; 文章目录NeurIPS2022——Masked Autoencoders As Spatiotemporal Learners研究动机本文贡献Introduction & Related work整体架构&…

redis进阶:mysql,redis双写一致性,数据库更新后再删除缓存就够了吗?

0. 引言 最近线上的一个状态修改功能出现了问题&#xff0c;一开始是运营找了过来&#xff0c;运营告知某条数据的状态已经开启了的&#xff0c;但是实际使用起来还是没有生效&#xff0c;于是拿到这个问题后&#xff0c;首先就去数据库查了这条数据&#xff0c;发现确实如他所…

深入了解字典树

字典树&#xff08;Trie&#xff09; 目录字典树&#xff08;Trie&#xff09;一、问题引入二、字典树介绍3、字典树的实现4、存储与查询一、问题引入 现有长度为n的字符串数组&#xff0c;[“go”&#xff0c;“goog”&#xff0c;“google”&#xff0c;“golang”&#xff0…

【数据结构入门】-链表之双向循环链表

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【数据结构初阶&#xff08;C实现&#xff09;】 文章目录链表初始化打印链表尾插尾删新建一个节点头插头删查找在pos之前插入*删除pos位…

CSS中的伪元素和伪类

一直被伪类和伪元素所迷惑&#xff0c;以为是同一个属性名称&#xff0c;根据CSS动画&#xff0c;索性开始研究a:hover:after&#xff0c;a.hover:after的用法。 伪元素 是HTML中并不存在的元素&#xff0c;用于将特殊的效果添加到某些选择器。 对伪元素的描述 伪元素有两…

【Verilog】握手信号实现跨时钟域数据传输-handshake

文章目录handshake握手电路使用握手信号实现跨时钟域数据传输接口信号图题目描述解题思路代码设计数据发送模块data_driver数据接收模块data_receivertestbench波形handshake握手电路 跨时钟域处理是个很庞大并且在设计中很常出现的问题握手(handshake)是用来处理信号跨时钟域…

数字化引领乡村振兴,VR全景助力数字乡村建设

一、数字乡村建设加速经济发展随着数字化建设的推进&#xff0c;数字化农业产业正在成为农业产业发展的主导力量&#xff0c;因此数字化技术赋予农业产业竞争力的能力不可小觑。数字化乡村建设背景下&#xff0c;数字化信息技术将全面改造升级农村产业&#xff0c;从农业、养殖…

new set数组对象去重失败

我们知道Set是JS的一个种新的数据结构&#xff0c;和数组类似&#xff0c;和数组不同的是它可以去重&#xff0c;比如存入两个1或两个"123"&#xff0c;只有1条数据会存入成功&#xff0c;但有个特殊情况&#xff0c;如果添加到set的值是引用类型&#xff0c;比如数组…

DataGear 4.5.1 发布,数据可视化分析平台

DataGear 4.5.1 发布&#xff0c;严重 BUG 修复&#xff0c;具体更新内容如下&#xff1a; 修复&#xff1a;修复SQL数据集对于DB2、SQLite等数据源预览时会报错的BUG&#xff1b;修复&#xff1a;修复系统对于MySQL、MariaDB等数据源中无符号数值类型有时报错的BUG&#xff1…

借助媛如意让ROS机器人turtlesim画出美丽的曲线-云课版本

首先安装并打开猿如意其次打开蓝桥云课ROS并加入课程在猿如意输入问题得到答案在蓝桥云课ROS验证如何通过turtlesim入门ROS机器人您可以通过以下步骤入门ROS机器人&#xff1a;安装ROS&#xff1a;您需要安装ROS&#xff0c;可以在ROS官网上找到安装指南。安装turtlesim&#x…

英文拼写检查:TX Spell .NET for .NET 10.0 Crack

用于 Windows 窗体应用程序的 TX Text Control .NET 的强大拼写检查和语言工具。 表现 可靠准确的拼写检查 使用 TX Spell .NET for Windows Forms&#xff0c;您可以为基于 TX Text Control 的应用程序添加极其快速、高度可靠和非常准确的拼写检查。将 TX Spell .NET for Wind…