Java 多线程 thread

1、并发与并行

1.1 并发(交替)

两个或多个时间在同一个时间段内发生
在这里插入图片描述

1.2 并行(同时)

两个或多个时间在同一时刻发生(同时发生)

在这里插入图片描述

2、进程与线程

2.1 进程

进程: 进入到内存的程序

特点:

  1. 每个进程都有一个独立内存空间
  2. 一个应用程序可以同时运行多个进行
  3. 进程也是程序的一次执行过程,是系统运行的基本单位
  4. 系统运行一个程序即是一个进程从创建、运行、消亡的过程
    在这里插入图片描述
    在这里插入图片描述

2.2 线程

线程:是进程中的一个执行单元
特点:

  1. 一个程序运行后至少有一个进程
  2. 一个进程中可以包含多个线程

在这里插入图片描述

2.3 线程调度

  • 单核CPU执行方式为:在多个线程中高速切换,轮流执行多个线程,切换速度为(1/n毫秒)效率低
  • 多核CPU执行方式为:如4核心8线程,8个线程在多个任务之间坐高速切换,速度是单线程CPU的8倍(每个任务执行的几率都被提高了8倍)
=>分时调度

所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间

=>抢占式调度 (java采用的方式)

优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度

2.4 守护线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:

关于守护线程的文章可以参考这篇
Java中的守护线程:https://www.cnblogs.com/qq1290511257/p/10645106.html

3、main(主)线程

当Java虚拟机启动时,通常有一个非守护进程线程(通常调用某些指定类的名为main的方法)
Java虚拟机将继续执行线程,直到发生以下任一情况:

  • 已经调用了Runtime类的exit方法,并且安全管理器已经允许进行退出操作
  • 所有不是守护进程线程的线程都已经死亡,无论是从调用返回到run方法还是抛出超出run方法的run

3.1 main(主)线程

Java 使用 java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例

创建Person类

public class Person {
    private String name;
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println(name+"==>"+i);
        }
    }

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

创建main线程类

/*
*   主线程:执行主(main)方法的线程
*   单线程程序:java程序中只有一个线程
*   执行从main方法开始,从上到下依次执行
*
*   ==================  main(主)线程   ==================
*   JVM执行main方法,main方法会进入到栈内存
*   JVM会找操作系统开辟一条main方法通向cpu的执行路径
*   cpu就会通过这个路径来执行main方法
*   这个路径有一个名字:main(主)线程
*
* */

public class ThreadDemo1 {

    public static void main(String[] args) {
        Person zhangsan = new Person("zhangsan");
        zhangsan.run();
        Person lisi = new Person("lisi");
        lisi.run();
    }
}

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

3.2 主线程异常

Exception in thread "main"
在这里插入图片描述

线程创建的二个方式:

4、创建多线程方式一:Thread 类

java.lang.Thread
线程是程序中执行的线程。 Java虚拟机允许应用程序同时执行多个执行线程。

4.1 Thread 类实现多线程

① 创建一个Thread类的子类
// 方式一:继承 Thread 类 
public class ThreadDemo1 extends Thread{}
② 重写 run() 方法 ☆

在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做的事情)

这里是线程的核心操作方法,一切线程的操作都放在这里

// run方法线程主体
public void run() {
    for (int i = 0; i < 200; i++) {
        System.out.println("子线程执行体--------"+i);
    }
}
③ 创建Thread类的子类对象
// 创建线程对象
Thread thread = new ThreadDemo1();
④ 调用Thread类中的方法start方法(开启新线程,间接执行run方法)

在这里插入图片描述

//调用 start()方法开启线程
// 执行 start方法 会自动调用 run方法
thread.start();

调用2次start() 直接报错 Exception in thread "main" java.lang.IllegalThreadStateException
在这里插入图片描述

⑤ 完整代码与解读

下面是简单版完整代码

package com.ctra.demo1;

// 创建线程方式一:
//  继承 thread 类
//  重写 run()方法 --> 编写线程执行体
//  创建线程对象,调用start 开启线程
public class ThreadDemo1 extends Thread{

    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("执行子线程====="+ i);
        }
    }

    public static void main(String[] args) {
        // 创建线程对象
        Thread thread = new ThreadDemo1();
        //调用 start()方法开启线程
        // 执行 start方法 会自动调用 run方法
        thread.start();

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

对于源码解读,关于java中线程随机执行的原因:
在这里插入图片描述

⑥ 多线程内存原理分析
public class MyThread extends Thread{

    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("执行子线程====="+ i);
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("执行主线程<<<<<"+i);
        }
    }
} 

