Java多线程

学习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、线程的生命周期及五种基本状态

在这里插入图片描述

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

  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、线程的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6QxFQrK9-1679015183039)(D:\Tools\学习资料\照片\文档图片\image-20230314164301198.png)]

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

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();
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hippoDocker

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值