【进阶篇-Day15:JAVA线程-Thread的介绍】

目录

  • 1、进程和线程
    • 1.1 进程的介绍
    • 1.2 并行和并发
    • 1.3 线程的介绍
  • 2、JAVA开启线程的三种方法
    • 2.1 继承Thread类:
    • 2.2 实现Runnable接口
    • 2.3 实现Callable接口
    • 2.4 总结:
  • 3、线程相关方法
    • 3.1 获取和设置线程名字的方法
    • 3.2 线程休眠方法:
    • 3.3 线程优先级方法:
    • 3.4 守护线程
  • 4、线程安全和同步
    • 4.1 案例
    • 4.2 安全和同步问题的解决:
      • 4.2.1 同步代码块:
      • 4.2.2 同步方法:
      • 4.2.3 Lock锁:
  • 5、线程通信
    • 5.1 两条线程的通信
    • 5.2 三条线程的通信
    • 5.3 等待唤醒机制
    • 5.4 生产者消费者模式
    • 5.5 总结
  • 6、线程生命周期
  • 7、线程池
    • 7.1 介绍:
    • 7.2 使用线程池:
      • 7.2.1 使用JDK提供的线程池:
      • 7.2.2 自定义线程池:
  • 8、单例设计模式

1、进程和线程

1.1 进程的介绍

在这里插入图片描述
在这里插入图片描述

1.2 并行和并发

在这里插入图片描述

1.3 线程的介绍

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、JAVA开启线程的三种方法

JAVA开启线程方法一共三种,如下所示:
在这里插入图片描述

2.1 继承Thread类:

package com.itheima.thread;
public class ThreadDemo1 {
    /**
     * 开启线程的第一种方式:继承Thread类
     *     1、编写一个类继承Thread
     *     2、重写run方法
     *     3、将线程任务代码写在run方法中
     *     4、创建线程对象
     *     5、调用start方法开启线程
     *
     *     细节:调用start方法开启线程,会自动的调用run方法执行
     *     注意:只有调用了start方法,才是开启了新的线程
     */
    public static void main(String[] args) {
        //4、创建线程对象
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        //5、调用start方法开启线程
        myThread1.start();
        myThread2.start();
    }
}

//1、编写一个类继承Thread
class MyThread extends Thread{
    //2、重写run方法
    @Override
    public void run() {
        //3、将线程任务代码写在run方法中
        for (int i = 1; i <= 200; i++) {
            System.out.println("线程执行了:" + i + "次");
        }
    }
}

注:Java程序默认是多线程的,程序启动后默认会存在两条线程。

  • 1、主线程;
  • 2、垃圾回收线程
package com.itheima.thread;
public class ThreadDemo2 {
    /**
     * Java程序默认是多线程的,程序启动后默认会存在两条线程
     *     1、主线程
     *     2、垃圾回收线程
     */
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 2000; i++) {
            System.out.println("主程序运行!");
        }
//        //制造垃圾
//        for (int i = 0; i < 5000000; i++) {
//            new Demo();
//        }
    }
}

class Demo{
    /**
     * 内存中不再使用的对象被称为垃圾,如果垃圾被回收了,则会调用finalize方法
     */
    @Override
    protected void finalize() throws Throwable {
        System.out.println("垃圾被清理了");
    }
}

2.2 实现Runnable接口

package com.itheima.thread;
public class ThreadDemo3 {
    /**
     * 开启线程的第二种方式:实现Runnable接口
     *     1、编写一个类实现Runnable接口
     *     2、重写run方法
     *     3、将线程任务代码写在run方法中
     *     4、创建线程任务资源
     *     5、创建线程对象,将资源传入
     *     6、使用线程对象调用start方法,开启线程
     *
     *     优势:如果一个类继承了其他父类,那就不能再继承Thread类了(java是单继承),但可以实现多个接口,因此开启线程可以实现Runnable接口
     */
    public static void main(String[] args) {
        //4、创建线程任务资源
        MyRunnable myRunnable = new MyRunnable();
        //5、创建线程对象,将资源传入
        Thread t = new Thread(myRunnable);
        //6、使用线程对象调用start方法,开启线程
        t.start();

        for (int i = 0; i < 10000; i++) {
            System.out.println("主线程执行了!");
        }
    }
}

//1、编写一个类实现Runnable接口
class MyRunnable implements Runnable{
    //2、重写run方法
    @Override
    public void run() {
        //3、将线程任务代码写在run方法中
        for (int i = 0; i < 200; i++) {
            System.out.println("线程任务执行了" + i);
        }
    }
}

上述方法可以使用匿名内部类的方式实现,如下所示:

package com.itheima.thread;
public class ThreadDemo {
    /**
     * 使用匿名内部类的方式开启线程:
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println("使用匿名内部类方式开启线程" + i);
                }
            }
        });
        t1.start();
    }
}

2.3 实现Callable接口

package com.itheima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadDemo4 {
    /**
     * 开启线程的第三种方式:实现Callable接口
     *     1、编写一个类实现Callable接口
     *     2、重写call方法
     *     3、将线程任务代码写在call方法中
     *     4、创建线程任务资源对象
     *     5、创建线程任务对象,封装线程资源
     *     6、创建线程对象,传入线程任务
     *     7、使用线程对象调用start开启线程
     *     
     *     优势:可以获取返回值
     */
    public static void main(String[] args) throws Exception {
        //4、创建线程任务资源对象
        MyCallable mc = new MyCallable();
        //5、创建线程任务对象,封装线程资源
        FutureTask<Integer> task1 = new FutureTask<>(mc);//开启第一个线程
        FutureTask<Integer> task2 = new FutureTask<>(mc);//开启第二个线程
        //6、创建线程对象,传入线程任务
        Thread th1 = new Thread(task1);
        Thread th2 = new Thread(task2);
        //7、使用线程对象调用start开启线程
        th1.start();
        th2.start();
        Integer result1 = task1.get();
        Integer result2 = task2.get();
        System.out.println("task1获取到的结果为:" + result1);
        System.out.println("task2获取到的结果为:" + result2);
    }
}

//1、编写一个类实现Callable接口
class MyCallable implements Callable<Integer>{
    //2、重写call方法
    @Override
    public Integer call() throws Exception {
        //3、将线程任务代码写在call方法中
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
            System.out.println("sum=" + sum);
        }
        return sum;
    }
}

这第三种方式也可以使用匿名内部类的方式实现:

package com.itheima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadDemo7 {
    /**
     * Callable开启线程,也可以使用匿名内部类的方式实现:
     */
    public static void main(String[] args) {
        Thread t = new Thread(new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 200; i++) {
                    sum += i;
                    System.out.println("使用匿名内部类方式开启线程" + i);
                }
                return sum;
            }
        }));
        t.start();
    }
}

2.4 总结:

上述三种方法,最推荐第二种,尤其使用匿名内部类的方式很方便。如果需要返回值的话,那就只能选择第三种方法了。

3、线程相关方法

3.1 获取和设置线程名字的方法

在这里插入图片描述

package com.itheima.thread;
public class ThreadDemo5 {
    /**
     * 线程设置名字和获取名字
     *
     * Thread类的方法:
     *     1、public String getName():获取线程名字
     *     2、public void setName():设置线程名字
     *             --注意:也可以创建对象时直接用构造方法设置线程名字(需要写带参构造方法)
     *     3、public Static Thread currentThread():获取当前线程的对象
     */
    public static void main(String[] args) {
        TestThread t1 = new TestThread("A:");//也可以创建对象时直接用构造方法设置线程名字
        TestThread t2 = new TestThread("B:");
        //2、public void setName():设置线程名字
//        t1.setName("线程一:");
//        t2.setName("线程二:");
        t1.start();
        t2.start();
    }
}

class TestThread extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 2000; i++) {
            //1、public String getName():获取线程名字
            System.out.println(super.getName() + "线程执行了" + i);
            //3、public Static Thread currentThread():获取当前线程的对象
            System.out.println(Thread.currentThread().getName() + "是通过currentThread方法获取的" + i);
        }
    }

    public TestThread(String name) {
        super(name);
    }

    public TestThread() {
    }
}

3.2 线程休眠方法:

在这里插入图片描述

package com.itheima.thread;
public class ThreadDemo6 {
    /**
     * 休眠线程的方法
     *     public static void sleep(long time):让线程休眠指定的时间,单位为毫秒
     */
    public static void main(String[] args) throws InterruptedException {
        for (int i = 5; i >= 0; i--) {
            System.out.println("倒计时" + i + "秒");
            Thread.sleep(1000);//休眠一秒
        }
    }
}

3.3 线程优先级方法:

为什么会有优先级的说法,先看下线程的调度方式:
在这里插入图片描述
简单来说:
(1)抢占式调度就是哪个线程抢到就执行哪个线程—随机;
(2)非抢占式调度则是轮流让每个线程执行—轮流使用;
Java使用的是抢占式调度,我们使用线程优先级方法,可以提高抢占的概率。下面来看具体的方法:

在这里插入图片描述

package com.itheima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadDemo7 {
    /**
     * 线程优先级的方法:
     *     public setPriority(int newPriority):设置线程优先级,1~10之间,1最高、10最低
     *     public final int getPriority():获取线程优先级,不设置默认为5
     */
    public static void main(String[] args) {
        //1、使用匿名内部类的方式定义两个线程
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println(Thread.currentThread().getName() + "---" + i);
                }
            }
        }, "线程A:");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println(Thread.currentThread().getName() + "---" + i);
                }
            }
        }, "线程B:");

        //2、setPriority(int newPriority):设置线程优先级
        t1.setPriority(1);//优先级设置为1
        t2.setPriority(10);//优先级设置为10

        //3、getPriority():获取线程优先级
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());

        //4、开启线程,优先级越高越容易先执行
        t1.start();
        t2.start();
    }
}