通过分析我们应该加深理解

  1. 区分理解:在main线程调用run方法和通过start方法间接调用run方法的区别
  2. start方法将开辟新的栈空间,实现多线程
  3. cpu就有多选择的权利,可以执行main方法,也可以执行俩个run方法
  4. 多线程好处:多个线程之间互不影响(在不同的栈空间)

在这里插入图片描述

4.2 获取线程名称

  1. 使用Thread类中的方法getName(),返回此线程的名称。
  2. 可以先获取到当前正在执行的线程,使用线程中的方法getName() 获取线程的名称
    static Thread currentThread() 返回对当前正在执行的线程对象的引用。
// 在 run 方法中
@Override
public void run() {
    // 获取线程名称,方式一
    String name = getName();
    System.out.println(name);
    // 获取线程名称,方式二
    Thread thread = Thread.currentThread();
    System.out.println(thread);
    String name = thread.getName();
    System.out.println(name);
}

推荐使用:Thread.currentThread().getName()

4.3 设置线程名称

  1. 使用Thread类中的方法setName(名字) setName(String name) 将此线程的名称更改为等于参数 name 。
  2. 创建一个带参的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字 Thread(String name) 分配一个新的 Thread对象。
public static void main(String[] args) {
    // 创建Thread类的子类对象
    MyThread myThread = new MyThread();
    // 方式一:setName 线程名称
    myThread.setName("ctra");
    myThread.start();
    // 方式二:MyThread("ctra2") 构造函数 线程名称
    new MyThread("ctra2").start();
}

4.4 sleep 方法

public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
毫秒数结束之后,线程继续执行

/*
*   public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
*   毫秒数结束之后,线程继续执行
*
* */
public class SleepDemo {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这里注意sleep的异常处理
由于当前类没有抛出异常,所以使用try catch方式捕获异常

5、创建多线程方式二: Runnable 接口

java.lang.Runnable
Runnable接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run 。

实现步骤:

  1. 创建一个 Runnable 接口的实现类
  2. 在实现类中重写Runnable接口的run方法,设置线程任务
  3. 创建一个Runnable接口的实现类对象
  4. 创建一个Thread 类对象,构造方法中传递Runnable接口的实现类对象
  5. 调用Thread 类中的start方法,开启新的线程执行run()

Runnable 接口实现类

package com.ctra.RunnableDemo3;

// 1.创建一个 Runnable 接口的实现类
public class RunnbaleImpl  implements Runnable{
    // 2.在实现类中重写Runnable接口的run方法,设置线程任务
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

main主线程 类

package com.ctra.RunnableDemo3;

public class DemoRunnable {

    public static void main(String[] args) {
        // 3.创建一个Runnable接口的实现类对象
        RunnbaleImpl runnbale = new RunnbaleImpl();
        // 4.创建一个Thread 类对象,构造方法中传递Runnable接口的实现类对象
        Thread thread = new Thread(runnbale);
        // 5.调用Thread 类中的start方法,开启新的线程执行run()
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

6、对比:Thread 类 与 Runnable接口

6.1 实现方式:2种

  1. 继承 Thread 类
  2. 实现 Runnable 接口 (推荐 ☆)

6.2 实现Runnable接口创建多线程的好处

  • 单继承的局限性:一个类智能继承一个类,类继承了Thread类就不能继续继承其他的类
  • 解耦:实现Runnable接口,把设置线程任务和开启新线程进行了分离(实现中重写了run方法来设置线程任务,创建Thread类调用start方法开启新线程)
Thread 类 Runnable接口
子类继承Thread 类 具备多线程能力 实现接口Runnable接口 具备多线程能力
启动线程:子类对象.start() 启动线程:传入目标对象+Thread对象.start()
不建议使用:避免OOP单继承局限性 推荐使用:避免单继承局限性
解耦,灵活方便
方便用一个对象被多个线程使用

7、匿名内部类方式实现线程的创建

  • 匿名:没有名字
  • 内部类:卸载其他的类的内部
  • 使用匿名内部类的作用:简化代码
  • 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

格式:
new 父类/接口(){
   重写父类/接口中的方法
}

package com.ctra.innerClassThread;

public class Demo {
    public static void main(String[] args) {

        // Thread 匿名内部类
        Thread thread = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("Thread =>" + i);
                }
            }
        };
        thread.start();

