Java多线程

Java多线程

  • 1、线程与进程
    • 1.1、进程
    • 1.2、线程
    • 1.3、多线程应用场景
    • 1.4、总结
  • 2、多线程两个概念
    • 2.1、并发与并行
  • 3、线程的生命周期及五种基本状态
  • 4、多线程实现方式
    • 4.1、继承Thread类重写run()方法
    • 4.2、实现Runnable接口
    • 4.3、使用Callable和Future接口创建线程
  • 5、常见成员方法
    • 5.1、线程对象、名字、睡眠
    • 5.2、线程的调度
    • 5.3、守护线程
    • 5.4、出让线程/礼让线程
    • 5.5、插入线程/插队线程
  • 6、线程安全与线程同步
    • 6.1、同步代码块(synchronized)
    • 6.2、同步方法(synchronized)
    • 6.3、Lock锁
  • 7、死锁
  • 8、生产者和消费者(等待唤醒机制)
    • 8.1、等待唤醒机制
    • 8.2、等待唤醒机制(阻塞队列方式)
    • 8.3、线程的状态
  • 9、线程池

学习Java多线程的路线可以从以下几个方面入手:

  1. 理解线程的概念和基础知识,包括线程的创建、启动和运行。
  2. 学习线程的状态及其相互转换,了解如何控制线程的执行。
  3. 学习并发控制,包括锁、同步和原子变量等。
  4. 了解常用的并发工具类,如Executor框架、CountDownLatch和CyclicBarrier等

1、线程与进程

1.1、进程

是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
进程是操作系统结构的基础;是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动。操作系统中,几乎所有运行中的任务对应一条进程(Process)。一个程序进入内存运行,即变成一个进程。进程是处于运行过程中的程序,并且具有一定独立功能。描述进程的有一句话非常经典的话——进程是系统进行资源分配和调度的一个独立单位。
进程是系统中独立存在的实体,拥有自己独立的资源,拥有自己私有的地址空间。进程的实质,就是程序在多道程序系统中的一次执行过程,它是动态产生,动态消亡的,具有自己的生命周期和各种不同的状态。进程具有并发性,它可以同其他进程一起并发执行,按各自独立的、不可预知的速度向前推进。
(注意,并发性(concurrency)和并行性(parallel)是不同的。并行指的是同一时刻,多个指令在多台处理器上同时运行。并发指的是同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,看起来就好像多个指令同时执行一样。)
进程由程序、数据和进程控制块三部分组成。

1.2、线程

线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
在Java Web中要注意,线程是JVM级别的,在不停止的情况下,跟JVM共同消亡,就是说如果一个Web服务启动了多个Web应用,某个Web应用启动了某个线程,如果关闭这个Web应用,线程并不会关闭,因为JVM还在运行,所以别忘了设置Web应用关闭时停止线程。

1.3、多线程应用场景

1、软件里的耗时操作:拷贝、迁移大文件;加载大量的资源文件

2、所有聊天软件

3、所有后台服务器

1.4、总结

什么是多线程?

有了多线程,我们就可以让程序同时做多件事。

多线程的作用?

充分利用等待时间,让CPU在多个程序之间切换,提高了效率。

多线程的应用场景?

只要你想让多个事情同时运行,你就可以用到多线程。

2、多线程两个概念

2.1、并发与并行

并发:在同一时刻,有多个指令在单个CPU上交替执行。

并行:在同一时刻,有多个指令在单个CPU上同时执行。

在一颗CPU上可能同时出现并发并行

3、线程的生命周期及五种基本状态

Java多线程_第1张图片

线程的生命周期指的是线程从创建到终止的整个过程。在这个过程中,线程会经历五种基本状态:

  1. 新建(New):当一个线程对象被创建时,它处于新建状态。
  2. 就绪(Runnable):当调用线程对象的start()方法后,线程处于就绪状态。此时,它已经具备了运行条件,但是还没有被分配CPU时间。
  3. 运行(Running):当线程获得CPU时间后,它就进入了运行状态。此时,它会执行run()方法中的代码。
  4. 阻塞(Blocked):当线程在运行过程中由于某些原因而暂停执行时,它就进入了阻塞状态。例如,当一个线程等待I/O操作完成或者等待获取锁时,它就会进入阻塞状态。
  5. 终止(Terminated):也称为死亡状态(Dead),当一个线程完成了run()方法中的所有代码或者调用了stop()方法后,它就进入了终止状态。