3.4 守护线程

在Java中,线程有两种类型:用户线程守护线程守护线程是为了支持用户线程而存在的,他们通常用于执行一些后台任务,如垃圾回收、监控等。与用户线程不同,守护线程在没有其他用户线程运行时会自动终止。
在这里插入图片描述

在这里插入图片描述

package com.itheima.thread;
public class ThreadDemo8 {
    /**
     * 守护线程
     *     public final void setDaemon(boolean on):设置为守护线程
     *
     * 注意:守护线程会随着所有非守护线程的结束而结束,所以如果有两条非守护线程,如果其中一条线程结束了,但守护线程不会结束。
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 20; i++) {//线程A运行20次
                    System.out.println(Thread.currentThread().getName() + "运行了" + i);
                }
            }
        }, "线程A:");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 200; i++) {//线程B运行200次
                    System.out.println(Thread.currentThread().getName() + "运行了" + i);
                }
            }
        }, "线程B:");

        //将线程B设置为守护线程
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}

4、线程安全和同步

4.1 案例

线程安全和同步问题,我们使用案例来介绍:

在这里插入图片描述
先按照之前学习的知识,来编写代码,如下所示:

package com.itheima.thread.lock;
public class ThreadTest1 {
    /**
     * 需求:某电影院目前正在上映国产大片,共有100张票,而他有3个窗口卖票,请设计一个程序模拟该电影院卖票
     *      -- 多条线程共享同一份资源,就会出现线程不安全问题(如本案例中,三个线程会卖同一张票,这是有问题的)
     */
    public static void main(String[] args) {
        TicketsTask ticketsTask = new TicketsTask();
        Thread t1 = new Thread(ticketsTask, "线程1:");
        Thread t2 = new Thread(ticketsTask, "线程2:");
        Thread t3 = new Thread(ticketsTask, "线程3:");

        t1.start();
        t2.start();
        t3.start();
    }
}

class TicketsTask implements Runnable{
    int ticket = 100;

    @Override
    public void run() {
        while (ticket != 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "号票");
            ticket--;
        }
    }
}

上述代码是不安全的,(1)票号有可能变成负数,(2)三个线程会卖同一张票,如下所示的结果:
在这里插入图片描述
在这里插入图片描述

(1)针对上面变成负数做,如下解释:

在这里插入图片描述

(2)针对上面三个线程会卖同一张票,如下解释:
在这里插入图片描述

上述两个问题,就是因为代码在运行过程中,cpu在来回切换运行这三个线程导致的,怎么解决呢?
答:当这一段逻辑在某一个线程运行时,先给这段代码上锁,确保运行完后,其他线程再来运行即可解决。
在这里插入图片描述
那如何上锁呢,接着看:

4.2 安全和同步问题的解决:

在这里插入图片描述

有三种方法上锁,我们一个一个看:

4.2.1 同步代码块:

在这里插入图片描述

package com.itheima.thread.lock;
public class ThreadTest1 {
    /**
     * 需求:某电影院目前正在上映国产大片,共有100张票,而他有3个窗口卖票,请设计一个程序模拟该电影院卖票
     *      -- 多条线程共享同一份资源,就会出现线程不安全问题(如本案例中,三个线程会卖同一张票,这是有问题的)
     */
    public static void main(String[] args) {
        TicketsTask ticketsTask = new TicketsTask();
        Thread t1 = new Thread(ticketsTask, "线程1:");
        Thread t2 = new Thread(ticketsTask, "线程2:");
        Thread t3 = new Thread(ticketsTask, "线程3:");

        t1.start();
        t2.start();
        t3.start();
    }
}

class TicketsTask implements Runnable{
    private final Object o = new Object();

    int ticket = 100000;

    @Override
    public void run() {
        //同步代码块:把需要上锁的代码放在里面
        //虽然锁对象可以是任意对象,但是要注意:锁对象需要唯一,即静态化。既然这样,可以使用字节码文件TicketsTask.class作为锁对象,好处:唯一且方便
        synchronized (TicketsTask.class){
            while (ticket != 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "号票");
                ticket--;
            }
        }
    }
}

4.2.2 同步方法:

在这里插入图片描述

package com.itheima.thread.lock;

public class ThreadTest2 {
    /**
     * 同步方法:在方法的返回值类型前面加入 synchronized 关键字
     *
     * public synchronized void method(){ }
     *
     * 同步方法的锁对象:
     *     1、非静态的方法:this
     *     2、静态的方法:类的字节码对象
     */
    public static void main(String[] args) {
        TicketsTask2 ticketsTask = new TicketsTask2();
        Thread t1 = new Thread(ticketsTask, "线程1:");
        Thread t2 = new Thread(ticketsTask, "线程2:");
        Thread t3 = new Thread(ticketsTask, "线程3:");

        t1.start();
        t2.start();
        t3.start();
    }
}