        // Runnable 匿名内部类
        Runnable runnable = new Runnable() {

            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("Runnable =>" + i);
                }
            }
        };
        new Thread(runnable).start();


        // 最终简化版
        new Thread(new Runnable() {

            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("Thread - Runnable =>" + i);
                }
            }
        }) {}.start();
    }
}

8、线程不安全-示例

8.1 出现线程不全的原因

在这里插入图片描述

8.2 出现线程不全的实现

问题: 多个线程操作同一个资源的情况下,线程不安全

↓ Runnable 接口实现类

public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int tickets = 100;

    // 设置线程任务:卖票
    public void run() {
        while (true) {
            if (tickets > 0) {
                // 为了提升安全性出现的概率,让程序休眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "购买了第:" + tickets + "号 门票");
                tickets--;
            }
        }
    }
}

↓ 测试类,开启三个线程

/*
* 模拟卖票
*  创建三个线程,同时开启,对100张共享票进行销售
*
* */

public class Test {
    public static void main(String[] args) {
        //创建Runnable接口的实现对象
        Runnable runnable = new RunnableImpl();
        //创建Thread类对象,构造方法中传递
        // 创建三个不同的线程 同时调用一个共享runnable实现类对象(对100张共享票进行销售)
        new Thread(runnable,"Kety").start();
        new Thread(runnable,"bili").start();
        new Thread(runnable,"cili").start();
    }
}

输出:
bili购买了第:100号 门票
Kety购买了第:100号 门票
cili购买了第:100号 门票
Kety购买了第:97号 门票
cili购买了第:97号 门票
bili购买了第:97号 门票
...
Kety购买了第:1号 门票
cili购买了第:0号 门票
bili购买了第:-1号 门票

视频链接:https://www.bilibili.com/video/BV1Sz4y197pj?p=11
这个是截图视频大佬的分析很给力,讲明白了sleep的真正含义,和多线程调用共享资源输出相同内容的原理
在这里插入图片描述

9、解决线程不安全问题

9.1 方式一: synchronized 同步代码块

synchronized(){
    可能会出现的线程安全问题的代码
}

注意:

  1. 通过代码块中的对象,可以使用任意的对象
  2. 必须保证多个线程使用的锁对象是一个
  3. 所对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行

① synchronized 同步代码块 代码实现

↓ 代码实现(在上面的 Runnable接口实现类中新增同步代码块):

public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int tickets = 100;
    // 创建一个锁对象  (新增)
    Object object=new Object();
    // 设置线程任务:卖票
    public void run() {
        // 使用死循环,让卖票的操作重复执行
        while (true) {
            // 同步代码块 (新增)
            synchronized (object){
                if (tickets > 0) {
                    // 为了提升安全性出现的概率,让程序休眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //有票,就卖
                    System.out.println(Thread.currentThread().getName() + "购买了第:" + tickets + "号 门票");
                    tickets--;
                }
            }
        }
    }
}

② 剖析 synchronized 同步代码块

同步技术的原理:
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器

在这里插入图片描述

③ synchronized 同步代码块的优缺点

优点:
同步保证了只能由一个线程在同步中执行共享数据,保证了安全
缺点:
程序频繁的判断锁,获取锁,释放锁,程序的效率会降低
总结:
同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去

9.2 方式二: synchronized 同步方法

同步方法:使用synchronize修饰的方法,叫做同步方法
格式:
public synchronized void method(){
    可能会出现的线程安全问题的代码
}

同步锁是谁?

  • 对于非static方法,同步锁就是this
  • 对于static方法,我们使用当前方法所在类的字节码对象(类名.class - 反射)

① synchronized 同步方法 代码实现

步骤:

  1. 把访问了共享数据的代码抽取出来,放到一个方法中
  2. 在方法上添加synchronize修饰符
package com.ctra.ThreadSynchronized;

/*
 *   实现同步的2个方法
 * 第一个:使用synchronize 代码块
 * 第二个:使用synchronize 同步方法
 *
 * */