创建线程对象(新建状态)–>调用start()方法-->有执行资格没有执行权(就绪状态)–>抢到了CPU执行权–>有执行资格有执行权(运行状态)–>①其他线程抢走CPU执行权–>有执行资格没有执行权(就绪状态);②run()执行完毕–>线程死亡变成垃圾(死亡状态);③sleep()或者其他阻塞式方法–>没有执行资格没有执行权(阻塞状态)–>sleep()时间到了其他阻塞方式结束–>有执行资格没有执行权(就绪状态)

4、多线程实现方式

在Java中,有三种主要的方式可以用来创建线程:

  1. 继承Thread类:可以通过继承Thread类并重写run()方法来定义线程的执行内容。然后创建Thread子类的实例并调用start()方法来启动线程。
  2. 实现Runnable接口:可以通过实现Runnable接口并重写run()方法来定义线程的执行内容。然后创建一个Thread对象,将Runnable实现类的实例作为参数传递给Thread构造函数。最后调用Thread对象的start()方法来启动线程。
  3. 除了继承Thread类和实现Runnable接口,还可以使用Callable和Future接口来创建线程。

4.1、继承Thread类重写run()方法

继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。

**例子:**继承Thread类重写run()方法创建多线程输出1~随机数

package com.hippo.javamultithreasding.multithreading;

import lombok.extern.slf4j.Slf4j;

/**
 * @ClassName ThreadDemo1
 * @Description TODO 继承Thread类重写run()方法
 * @Author tangxl
 * @create 2023-03-10 10:55
 **/
@Slf4j
public class ThreadDemo1 extends Thread {
    private String name;
    public ThreadDemo1(String name){
        System.out.println("创建线程:"+name);
    }


    /**
     * 重写Thread的run()方法
     */
    @Override
    public void run() {
        // 循环输出1~随机数
        int randomNumber = (int)(Math.random() * 100) + 1;
        for (int i = 0; i < randomNumber; i++) {
            System.out.println(getName()+"输出"+i);
        }
    }

    public static void main(String[] args) {
        /**
         * 继承Thread类重写run()方法创建多线程:
         * 1.自己定义一个类继承Thread
         * 2.重写run()方法
         * 3.创建子类线程,并启动线程
         */
        // 继承Thread创建线程
        Thread thread1 = new ThreadDemo1("线程A");
        Thread thread2 = new ThreadDemo1("线程B");
        // 线程命名
        thread1.setName("线程A");
        thread2.setName("线程B");
        // 就绪状态
        thread1.start();
        thread2.start();
    }
}

4.2、实现Runnable接口

实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

**例子:**创建了一个实现Runnable接口的类,并重写了run方法来计算1到100的和。然后我们创建了一个Thread对象,并将RunnableExample对象作为参数传递给它。最后,我们调用thread.start()方法来启动线程。

public class RunnableExample implements Runnable {
    @Override
    public void run() {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        System.out.println(sum);
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new RunnableExample());
        thread.start();
    }
}

4.3、使用Callable和Future接口创建线程

Callable接口与Runnable接口类似,都是用来定义线程的执行内容的。不同的是,Callable接口中的call()方法可以返回一个结果,并且可以抛出异常。而Runnable接口中的run()方法没有返回值,并且不能抛出异常。

Future接口表示异步计算的结果。它提供了一些方法来检查计算是否完成,等待计算完成,并获取计算结果。

要使用Callable和Future接口创建线程,需要执行以下步骤:

  1. 定义一个类实现Callable接口,并重写call()方法。
  2. 使用Executors类中的静态工厂方法创建一个ExecutorService对象。
  3. 调用ExecutorService对象的submit()方法,将Callable实现类的实例作为参数传递给该方法。submit()方法会返回一个Future对象。
  4. 调用Future对象的get()方法来获取异步计算的结果。如果计算尚未完成,get()方法会阻塞直到计算完成。

**例子:**我们创建了一个Callable对象,它的call方法计算1到100的和。然后我们使用FutureTask包装这个Callable对象,并将其传递给Thread对象来启动线程。最后,我们调用futureTask.get()方法来获取计算结果。

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

public class CallableExample {
    public static void main(String[] args) throws Exception {
        Callable<Integer> task = () -> {
            int sum = 0;
            for (int i = 1; i <= 100; i++) {
                sum += i;
            }
            return sum;
        };
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }
}