class TicketsTask2 implements Runnable{
    private int ticket = 1000;

    @Override
    public void run() {
        while (true) {
            if (method()) break;
        }
    }

    /*
     * 同步方法:在方法的返回值类型前面加入 synchronized 关键字
     */
    private synchronized boolean method() {
        if (ticket == 0){
            return true;
        }
        System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "号票");
        ticket--;
        return false;
    }
}

4.2.3 Lock锁:

在这里插入图片描述

在这里插入图片描述

package com.itheima.thread.lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTask3 {
    /**
     * 互斥锁 ReentrantLock()
     * @param args
     */
    public static void main(String[] args) {
        TicketsTask3 ticketsTas = new TicketsTask3();
        Thread thread1 = new Thread(ticketsTas, "线程1");
        Thread thread2 = new Thread(ticketsTas, "线程2");
        Thread thread3 = new Thread(ticketsTas, "线程3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class TicketsTask3 implements Runnable{
    private int tickets = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                //上锁,以下代码会被锁住
                lock.lock();
                if (tickets == 0){
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "卖出了" + tickets + "张票");
                tickets--;
            } finally {
                //解锁,放在finally是因为无论如何都会执行到
                lock.unlock();
            }
        }
    }
}

在这里插入图片描述

在这里插入图片描述

5、线程通信

在这里插入图片描述

在这里插入图片描述

5.1 两条线程的通信

我们先看下如果两条线程没有通信会出现什么情况,如下所示的代码,就是谁抢到,谁就执行:

package com.itheima.correspondence;

public class CorrespondenceDemo1 {
    /**
     * 两条线程通信
     *
     */
    public static void main(String[] args) {
        Prints p = new Prints();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Prints.class){//同步代码块:在这里面的方法会执行完,才会切换CPU到其他线程
                        p.print1();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Prints.class){
                        p.print2();
                    }
                }
            }
        }).start();
    }
}

class Prints{
    public void print1(){
        System.out.print("传");
        System.out.print("智");
        System.out.print("教");
        System.out.print("育");
        System.out.println();
    }

    public void print2(){
        System.out.print("黑");
        System.out.print("马");
        System.out.print("程");
        System.out.print("序");
        System.out.print("员");
        System.out.println();
    }
}

上述代码运行结果如下,发现这两个线程是没有顺序的,因此
在这里插入图片描述

如何解决上述两个线程随机运行的问题呢,也就是说这两个线程,如何按我们想要的特定顺序执行呢,我们接着看:
在这里插入图片描述

package com.itheima.correspondence;