public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int tickets = 100;
    // 创建一个锁对象  (新增)
    Object object = new Object();

    // 设置线程任务:卖票
    public void run() {
        System.out.println("this:" + this);
        // 使用死循环,让卖票的操作重复执行
        while (true) {
            // 方式一:同步代码块 (新增)
            synchronized (object) {
//                saleTickets();
                saleTickets2();
            }
        }
    }

    /*
     * 定义一个同步方法
     * 同步方法也会把方法内部的代码锁住
     * 只让一个线程执行
     * 同步方法的锁对象是谁?
     * 就是实现类对象 new RunnableImpl()
     * 也就是this
     * */
    public synchronized void saleTickets() {
        if (tickets > 0) {
            // 为了提升安全性出现的概率,让程序休眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //有票,就卖
            System.out.println(Thread.currentThread().getName() + "购买了第:" + tickets + "号 门票");
            tickets--;
        }
    }

    // 验证同步方法的对象为this
    public void saleTickets2() {
        synchronized (this) {
            if (tickets > 0) {
                // 为了提升安全性出现的概率,让程序休眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //有票,就卖
                System.out.println(Thread.currentThread().getName() + "购买了第:" + tickets + "号 门票");
                tickets--;
            }
        }
    }
}

② static synchronized 静态的同步方法

在这里插入图片描述
↓ 代码实现

 // 静态同步方法
    /*
    * 静态的同步方法
    * 锁对象是谁?
    * 不能是this
    * this是创建对象之后产生的,静态方法优先于对象
    * 静态方法的锁对象是本类的class属性 --> class文件对象(反射)
    * */
    public static synchronized void saleTicketsStatic() {
            if (tickets > 0) {
                // 为了提升安全性出现的概率,让程序休眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //有票,就卖
                System.out.println(Thread.currentThread().getName() + "购买了第:" + tickets + "号 门票");
                tickets--;
            }
    }

    // 验证静态同步方法的对象为 本类的class属性
    public static  void saleTicketsStatic2() {
        synchronized(Runnable.class){
            if (tickets > 0) {
                // 为了提升安全性出现的概率,让程序休眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //有票,就卖
                System.out.println(Thread.currentThread().getName() + "购买了第:" + tickets + "号 门票");
                tickets--;
            }
        }
    }

9.3 方式三:Lock 锁

java.util.concurrent.locks.Lock 接口
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。

常用方法:

  • void lock() 获取锁

    public void lock()获得锁。
    如果锁没有被另一个线程占用并且立即返回,则将锁定计数设置为1。
    如果当前线程已经保持锁定,则保持计数增加1,该方法立即返回。
    如果锁被另一个线程保持,则当前线程将被禁用以进行线程调度,并且在锁定已被获取之前处于休眠状态,此时锁定 保持计数被设置为1。

  • void unlock() 释放锁
    public void unlock()尝试释放此锁。
    如果当前线程是该锁的持有者,则保持计数递减。 如果保持计数现在为零,则锁定被释放。 如果当前线程不是该锁的持有者,则抛出IllegalMonitorStateException 。

常用类

  • java.util.concurrent.locks.ReentrantLock implement Lock 接口

使用步骤:

  1. 在成员位置创建一个ReentrantLock
  2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

↓ 代码实现:

package com.ctra.ThreadLock;

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

public class RunnableImpl implements Runnable {
    public static int tickets = 100;
    // 1、在成员位置创建一个ReentrantLock
    Lock l =   new ReentrantLock();
    public void run() {
        while (true){
            // 2、在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
            l.lock();
            if(tickets>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"买到了第-->"+ tickets+"号 门票");
                tickets--;
            }
            // 3、在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
            l.unlock();
        }
    }
}

防止死锁

优化代码,将unlock方法放在 finally 中,防止死锁

package com.ctra.ThreadLock;

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

public class RunnableImpl2 implements Runnable {

    public static int tickets = 100;

    // 1、在成员位置创建一个ReentrantLock
    Lock l =   new ReentrantLock();

    public void run() {
        while (true){
            // 2、在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
            l.lock();
            if(tickets>0){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"买到了第-->"+ tickets+"号 门票");
                    tickets--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    // 3、在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    l.unlock();  //无论程序是否异常,都会释放锁
                }
            }
        }
    }
}