5、常见成员方法

方法名称 说明
String getName() 返回此线程名称
void setName(String name) 设置线程名字(构造方法也可以设置名字)
static Thread currentThread() 获取当前线程的的对象
static void sleep(long time) 让线程休眠指定时间,单位毫秒
setPriority(int newPriority) 设置线程优先级
final int getPriority() 获取线程优先级
final void setDeamon(boolean on) 设置为守护线程
public static void yield() 出让线程/礼让线程
public static void join() 插入线程/插队线程

5.1、线程对象、名字、睡眠

**例子:**获取线程对象,线程命名获取线程名字,线程睡眠。

package com.hippo.javamultithreasding.multithreading;

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

/**
 * @ClassName ThreadDemo04
 * @Description TODO 线程方法使用
 * @Author tangxl
 * @create 2023-03-13 08:36
 **/
public class ThreadDemo4 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个MyCallable类实现Callable接口,重写call(有返回值,表示多线程运行结果)
        Runnable callable = ()->{
            System.out.println(Thread.currentThread().getName() + "执行结果" + ((int) Math.random() * 100) + 1);
        };

        // 创建线程
        Thread t1 = new Thread(callable);
        Thread t2 = new Thread(callable);

        // 线程设置名称
        t2.setName("线程2");

        // 启动线程
        t1.start();
        // 线程睡眠
        Thread.sleep(2000);
        t2.start();

        // 获取当前正在执行的线程
        String ThreadName = Thread.currentThread().getName();
        System.out.println("当前线程名称:" + ThreadName);

        System.out.println("t1线程名称:" + t1.getName());
        System.out.println("t2线程名称:" + t2.getName());
    }
}

5.2、线程的调度

抢占式调度:随机性,多个线程争夺CPU资源,Java中使用抢占式调度,线程优先级越大抢占到CPU资源的概率就越大,优先级最小是1,最大是10,默认是5。

非抢占式调度:轮流性,多个线程轮流执行任务。

**例子:**修改线程优先度执行两个线程。

package com.hippo.javamultithreasding.multithreading;

/**
 * @ClassName ThreadDemo5
 * @Description TODO 线程优先级--java是抢占式调度
 * @Author tangxl
 * @create 2023-03-13 08:56
 **/
public class ThreadDemo5 {

    public static void main(String[] args) {
        // 实现重写call()方法
        Runnable runnable = ()->{
            for (int i = 0; i < 100 ;i++) {
                System.out.println(Thread.currentThread().getName() + "输出:" + i);
            }
        };

        // 创建线程
        Thread t1 = new Thread(runnable,"线程1");
        Thread t2 = new Thread(runnable,"线程2");

        // 获取线程优先级
        int priority1 = t1.getPriority();
        int priority2 = t1.getPriority();
        System.out.println(t1.getName() + "优先级:" + priority1);
        System.out.println(t2.getName() + "优先级:" + priority2);

        // 修改线程优先级
        t1.setPriority(10);
        t2.setPriority(1);
        System.out.println(t1.getName() + "修改后优先级:" + t1.getPriority());
        System.out.println(t2.getName() + "修改后优先级:" + t2.getPriority());

        // 执行线程任务
        t1.start();
        t2.start();

        // 总结:线程优先级范围1~10,默认5,线程优先级越小,线程越优先执行
    }
}

总结:java线程是抢占式调度,线程优先级范围1~10,默认5,线程优先级越小,线程越优先执行

5.3、守护线程

作用:当其他线程结束后,守护线程也会陆续结束

使用场景:QQ–聊天:线程1;文件传输:线程2,当关闭聊天后文件传输也会陆续结束。

**例子:**创建两个线程–线程1、线程2,设置线程2为守护线程

package com.hippo.javamultithreasding.multithreading;

/**
 * @ClassName TreradDemo6
 * @Description TODO 线程方法--开启守护线程
 * @Author tangxl
 * @create 2023-03-13 09:09
 **/
public class TreradDemo6 {

    public static class MyThread1 extends Thread {
        @Override
        public void run() {
            // 循环输出1~10
            for (int i = 1; i < 10 ;i++) {
                System.out.println(getName() + "输出" + i);
            }
        }
    }