public class CorrespondenceDemo1 {
    /**
     * 两条线程通信
     *
     */
    public static void main(String[] args) {
        Prints p = new Prints();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Prints.class){//同步代码块:在这里面的方法会执行完,才会切换CPU到其他线程
                        try {
                            p.print1();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Prints.class){
                        try {
                            p.print2();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();
    }
}

class Prints{
    //标记为1线程1执行,标记为2线程2执行
    int flag = 1;

    public void print1() throws InterruptedException {
        if (flag != 1){
            //线程1等待
            Prints.class.wait();//锁对象调用等待方法
        }
        System.out.print("传");
        System.out.print("智");
        System.out.print("教");
        System.out.print("育");
        System.out.println();
        flag = 2;
        //唤醒线程2
        Prints.class.notify();//锁对象调用唤醒方法。注意这个方法会随机唤醒一个线程,也就是下次有可能还会唤醒线程1
    }

    public void print2() throws InterruptedException {
        if (flag != 2){
            //线程2等待
            Prints.class.wait();//锁对象调用等待方法
        }
        System.out.print("黑");
        System.out.print("马");
        System.out.print("程");
        System.out.print("序");
        System.out.print("员");
        System.out.println();
        flag = 1;
        //唤醒线程1
        Prints.class.notify();//锁对象调用唤醒方法。注意这个方法会随机唤醒一个线程,也就是下次有可能还会唤醒线程2
    }
}

5.2 三条线程的通信

上述是两条线程的通信,那三条线程怎么通信呢,我们接着看:

假设我们按上述处理两条线程那样,处理三条线程的通信问题,如下代码:

package com.itheima.correspondence;
public class CorrespondenceDemo2 {
    /**
     * 三条线程通信
     */
    public static void main(String[] args) {
        Print2 p = new Print2();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Print2.class){
                        try {
                            p.print1();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Print2.class){
                        try {
                            p.print2();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Print2.class){
                        try {
                            p.print3();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();
    }
}

class Print2{
    int flag = 1;

    public void print1() throws InterruptedException {
        if (flag != 1){
            Print2.class.wait();//线程1等待
        }

        System.out.print("传");
        System.out.print("智");
        System.out.print("教");
        System.out.print("育");
        System.out.println();

        flag = 2;//让线程2执行
        //随机唤醒一条线程
        Print2.class.notify();
    }

    public void print2() throws InterruptedException {
        if (flag != 2){
            Print2.class.wait();//线程2等待
        }

        System.out.print("黑");
        System.out.print("马");
        System.out.print("程");
        System.out.print("序");
        System.out.print("员");
        System.out.println();

        flag = 3;//让线程3执行
        //随机唤醒一条线程
        Print2.class.notify();
    }

    public void print3() throws InterruptedException {
        if (flag != 3){
            Print2.class.wait();//线程3等待
        }

        System.out.print("传");
        System.out.print("智");
        System.out.print("大");
        System.out.print("学");
        System.out.println();

        flag = 1;//让线程1执行
        //随机唤醒一条线程
        Print2.class.notify();
    }
}

运行结果:
在这里插入图片描述

发现并没有按照1,2,3的顺序执行,我们分析下原因:
(1)假设在程序开始运行时,线程2抢到了CPU执行权,由于flag=1,因此线程2进入等待状态;
(2)然后CPU释放出资源,这时线程1线程3会抢占执行权,假设线程3抢到了CPU的执行权,由于flag=1,因此线程3进入等待状态;
(3)由于此时线程2线程3都进入了等待状态,因此线程1会抢占到CPU执行权。由于flag=1,因此线程1会执行完(设置flag=2,并随机唤醒一条线程);
(4)假设此时随机唤醒了线程3,那么线程3就会从等待状态的地方继续向下运行,此时我们发现线程3运行完,并设置flag=1,如果此时线程1抢到执行权,那么就会运行线程1
(5)代码运行到此时,我们发现线程运行顺序如下:线程1——》线程3——》线程1…

原因就是notify()这个方法是随机唤醒一个线程。我们换如下所示的唤醒线程的方法:
在这里插入图片描述

package com.itheima.correspondence;
public class CorrespondenceDemo2 {
    /**
     * 三条线程通信
     */
    public static void main(String[] args) {
        Print2 p = new Print2();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Print2.class){
                        try {
                            p.print1();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Print2.class){
                        try {
                            p.print2();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (Print2.class){
                        try {
                            p.print3();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();
    }
}

class Print2{
    int flag = 1;

    public void print1() throws InterruptedException {
        while (flag != 1){//把if换成while,是为了当线程唤醒时,再重新判断一次条件
            Print2.class.wait();//线程1等待
        }

        System.out.print("传");
        System.out.print("智");
        System.out.print("教");
        System.out.print("育");
        System.out.println();

        flag = 2;//让线程2执行
        //随机唤醒一条线程
        Print2.class.notifyAll();
    }

    public void print2() throws InterruptedException {
        while (flag != 2){//把if换成while,是为了当线程唤醒时,再重新判断一次条件
            Print2.class.wait();//线程2等待
        }

        System.out.print("黑");
        System.out.print("马");
        System.out.print("程");
        System.out.print("序");
        System.out.print("员");
        System.out.println();

        flag = 3;//让线程3执行
        //随机唤醒一条线程
        Print2.class.notifyAll();
    }

    public void print3() throws InterruptedException {
        while (flag != 3){//把if换成while,是为了当线程唤醒时,再重新判断一次条件
            Print2.class.wait();//线程3等待
        }

        System.out.print("传");
        System.out.print("智");
        System.out.print("大");
        System.out.print("学");
        System.out.println();

        flag = 1;//让线程1执行
        //随机唤醒一条线程
        Print2.class.notifyAll();
    }
}

运行结果:
在这里插入图片描述
发现是按照我们想要的顺序执行的。

注意:
(1)但是每次都是把所有的线程唤醒,运行效率很低。。。
(2)同步代码块技术不是说不会切换到其他线程么,那为啥wait方法运行后,就会切换呢,答:
在这里插入图片描述

5.3 等待唤醒机制

上面虽然可以解决线程通信问题,但是效率太低,本节就解决效率低的问题。
在这里插入图片描述

直接上代码,看注释即可:

package com.itheima.correspondence;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class CorrespondenceDemo3 {
    /**
     * 三条线程通信 --优化
     */
    public static void main(String[] args) {
        Print3 p = new Print3();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        p.print3();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();
    }
}

class Print3{
    ReentrantLock lock = new ReentrantLock();//锁对象

    //获取锁状态
    Condition c1 = lock.newCondition();
    Condition c2 = lock.newCondition();
    Condition c3 = lock.newCondition();

    int flag = 1;

    public void print1() throws InterruptedException {
        lock.lock();//上锁

        if (flag != 1){
            //注意:第一次调用await()方法时,c1就会和线程1绑定
            c1.await();//状态不为1,让线程1等待,并让c1绑定线程1
        }

        System.out.print("传");
        System.out.print("智");
        System.out.print("教");
        System.out.print("育");
        System.out.println();

        flag = 2;//唤醒线程2
        c2.signal();//注意:这里是指定线程2被唤醒,如果没有被绑定的话,是随机唤醒一个线程

        lock.unlock();//释放锁
    }

    public void print2() throws InterruptedException {
        lock.lock();//上锁

        if (flag != 2){
            c2.await();//状态不为2,让线程2等待,并让c2绑定线程2
        }

        System.out.print("黑");
        System.out.print("马");
        System.out.print("程");
        System.out.print("序");
        System.out.print("员");
        System.out.println();

        //唤醒线程3
        flag = 3;
        c3.signal();

        lock.unlock();
    }

    public void print3() throws InterruptedException {
        lock.lock();

        if (flag != 3){
            c3.await();//状态不为3,让线程3等待,并让c3绑定线程3
        }

        System.out.print("传");
        System.out.print("智");
        System.out.print("大");
        System.out.print("学");
        System.out.println();

        //唤醒线程1
        flag = 1;
        c1.signal();

        lock.unlock();
    }
}

5.4 生产者消费者模式

在这里插入图片描述

在这里插入图片描述

下面看代码:

这个是共享数据区的代码:

package com.itheima.correspondence.producer_consumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 共享数据区:注意,公共并静态,这样就可以使用类名直接调用了
 */
public class WareHouse {
    public static boolean mark = false;

    public static ReentrantLock lock = new ReentrantLock();

    //
    public static Condition producer = lock.newCondition();
    public static Condition consumer = lock.newCondition();
}

这个是生产者代码:

package com.itheima.correspondence.producer_consumer;
public class Producer implements Runnable{

    @Override
    public void run() {
        while (true){
            WareHouse.lock.lock();//上锁
            if (WareHouse.mark){
                //true:说明有包子,线程进入等待状态
                try {
                    WareHouse.producer.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }else {
                //false:没有包子,生产包子,生产后改变mark状态,唤醒消费者线程
                System.out.println("生产者线程生产包子...");

                WareHouse.mark = true;
                WareHouse.consumer.signal();//生产完包子,唤醒消费者线程
            }
            WareHouse.lock.unlock();//释放锁
        }
    }
}

这个是消费者代码:

package com.itheima.correspondence.producer_consumer;
public class Consumer implements Runnable{

    @Override
    public void run() {
        while (true){
            WareHouse.lock.lock();
            if (WareHouse.mark){
                //true:说明有包子,开吃包子,吃完后改变mark状态,唤醒生产者线程
                System.out.println("消费者线程吃包子...");

                WareHouse.mark = false;
                WareHouse.producer.signal();//吃完包子,唤醒生产者线程
            }else {
                //false:说明没有包子,线程进入等待状态
                try {
                    WareHouse.consumer.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            WareHouse.lock.unlock();
        }
    }
}

测试代码:

package com.itheima.correspondence.producer_consumer;

public class Test {
    public static void main(String[] args) {
        new Thread(new Producer()).start();
        new Thread(new Consumer()).start();
    }
}

运行结果:
在这里插入图片描述

5.5 总结

在这里插入图片描述

6、线程生命周期

在这里插入图片描述

下面看看一个线程的生命周期流程图:

在这里插入图片描述

Java中的线程状态:
在这里插入图片描述

7、线程池

7.1 介绍:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.2 使用线程池:

7.2.1 使用JDK提供的线程池:

在这里插入图片描述

package com.itheima.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {
    /**
     * JDK自带线程池
     *     Executors 中提供静态方法来创建线程池
     *       static ExecutorService newCachedThreadPool()  创建一个默认的线程池
     *       static newFixedThreadPool(int nThreads)       创建一个指定最多线程数量的线程池
     * @param args
     */
    public static void main(String[] args) {
        //1、获取线程池
        //ExecutorService pool = Executors.newCachedThreadPool();//此方法最多可以创建 2147483647(21亿)线程对象
        ExecutorService pool = Executors.newFixedThreadPool(10);//此方法可以指定最多创建的线程对象

        //2、提交线程任务
        for (int i = 1; i < 100; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "提交了线程任务");
                }
            });
        }

        //3、关闭池
        pool.shutdown();
    }
}

7.2.2 自定义线程池:

在这里插入图片描述
在这里插入图片描述

package com.itheima.pool;
import java.util.concurrent.*;
public class ThreadPoolDemo2 {
    /**
     * 自定义线程池对象
     *   参数5:任务队列
     *         1)有界队列  new ArrayBlockingQueue<>(10)
     *         2)无界队列  new LinkedBlockingQueue<>()。注:最多21亿个任务数
     */
    public static void main(String[] args) {
        //1、获取线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2, //核心线程数量
                5,  //最大线程数量
                60,  //空闲时间
                TimeUnit.SECONDS,//空闲单位
                new ArrayBlockingQueue<>(10),//任务队列,即指定排队人数
                Executors.defaultThreadFactory(),//线程对象任务工厂
                new ThreadPoolExecutor.AbortPolicy()//拒绝策略
        );

        //2、提交线程池
        for (int i = 0; i < 13; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "提交了线程任务");
                }
            });
        }

        //3、关闭池
        pool.shutdown();
    }
}

在这里插入图片描述

在这里插入图片描述

8、单例设计模式

在这里插入图片描述
在这里插入图片描述

package com.itheima.single_design;
public class SingleDesignDemo2 {
    /**
     * 单例设计模式 -懒汉式(延迟加载模式)
     * ------------------------------------------------
     * class Single2{
     *     private Single2(){ //私有化构造方法,防止new对象
     *     }
     *
     *     private static Single2 s;
     *
     *     public static Single2 getInstance(){
     *         if (s == null) s = new Single2();//等调用对象时才new
     *         return s;
     *     }
     * }
     * 上述写法的弊端:在多线程并发操作的时候,有可能创建出多个对象
     * -------------------------------------------------
     * class Single2{
     *     private Single2(){ //私有化构造方法,防止new对象
     *     }
     *
     *     private static Single2 s;
     *
     *     public static Single2 getInstance(){
     *         synchronized (Single2.class){//给这段代码上锁,这样避免了多线程的操作
     *             if (s == null) s = new Single2();//等调用对象时才new
     *             return s;
     *         }
     *     }
     * }
     * 述写法的弊端:效率非常低,因为线程会多次被阻塞(线程阻塞通常是指一个线程在执行过程中暂停,以等待某个条件的触发)。
     * 下述是正确的写法
     */
    public static void main(String[] args) {
        Single2 s1 = Single2.getInstance();
        Single2 s2 = Single2.getInstance();

        System.out.println(Single1.getInstance());

        System.out.println(s1 == s2);//true:表明指向同一个地址
    }
}

class Single2{
    private Single2(){ //私有化构造方法,防止new对象
    }

    private static Single2 s;

    public static Single2 getInstance(){
        if (s == null){//这里加if,是为了减小线程被多次阻塞的次数,从而提高效率
            synchronized (Single2.class){//给这段代码上锁,这样避免了多线程的操作
                if (s == null) {
                    s = new Single2();//等调用对象时才new
                }
            }
        }
        return s;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot(20)(删除文章分类。获取、更新、删除文章详细)(Validation分组校验)

目录 一、删除文章分类功能。 &#xff08;1&#xff09;接口文档。 1、请求路径、请求参数。 2、请求参数。 3、响应数据。 &#xff08;2&#xff09;实现思路与代码书写。 1、controller层。 2、service接口业务层。 3、serviceImpl实现类。 4、mapper层。 5、后端接口测试。…

如何搭建JMeter分布式集群环境来进行性能测试

在性能测试中&#xff0c;当面对海量用户请求的压力测试时&#xff0c;单机模式的JMeter往往力不从心。如何通过分布式集群环境&#xff0c;充分发挥JMeter的性能测试能力&#xff1f;这正是许多测试工程师在面临高并发、海量数据时最关注的问题。那么&#xff0c;如何轻松搭建…

Y20030025基于php+mysql的幼儿健康管理系统设计与实现 源代码 配置 文档

幼儿健康管理系统的设计与实现 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 在信息化时代的浪潮中&#xff0c;幼儿健康管理面临着前所未有的挑战与机遇。为了更好地满足家长和幼儿园对幼儿健康管理的需求&#xff0c;我们致力于开发一套基于PHP的幼…

时频转换 | Matlab基于垂直二阶同步压缩变换vertical second-order synchrosqueezing一维数据转二维图像方法

目录 基本介绍程序设计参考资料获取方式基本介绍 时频转换 | Matlab基于垂直二阶同步压缩变换vertical second-order synchrosqueezing一维数据转二维图像方法 程序设计 clear clc % close all load x.mat % 导入数据 x

1.1 数据结构的基本概念

1.1.1 基本概念和术语 一、数据、数据对象、数据元素和数据项的概念和关系 数据&#xff1a;是客观事物的符号表示&#xff0c;是所有能输入到计算机中并被计算机程序处理的符号的总称。 数据是计算机程序加工的原料。 数据对象&#xff1a;是具有相同性质的数据元素的集合&…

SpringBoot小知识(2):日志

日志是开发项目中非常重要的一个环节&#xff0c;它是程序员在检查程序运行的手段之一。 1.日志的基础操作 1.1 日志的作用 编程期调试代码运营期记录信息&#xff1a; * 记录日常运营重要信息(峰值流量、平均响应时长……) * 记录应用报错信息(错误堆栈) * 记录运维过程数据(…

传输控制协议(TCP)

传输控制协议是Internet一个重要的传输层协议。TCP提供面向连接、可靠、有序、字节流传输服务。 1、TCP报文段结构 注&#xff1a;TCP默认采用累积确认机制。 2、三次握手、四次挥手 &#xff08;1&#xff09;当客户向服务器发送完最后一个数据段后&#xff0c;发送一个FIN段…

输出保留3位小数的浮点数

输出保留3位小数的浮点数 C语言代码C代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 读入一个单精度浮点数&#xff0c;保留3位小数输出这个浮点数。 输入 只有一行&#xff0c;一个单精度浮点数。 输出 也只有一…

安装 RabbitMQ 服务

安装 RabbitMQ 服务 一. RabbitMQ 需要依赖 Erlang/OTP 环境 (1) 先去 RabbitMQ 官网&#xff0c;查看 RabbitMQ 需要的 Erlang 支持&#xff1a;https://www.rabbitmq.com/ 进入官网&#xff0c;在 Docs -> Install and Upgrade -> Erlang Version Requirements (2) …

【竞技宝】CS2-上海major:MongoLZ成为亚洲之光

北京时间2024年12月1日,上海major在昨日正式拉开比赛序幕,首日第六轮迎来MongolZ对阵MIBR、COL对阵PUA。以下是本轮比赛的详细战报。 MongoLz 13-6 MIBR(比赛地图:远古遗迹) 上半场,MongoLz先做进攻方。手枪局,MongoLz抱团进攻遭遇MIBR重防被接连秒掉三人,然而在5V2的残局中,M…

【绘图】数据可视化(python)

对于数据绝对值差异较大&#xff08;数据离散&#xff09; 1. 对数坐标直方图&#xff08;Histogram with Log Scale&#xff09; import pandas as pd import matplotlib.pyplot as plt import numpy as np# 示例数据 data {count: [10, 20, 55, 90, 15, 5, 45, 80, 1000, …

MySQL - Why Do We Need a Thread Pool? - mysql8.0

MySQL - Why Do We Need a Thread Pool? - mysql8.0 本文主要由于上次写的感觉又长又臭&#xff0c; 感觉学习方法有问题&#xff0c; 我们这次直接找来了 thread pool 的原文&#xff0c;一起来看看官方的开发者给出的blog – 感觉是个大神 但是好像不是最官方的 &#xff0c…

【JS】栈内存、堆内存、事件机制区别

js中&#xff0c;内存主要分为两种类型&#xff1a;栈内存&#xff08;stack&#xff09;、堆内存&#xff08;heap&#xff09;&#xff0c;两种内存区域在存储和管理数据时有各自的特点和用途。 栈内存 访问顺序 栈是先进后出、后进先出的数据结构&#xff0c;栈内存是内存用…

glog在vs2022 hello world中使用

准备工作 设置dns为阿里云dns 223.5.5.5&#xff0c;下载cmake&#xff0c;vs2022&#xff0c;git git clone https://github.com/google/glog.git cd glog mkdir build cd build cmake .. 拷贝文件 新建hello world并设置 设置预处理器增加GLOG_USE_GLOG_EXPORT;GLOG_NO_AB…

20241127 给typecho文章编辑附件 添加视频 图片预览

Typecho在写文章时&#xff0c;如果一次性上传太多张图片可能分不清哪张&#xff0c;因为附件没有略缩图&#xff0c;无法实时阅览图片&#xff0c;给文章插入图片时很不方便。 编辑admin/file-upload.php 大约十八行的位置 一个while 循环里面,这是在进行html元素更新操作,在合…

重生之我在异世界学编程之C语言:二维数组篇

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一 二维数组的创建1. 二维数组的…

Tree搜索二叉树、map和set_数据结构

数据结构专栏 如烟花般绚烂却又稍纵即逝的个人主页 本章讲述数据结构中搜索二叉树与HashMap的学习&#xff0c;感谢大家的支持&#xff01;欢迎大家踊跃评论&#xff0c;感谢大佬们的支持! 目录 搜索二叉树的概念二叉树搜索模拟实现搜索二叉树查找搜索二叉树插入搜索二叉树删除…

分离整数的各个数

分离整数的各个数 C语言代码C 语言代码Java语言代码Python语言代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 给定一个整数&#xff0c;要求从个位开始分离出它的每一位数字。 输入 输入一个整数&#xff0c;整数在1到100000000之间…

OpenAI Whisper 语音识别 模型部署及接口封装

环境配置: 一、安装依赖&#xff1a; pip install -U openai-whisper 或者&#xff0c;以下命令会从这个存储库拉取并安装最新的提交&#xff0c;以及其Python依赖项&#xff1a; pip install githttps://github.com/openai/whisper.git 二、安装ffmpeg&#xff1a; cd …

PotPlayer 最新版本支持使用 Whisper 自动识别语音生成字幕

PotPlayer 最新版本支持使用 Whisper 自动识别语音生成字幕 设置使用下载地址 设置 使用 下载地址 https://www.videohelp.com/software/PotPlayer