10、 线程的状态 6个

线程状态。线程可以处于以下状态之一:

  • NEW
    尚未启动的线程处于此状态。
  • RUNNABLE
    在Java虚拟机中执行的线程处于此状态。
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态。
  • WAITING
    正在等待另一个线程执行特定动作的线程处于此状态。
  • TIMED_WAITING
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED
    已退出的线程处于此状态。

一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。

在这里插入图片描述

11.1 等待唤醒: wait & notify

在这里插入图片描述
↓ 代码实现:

package com.ctra.WaitAndNotify;

import sun.rmi.runtime.NewThreadAction;

public class DemoWaitAndNotify {

    public static void main(String[] args) {
        //   创建锁对象 保证唯一
        final Object obj = new Object();


        // 消费者 (顾客)
        new Thread() {
            @Override
            public void run() {

                // 保证等待和唤醒 (生产和消费)的线程只有一个个执行,需要使用同步技术
                synchronized (obj) {
                    System.out.println("消费者:我要买东西!!!");
                    // 调用wait方法,放弃cpu的执行,进入到waiting状态(无线等待
                    try {
                        // 使用锁对象调用wait
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("消费者:货已收到,感谢老板");
                }
            }
        }.start();


        // 生产者(老板)
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 保证 等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj) {
                    System.out.println("生产者:接到订单,我要开始生产了!!!");
                    // 做好包子后,调用notify方法,唤醒顾客吃包子
                    obj.notify();
                }
            }
        }.start();
    }
}

↓ 循环版,等待与消费 代码实现:

package com.ctra.WaitAndNotify;

import sun.rmi.runtime.NewThreadAction;

import java.util.Random;

public class DemoWaitAndNotify {