    public static class MyThread2 extends Thread {
        @Override
        public void run() {
            // 循环输出1~100
            for (int i = 1; i < 100 ;i++) {
                System.out.println(getName() + "输出" + i);
            }
        }
    }
    public static void main(String[] args) {
        /**
         * final void setDaemon(boolean on) 设置为守护线程
         * 细节:
         *      当其他线程结束后,守护线程也会陆续结束
         * 通俗易懂:
         *      例如下面当线程1结束后,线程2没有执行完也要结束。
         */
        // 创建线程
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();

        // 线程命名
        t1.setName("线程1");
        t2.setName("线程2");

        // 把线程2设置为守护线程(备胎线程)
        t2.setDaemon(true);

        // 执行线程
        t1.start();
        t2.start();
    }
}

5.4、出让线程/礼让线程

作用:线程礼让是指线程主动放弃当前获得的CPU时间片,让其他线程获得执行机会。这样可以避免某些线程长时间占用CPU,导致其他线程无法执行。不过,具体的执行顺序还是由操作系统的调度算法决定。

package com.hippo.javamultithreasding.multithreading;

/**
 * @ClassName ThreadDemo7
 * @Description TODO 常见成员方法--出让线程/礼让线程
 * @Author tangxl
 * @create 2023-03-13 10:43
 **/
public class ThreadDemo7 extends Thread{
    @Override
    public void run() {
        // 循环输出1~100
        for (int i = 1; i < 100 ;i++) {
            System.out.println(getName() + "输出" + i);
            // 表示出让当前CPU的执行权
            Thread.yield();
        }
    }

    public static void main(String[] args) {
        /**
         * public static void yield() 出让线程/礼让线程
         */

        // 创建线程
        Thread t1 = new ThreadDemo7();
        Thread t2 = new ThreadDemo7();

        // 线程命名
        t1.setName("线程1");
        t2.setName("线程2");

        // 线程执行
        t1.start();
        t2.start();
    }
}

5.5、插入线程/插队线程

作用:线程插队是指让某个线程先执行完毕,再执行其他线程。这可以通过使用 join()方法来实现。当一个线程调用另一个线程的 join()方法时,当前线程会被阻塞,直到被join()的线程执行完毕。

**例子:**在主线程前插入线程1先执行,等线程1执行结束才会执行main线程。

package com.hippo.javamultithreasding.multithreading;

/**
 * @ClassName ThreadDemo8
 * @Description TODO 常见成员方法--插入线程/插队线程
 * @Author tangxl
 * @create 2023-03-13 11:39
 **/
public class ThreadDemo8 extends Thread {
    @Override
    public void run() {
        // 循环输出1~100
        for (int i = 1; i < 100 ;i++) {
            System.out.println(getName() + "输出" + i);
            // 表示出让当前CPU的执行权
            Thread.yield();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        /**
         * public final void join() 插入线程/插队线程
         */

        // 创建线程
        Thread t1 = new ThreadDemo8();
        //线程命名
        t1.setName("线程1");
        //执行线程
        t1.start();

        // 把线程1插入到当前线程之前(当前线程:main线程)
        t1.join();

        // 执行在main线程中
        for (int i = 1; i < 10; i++) {
            System.out.println("main线程" + i);
        }
    }
}
1月:1、线上充值类割接上线;2、其他资金认领传财辅动态开关控制割接上线;3、营收稽核登录表单控制动态控制开发割接上线;4、线上充值类资金重要表备份;
2月:1、开发智慧财务实时短信发送和群发短信接口;2、联调短渠由IP变域名短信实时发送接口;3、讨论23年度营收稽核需求明细;3、画政企预存款旬报、撤销核销、三级稽核相关流程图和思维导图;4、开发营收稽核政企二级稽核预存款生成旬报和旬报撤销

6、线程安全与线程同步

线程的执行,有随机性,有可能在执行的时候,Cpu执行权被抢走。
当线程获得了执行的数据,将操作的数据锁起来,当线程执行完释放数据,其他线程才能使用。

6.1、同步代码块(synchronized)

synchronized关键字加到代码块上

格式:

synchronized(){
 操作的共享的代码
}

特点:
①锁默认打开,有一个线程进去了,锁自动关闭;
②里面的代码执行完毕,线程出来,锁自动释放。

**例子:**某电影院的观众席有100张座位,现在有3个窗口同时售票,请设计一个程序模拟该售票过程

package com.hippo.javamultithreasding.multithreading;

/**
 * @ClassName ThreaDemo9
 * @Description TODO 线程安全/线程同步--同步代码
 * @Author tangxl
 * @create 2023-03-14 10:10
 **/
public class ThreadDemo9 extends Thread {

    // 票数(加入static关键字,使得所有线程共享该变量)
    static int ticket = 0;

    // 为了保证线程安全,可以将共享的数据封装到一个对象中,然后对该对象进行同步
//    static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            // 同步代码块
             synchronized (ThreadDemo9.class){
                 if (ticket < 100) {
                     try {
                         // 模拟网络延迟
                         Thread.sleep(10);
                     } catch (InterruptedException e) {
                         throw new RuntimeException(e);
                     }
                     ticket++;
                     System.out.println("@"+getName() + "卖出了第" + ticket + "张票");
                 }
                 else {
                     break;
                 }
             }

        }
    }

    public static void main(String[] args) {
        /**
         * 需求:某电影院的观众席有100张座位,现在有3个窗口同时售票,请设计一个程序模拟该售票过程
         */
        // 创建线程
        Thread t1 = new ThreadDemo9();
        Thread t2 = new ThreadDemo9();
        Thread t3 = new ThreadDemo9();

        // 线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        // 线程执行
        t1.start();
        t2.start();
        t3.start();

    }
}

6.2、同步方法(synchronized)

synchronized关键字加到方法上

格式:

修饰符 synchronized 返回值类型 方法名(方法参数){...}

特点:
①同步方法是锁住方法里面的所有代码;
②锁对象不能自己指定,非静态–this,静态–当前类的字节码文件对象。

**例子:**某电影院的观众席有100张座位,现在有3个窗口同时售票,请设计一个程序模拟该售票过程

package com.hippo.javamultithreasding.multithreading;

/**
 * @ClassName ThreadDemo10
 * @Description TODO 线程安全/线程同步--同步方法
 * @Author tangxl
 * @create 2023-03-14 11:39
 **/
public class ThreadDemo10 implements Runnable {

    // 票数
    // S:考虑为什么这里不加static?
    // 因为实现Runnable的类是作为一个参数传给Thread的,这里所有线程共享一个ThreadDemo9对象,所以不需要static
    int ticket = 0;

    @Override
    public void run() {
        // 1、循环
        while (true) {
            // 2、同步代码块(同步方法)
            synchronized (ThreadDemo9.class){
                if (method()) {
                    break;
                }
            }

        }
    }

    private synchronized boolean method() {
        // 3、判断共享数据是否到了末尾,如果没有到末尾
        if (ticket < 100) {
            try {
                // 模拟网络延迟
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            ticket++;
            System.out.println("@"+Thread.currentThread().getName() + "卖出了第" + ticket + "张票");
        }
        // 4、判断共享数据是否到了末尾,如果到了末尾
        else {
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        /**
         * 需求:某电影院的观众席有100张座位,现在有3个窗口同时售票,请设计一个程序模拟该售票过程
         * 同步方法完成
         */
        // 创建Runnable接口的实现类对象
        Runnable runnable = new ThreadDemo10();

        // 创建线程
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);

        // 线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        // 线程执行
        t1.start();
        t2.start();
        t3.start();

    }
}

6.3、Lock锁

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例

作用:可以手动加锁,也可以手动释放锁

**例子:**某电影院的观众席有100张座位,现在有3个窗口同时售票,请设计一个程序模拟该售票过程

package com.hippo.javamultithreasding.multithreading;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName ThreadDemo11
 * @Description TODO 线程安全/线程同步---Lock锁
 * @Author tangxl
 * @create 2023-03-14 14:48
 **/
public class ThreadDemo11 extends Thread {

    // 票数(加入static关键字,使得所有线程共享该变量)
    static int ticket = 0;

    // 为了保证线程安全,可以将共享的数据封装到一个对象中,然后对该对象进行同步
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 同步代码块
            lock.lock();
           try {
               if (ticket < 100) {
                   // 模拟网络延迟
                   Thread.sleep(10);
                   ticket++;
                   System.out.println("@"+getName() + "卖出了第" + ticket + "张票");
               }
               else {
                   break;
               }
           }catch (InterruptedException e) {
               throw new RuntimeException(e);
           } finally {
                lock.unlock();
           }
        }
    }

    public static void main(String[] args) {
        /**
         * 需求:某电影院的观众席有100张座位,现在有3个窗口同时售票,请设计一个程序模拟该售票过程
         */
        // 创建线程
        Thread t1 = new ThreadDemo9();
        Thread t2 = new ThreadDemo9();
        Thread t3 = new ThreadDemo9();

        // 线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        // 线程执行
        t1.start();
        t2.start();
        t3.start();

    }
}