    public static void main(String[] args) {
        //   创建锁对象 保证唯一
        final Object obj = new Object();
        // 消费者 (顾客)
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    // 保证等待和唤醒 (生产和消费)的线程只有一个个执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("消费者"+":我要买东西!!!");
                        // 调用wait方法,放弃cpu的执行,进入到waiting状态(无线等待
                        try {
                            // 使用锁对象调用wait
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("消费者:货已收到,感谢老板");
                        System.out.println("----------------------------------");
                    }
                }
            }
        }.start();


        // 生产者(老板)
        new Thread() {
            @Override
            public void run() {
                while (true) {
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    // 保证 等待和唤醒的线程只能有一个执行,需要使用同步技术
                    synchronized (obj) {
                        System.out.println("生产者:接到订单,我要开始生产了!!!");
                        // 做好包子后,调用notify方法,唤醒顾客吃包子
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

10.2 计时等待 TimeWaiting: wait() & notifyAll

sleep 和 wait 的区别

  • 进入到sleep(long m)方法,在毫秒值结束后,线程进入到 Runnable/Blocked状态
  • 使用wait(long m)方法,wait方法如果在毫秒值结束后,还没有被notify唤醒,会自动苏醒,线程进入到 Runnable/Blocked状态

11、线程间通信

概念: 多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同
为什么要处理线程间通信:多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,name多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据

11.1 等待唤醒(线程通信 wait & notify)

  • wait:线程不再活动,不再参与调度,进入wait set 中,因此不会浪费CPU资源,也不会去竞争锁了,这时的线程状态即是WAITING。他还要等着别的线程一个特别的动作,也就是“通知(notify)”在这个对象上的等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中
  • notify:选取所通知对象的wait set 中的一个线程释放;例如:餐馆有空位置后,等候就餐最旧的顾客先入座
  • notifyAll:释放所有统治对象的wait set 上的全部线程

哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初终端的地方是在同步块内,而此刻他已经不持有锁,所以它需要再次尝试去获取锁(很可能面临其他线程的竞争),成功后才能在当初调用wait方法之后的地方恢复执行
总结如下:

  • 如果能获取锁,县城就从WAITING状态编程RUNNABLE状态;
  • 否则,从wait set 出来,又进入entry set ,线程就从WAITING状态又变成了 BLOCKED状态

案例分析:生产者与消费者
在这里插入图片描述

12、线程池

线程池思想概述
在这里插入图片描述

线程池:就是一个容器–>就是一个集合(ArrayList,HashSet,LinkedList<Thread>,HashMap)

线程池:JDK1.5 之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池

Executors类中的静态方法:

  • static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
  • 参数 :nThreads - 池中的线程数
  • 返回值:ExecutorService接口,返回的是ExecutorService接口的实现类对象,哦们可以使用ExecutorService接口接收(面向接口编程)

java.util.concurrent.ExecutorService :线程池接口 用来从线程池中获取线程,调用start方法,执行线程任务

  • submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。
  • shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。

线程池的使用步骤:

  1. 使用线程池的工厂类Executors里边提供的静态方法 newFixedThreadPool 生产一个指定线程数量的线程池
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务调用
  3. 调用ExecutorService 中的方法submit,传递线程任务(实现类),开启线程,执行run方法
  4. 调用ExecutorService 中的方法shutdown销毁线程池(部件一致性)

↓ 代码实现:

public class ThreadPoolDemo {

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(3);

        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
    }
}
public class RunnableImpl implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的接口");
    }
}

热门文章

暂无图片
编程学习 ·

C++核心准则ES.40:避免复杂的表达式

ES.40: Avoid complicated expressionsES.40:避免复杂的表达式Reason(原因)Complicated expressions are error-prone.复杂的表达式容易引发错误。Example(示例)// bad: assignment hidden in subexpression while ((c = getc()) != -1)// bad: two non-local variables as…
暂无图片
编程学习 ·

Python学习:变量

Python 变量类型 变量存储在内存中的值。这就意味着在创建变量时会在内存中开辟一个空间。 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中。 因此,变量可以指定不同的数据类型,这些变量可以存储整数,小数或字符。变量赋值 Python 中的变量赋…
暂无图片
编程学习 ·

一文读懂BERT(原理篇)

一文读懂BERT(原理篇)2018年的10月11日,Google发布的论文《Pre-training of Deep Bidirectional Transformers for Language Understanding》,成功在 11 项 NLP 任务中取得 state of the art 的结果,赢得自然语言处理学界的一片赞誉之声。本文是对近期关于BERT论文、相关文…
暂无图片
编程学习 ·

自动驾驶论文解析(7)

本文解析论文: A Practical Trajectory Planning Framework for Autonomous Ground Vehicles Driving in Urban Environments 来自国防科大团队 文章依旧沿用了经典的横向的空间规划,和纵向的速度规划。在横向上,横向位置是关于纵向距离S的三阶多项式。纵向上速度是关于时间…
暂无图片
编程学习 ·

直播带货平台开发公司哪家强?

直播带货正在我们的生活中大放异彩,直播带货的用户却日益增多,甚至成为了一种刚需,未来的发展必然一片光明,在各大商场店铺实时播放,与实体店销售员一样承担着重要的角色,它战胜了传统的广告,正昂首阔步向我们走来。直播的背后,也凸显出社会的进步与电子商务格局的变化…
暂无图片
编程学习 ·

Docker的帮助和镜像命令

帮助命令 docker version 查看docker版本 docker info 显示全系统信息 docker --help 显示docker相关的所有命令 镜像命令 列表镜像 docker images 列表本机上的镜像REPOSITORY --表示镜像的仓库源 TAG --表示镜像的标签 IMAGE ID --镜像的ID CREATED --镜像的创建时间 SIZE --…
暂无图片
编程学习 ·

Spring Boot + RabbitMQ 配置参数解释

application.properties配置文件写法#rabbitmq spring.rabbitmq.virtual-host=/ spring.rabbitmq.host=192.168.124.20 spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest spring.rabbitmq.listener.concurrency=10 spring.rabbitmq.l…
暂无图片
编程学习 ·

51单片机8*8点阵显示“中国”

#include <reg52.h> #include <intrins.h> //位移函数 sbit DIO=P3^4; //2片74HC595数据输入端 sbit S_CLK=P3^5;//串行输入时钟 sbit R_CLK=P3^6;//并行输出时钟 unsigned char code table[2][8]={0xEF,0xEF,0xEF,0x01,0x6D,0x01,0xEF,0xEF,0x01,0x7D,0x01,0x69,0…
暂无图片
编程学习 ·

win10查看端口进程占用

1、按 win+R,点击运行页面,写入cmd回车,点击命令行页面;2、使用命令查看端口,这里查看443端口;netstat -aon|findstr "443"3、在这里,大家可以看到,本地的433端口被PID为4452的进程占用了;4、然后,使用tasklist命令查看进程;tasklist|findstr "4452&…
暂无图片
编程学习 ·

其实AQS并不难

不啰嗦,直接上干货 文章目录上锁解锁总结条件队列 newConditionCLH队列的数据结构扩展 interrupted 上锁ReentrantLock reentrantLock = new ReentrantLock(true);或者ReentrantLock reentrantLock = new ReentrantLock();看构造函数://无参的构造函数,默认为非公平锁public…
暂无图片
编程学习 ·

查找 -- 7.1 Sear for a Range -- 图解

/********************************************************************************************************** 描述 Given a sorted array of integers, find the starting and ending position of a given target value. Your algorithm’s runtime complexity must be i…
暂无图片
编程学习 ·

01 HTML知识笔记(标签—布局)

本人使用的是sublime text3编辑器,这款软件的汉化破解版会随资料一起上传的😀然后找到的一些比较好的博文同大家一起share!!!Sublime text 3 汉化 破解版 分享Sublime Text3快捷键大全用sublime text3编写的html网页用浏览器打开出现中文乱码的原理及解决方法目录一、htm…
暂无图片
编程学习 ·

Day03 数据类型转换 +墨子

学习python的第三天墨子 墨子,名翟,春秋战国之际的思想家,墨家的创始人。 墨子反对不义之战,广收门徒周游列国,在百家争鸣的战国产生了很大的影响。墨子还是一位高明的工匠,谙熟各种机械工程技艺,为了止楚攻宋,曾与名匠公输般(鲁班)进行攻防演练,使对方折服。他还详…
暂无图片
编程学习 ·

专业外语学科复习总结

文章目录英译汉选择题Unit 1-ExcerciseUnit 2-ExerciseUnit 3-ExerciseUnit 4- ExerciseUnit 5- ExerciseUnit 6- ExerciseUnit 7- Exercise阅读题作文阶段性巩固练习(unit1~3)阶段性巩固练习(unit4~5)阶段性巩固练习(unit6~7) 英译汉 OS also manage files on computer hard d…
暂无图片
编程学习 ·

硅上量子点激光器报告最新进展总结(二)

————来自蔻享学术UCSB万雅婷博士报告一、量子点在传统的F-P腔上的应用:87%的电注入效率,175mW的输出功率,6.5mA的阈值电流 APL photonics 3(3), 030901(2018)这些指标到现在仍然代表硅上量子点激光器最好的性能。图一 F-P量子点激光器寿命测试 硅上量子点激光器具…
暂无图片
编程学习 ·

limit和rownum的区别,做兼容

两个数据库分页还是分批查sql肯定是不兼容的 然后limit的第二个参数是偏移量,不是between xx and yy 其次是limit后面不支持运算符 有种方式是 set @sql = concat(‘select* from user where id= 123456 andcode= 111 and create_date >= 20190101 and create_date <= 2…
暂无图片
编程学习 ·

UE4学习-添加机关并添加代码控制

文章目录添加机关代码编写给密室添加屋顶打印日志控制系统角色创建一个新游戏模式替换DefaultPawn添加抓取组件获取起点和终点物体拾取,碰撞属性设置今日完整代码 添加机关 首先向场景里面添加一个聚光源添加聚光源以后,可以对其属性进行修改,如图:然后需要给聚光源添加一个…
暂无图片
编程学习 ·

URI URL URN 的区别

URIUniform Resource Identifier,是一个紧凑的字符串用来标示抽象或物理资源。 包括URL与URNURLUniform Resource Locator,是URI的子集,除了确定一个资源,还提供一种定位该资源的主要访问机制(如其网络“位置”)。 eg: http://www.ietf.org/rfc/rfc2396.txt 让URI能成为URL的…
暂无图片
编程学习 ·

ThinkPHP6的前置中间件和后置中间件有哪些区别?

在上一个例子中我们学会了定义中间件,也理解了中间件的含义,再向下翻翻手册,又发现一个叫做“前置中间件”、“后置中间件”,这是什么情况?两者又有和区别呢? 一、定义的区别。 从官网手册中不难发现,前置中间件和后置中间件定义就不同,我们看下面。 前置中间件定义: …