7、死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

**例子:**线程1获得了obj1锁,线程2获得了obj2锁,线程1想要获取obj2锁,线程2想要获取obj1锁,造成死锁

package com.hippo.javamultithreasding.multithreading;

/**
 * @ClassName ThreadDemo12
 * @Description TODO 死锁
 * @Author tangxl
 * @create 2023-03-14 15:13
 **/
public class ThreadDemo12 extends Thread {
    // 定义两个对象作为锁
    static Object obj1 = new Object();
    static Object obj2 = new Object();

    @Override
    public void run() {
        // 线程1先获取obj1锁,再获取obj2锁
        if (getName().equals("线程1")) {
            synchronized (obj1) {
                System.out.println("线程1获取到了obj1锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (obj2) {
                    System.out.println("线程1获取到了obj2锁");
                }
            }
        }
        // 线程2先获取obj2锁,再获取obj1锁
        if (getName().equals("线程2")) {
            synchronized (obj2) {
                System.out.println("线程2获取到了obj2锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (obj1) {
                    System.out.println("线程2获取到了obj1锁");
                }
            }
        }
    }

public static void main(String[] args) {
        // 创建线程
        Thread t1 = new ThreadDemo12();
        Thread t2 = new ThreadDemo12();

        // 线程命名
        t1.setName("线程1");
        t2.setName("线程2");

        // 线程执行
        t1.start();
        t2.start();
    }
}

8、生产者和消费者(等待唤醒机制)

生产者:生产数据
消费者:消费数据

8.1、等待唤醒机制

等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下︰
wait :线程不再活动,不再参与调度,进入wait set中,因此不会浪费CPU资源,也不会去竞争锁了,这时的线程状态即是WAITING。它还要等着别的线程执行一个特别的动作,也即是"通知( notify ) "在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列( ready queue )中
notify :则选取所通知对象的wait set 中的一个线程释放﹔例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
notifyAll :则释放所通知对象的wait set 上的全部线程。

**例子:**实现生产者与消费者(等待唤醒机制),实现线程轮流交替执行的效果

package com.hippo.javamultithreasding.multithreading;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName ThreadDemo13
 * @Description TODO 等待唤醒机制
 * @Author tangxl
 * @create 2023-03-14 15:41
 **/
public class ThreadDemo13 {

    // 定义一个锁对象
    static Lock lock = new ReentrantLock();

    //定义一个生产者--厨师类
    public static class Cook extends Thread {
        @Override
        public void run() {
            while (true) {
                // 同步代码块
                synchronized (ThreadDemo13.lock) {
                    System.out.println("厨师正在做饭");
                    try {
                        // 做饭需要3秒
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("厨师做好了饭");
                    // 唤醒等待的线程
                    ThreadDemo13.lock.notify();
                    try {
                        // 做完饭后,进入等待状态
                        System.out.println("厨师休息");
                        ThreadDemo13.lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    //定义一个消费者--吃货类
    public static class Eater extends Thread {

        @Override
        public void run() {
            while (true) {
                // 同步代码块
                synchronized (ThreadDemo13.lock) {
                    System.out.println("吃货正在吃饭");
                    try {
                        // 吃饭需要3秒
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("吃货吃完了饭");
                    // 唤醒等待的线程
                    ThreadDemo13.lock.notify();
                    try {
                        // 吃完饭后,进入等待状态
                        System.out.println("吃货先等着");
                        ThreadDemo13.lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        /**
         * 实现生产者与消费者(等待唤醒机制),实现线程轮流交替执行的效果
         */
        // 创建线程
        Thread t1 = new Cook();
        Thread t2 = new Eater();

        // 线程命名
        t1.setName("厨师");
        t2.setName("吃货");

        // 线程执行
        t1.start();
        t2.start();
    }
}

8.2、等待唤醒机制(阻塞队列方式)

put数据时:放不进去,会等着,也叫做阻塞。
take数据时:取出第一个数据,取不到会等着,也叫做阻塞。

阻塞队列的继承结构
接口(Iterable、Collection、Quene、BlockingQuene)
实现类(ArrayBlockingQuene、LinkedBlockingQuene)

ArrayBlockingQuene:数组实现,有界。
LinkedBlockingQuene:底层是连表,无界,但不是真的无界,最大为int的最大值。

**例子:**利用阻塞队列完成生产者与消费者(等待唤醒机制),实现线程轮流交替执行的效果

因为使用了同一个阻塞队列里面的锁,输出在锁外面,输出会有一些问题,但是数据没有问题。

package com.hippo.javamultithreasding.multithreading;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @ClassName ThreadDemo14
 * @Description TODO 等待唤醒机制--阻塞队列方式实现
 * @Author tangxl
 * @create 2023-03-14 16:01
 **/
public class ThreadDemo14 {

    //新建一个生产类--厨师
    public static class Cook extends Thread {
        // 定义一个阻塞队列
        ArrayBlockingQueue<String> queue;

        public Cook(ArrayBlockingQueue<String> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true) {
                //不断的把面条放入阻塞队列
                try {
                    queue.put("面条");
                    System.out.println("厨师放了一碗面条");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    //定义一个消费类--吃货
    public static class Eater extends Thread {
        // 定义一个阻塞队列
        ArrayBlockingQueue<String> queue;

        public Eater(ArrayBlockingQueue<String> queue) {
            this.queue = queue;
        }
        @Override
        public void run() {
            while (true) {
                //不断的从阻塞队列取面条出来
                try {
                    String cook = queue.take();
                    System.out.println("吃货拿了一碗面条");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public static void main(String[] args) {
        /**
         * 利用阻塞队列完成生产者与消费者(等待唤醒机制),实现线程轮流交替执行的效果
         * 细节:
         *      生产者和消费者必须使用同一个阻塞队列
         */

        // 创建阻塞队列
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        //创建线程,并把阻塞对象传入线程
        Thread cook = new Cook(queue);
        Thread eater = new Eater(queue);

        //线程命名
        cook.setName("厨师");
        eater.setName("吃货");

        //执行线程
        cook.start();
        eater.start();
    }
}

8.3、线程的状态

Java多线程_第2张图片

注意:程序中没有运行状态。

9、线程池

线程池是一种多线程处理形式,它可以将任务添加到队列中,然后在创建线程后自动启动这些任务。线程池的优点有:降低资源消耗,提高响应速度,提高线程的可管理性。

线程池的核心逻辑是利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。线程池的核心逻辑包括以下几个方面:

  • 线程池的创建,通过Executors类提供的静态工厂方法或者ThreadPoolExecutor类的构造方法来创建不同类型的线程池。
  • 线程池的执行,通过execute或submit方法来向线程池提交任务,线程池会根据自身的状态和参数来决定是否接受任务、创建新的线程、放入任务队列或者拒绝任务。
  • 线程池的管理,通过一些内部变量和方法来维护线程池的运行状态、线程数量、任务队列、拒绝策略等,同时提供了一些外部接口来监控和控制线程池的行为。
  • 线程池的销毁,通过shutdown或shutdownNow方法来关闭线程池,释放资源,同时处理未完成的任务。

线程池主要核心原理:

①创建一个池子,池子是空的;

②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次再次提交任务时,不需要创建新的线程,直接复用已有的线程即可;

③当提交任务时,池子没有空闲的线程,也无法创建新的线程,此时需要排队等待。

**例子:**创建线程池,给线程池提交任务,所有任务完成,关闭线程。

package com.hippo.javamultithreasding.multithreading;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName ThreadDemo15
 * @Description TODO 线程池--使用工具类创建线程池
 * @Author tangxl
 * @create 2023-03-16 15:26
 **/
public class ThreadDemo15 {
    public static void main(String[] args) {
        // 创建一个没有上线的线程池
        //         ExecutorService pool = Executors.newCachedThreadPool();
        // 创建一个有上线的线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        // 创建10个任务
        for (int i = 0; i < 10; i++) {
            // 给线程池提交任务
            pool.submit(new Runnable() {
                 @Override
                 public void run() {
                     System.out.println(Thread.currentThread().getName());
                 }
            });
        }
        // 查看线程池中线程数量
        pool.shutdown();
        // 关闭线程池
        pool.shutdown();
    }
}
package com.hippo.javamultithreasding.multithreading;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ThreadDemo
 * @Description TODO 线程池--自定义线程池
 * @Author tangxl
 * @create 2023-03-16 15:47
 **/
public class ThreadDemo16 {
    public static void main(String[] args) {

        /**
         *  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
         *  (corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
         *  (1) corePoolSize:核心线程数,线程池中的常驻核心线程数,即使没有任务需要执行,也不会回收。
         *  (2) maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1。
         *  (3) keepAliveTime:多余的空闲线程的存活时间,当线程池中的线程数量超过corePoolSize时,如果某线程空闲时间达到keepAliveTime,
         *  则会终止,直到线程数量等于corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数量不超过corePoolSize时,
         *  该参数也会生效,直到线程数量为0。
         *  (4) unit:keepAliveTime的单位,有7种取值,在TimeUnit类中有7种静态属性:
         *  TimeUnit.DAYS;               //天
         *  TimeUnit.HOURS;             //小时
         *  TimeUnit.MINUTES;           //分钟
         *  TimeUnit.SECONDS;           //秒
         *  TimeUnit.MILLISECONDS;      //毫秒
         *  TimeUnit.MICROSECONDS;      //微妙
         *  TimeUnit.NANOSECONDS;       //纳秒
         *  (5) workQueue:任务队列,被提交但尚未被执行的任务。
         *  (6) threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般用默认的即可。
         *  (7) handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略。
         *  一般有以下4种策略:
         *  ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         *  ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
         *  ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
         *  ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
         *
         *   allowCoreThreadTimeOut(boolean):允许核心线程超时,默认为false,即核心线程不会超时。
         *   prestartAllCoreThreads():预启动所有核心线程。
         *   prestartCoreThread():预启动一个核心线程。
         *   remove(Runnable):从线程池中移除某个任务。
         *   purge():移除线程池中所有的任务。
         *   shutdown():关闭线程池,但是会等待线程池中的任务执行完毕。
         *   shutdownNow():关闭线程池,并且尝试停止正在执行的任务。
         *   execute(Runnable):执行任务。
         *   getActiveCount():获取线程池中活跃的线程数。
         *   getCompletedTaskCount():获取已完成的任务数量。
         *   getCorePoolSize():获取核心线程数。
         *   getKeepAliveTime(TimeUnit):获取线程池中线程的空闲时间。
         *   getLargestPoolSize():获取线程池中曾经创建过的最大线程数。
         *   getMaximumPoolSize():获取线程池中允许的最大线程数。
         *   getPoolSize():获取线程池中当前的线程数。
         *   getQueue():获取线程池中的任务队列。
         *   getTaskCount():获取线程池中的任务总数。
         *   isShutdown():判断线程池是否已经关闭。
         *   isTerminated():判断线程池是否已经终止。
         *   isTerminating():判断线程池是否正在终止。
         *   awaitTermination(long, TimeUnit):等待线程池终止。
         *   toString():返回线程池的字符串表示。
         *   getThreadFactory():获取线程工厂。
         *   setThreadFactory(ThreadFactory):设置线程工厂。
         *   getRejectedExecutionHandler():获取拒绝策略。
         *   getQueue():获取任务队列。
         *   setCorePoolSize(int):设置核心线程数。
         *   setMaximumPoolSize(int):设置线程池中允许的最大线程数。
         *   setKeepAliveTime(long, TimeUnit):设置线程池中线程的空闲时间。
         *   setRejectedExecutionHandler(RejectedExecutionHandler):设置拒绝策略。
         *   setThreadFactory(ThreadFactory):设置线程工厂。
         *   setQueueCapacity(int):设置任务队列的容量。
         *   setAllowCoreThreadTimeOut(boolean):设置是否允许核心线程超时。
         *   setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean):设置是否允许定时任务在关闭线程池后继续执行。
         *   setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean):设置是否允许延时任务在关闭线程池后继续执行。
         *   setRemoveOnCancelPolicy(boolean):设置是否允许取消任务在关闭线程池后继续执行。
         *   setThreadNamePrefix(String):设置线程名称的前缀。
         *   setWaitForTasksToCompleteOnShutdown(boolean):设置是否允许关闭线程池后等待任务执行完毕。
         *   setAwaitTerminationSeconds(int):设置关闭线程池后等待任务执行完毕的时间。
         *   setKeepAliveSeconds(int):设置线程池中线程的空闲时间。
         */

        // 自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        // 执行任务
        pool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });

        // 关闭线程池
        pool.shutdown();
    }

}

你可能感兴趣的:(java,java,jvm,开发语言)