Java多线程

1. 基础知识引入

1.1 线程

操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位

单线程:一个进程如果只有一条执行路径,则称为单线程程序
​多线程:一个进程如果有多条执行路径,则称为多线程程序

1.2 进程

程序的基本执行实体

  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
  • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
  • 并发性:任何进程都可以同其他进程一起并发执行

1.3 多线程应用场景

  • 软件中的耗时操作
  • 拷贝、迁移大文件
  • 加载大量的资源文件

1.4 并发和并行

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

2. 多线程的实现方式

2.1 实现多线程方式一:继承Thread类

package com.bobo.Thread;

public class Test {
    public static void main(String[] args) {
        /*
        多线程的第一种启动方式:
            1.自己定义一个类继承Thread类
            2.重写run方法
            3.创建子类的对象,并启动线程
        */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("t1");
        t2.setName("t2");

        //开启线程
        t1.start();
        t2.start();
    }
}

package com.bobo.Thread;

public class MyThread extends Thread{
    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 0; i < 20; i++) {
            System.out.println(getName() + "hello");
        }

    }
}

2.2 实现多线程方式二:实现Runnable接口

package com.bobo.Thread1;

public class Test {
    public static void main(String[] args) {
        /*
            多线程的第二种启动方式:
                1.自已定义一个类实现Runnable接口
                2.重写里面的run方法
                3.创建自已的类的对象
                4.创建个Thread类的对象,并开启线程
         */

        //创建MyRun的对象
        //表示多线程要执行的任务
        MyRun mr = new MyRun();

        //创建线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        //给线程设置名字
        t1.setName("t1");
        t2.setName("t2");

        //开启线程
        t1.start();
        t2.start();
    }
}

package com.bobo.Thread1;

public class MyRun implements Runnable{
    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 0; i < 20; i++) {
            //获取到当前线程的对象
            Thread t = Thread.currentThread();

            System.out.println(t.getName() + "hello");
        }
    }
}

2.3 实现多线程方式三: 实现Callable接口和Future接口

package com.bobo.Thread2;

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

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*
            多线程的第三种实现方式:
                特点:可以获取到多线程运行的结果

                1.创建一个类MyCallab1e实现ca11ab1e接口
                2.重写ca11(是有返回值的,表示多线程运行的结果)
                3.创建MyCa11ab1e的对象(表示多线程要执行的任务)
                4.创建FutureTask的对象(作用管理多线程运行的结果)
                5.创建Thread类的对象。并启动(表示线程)
         */

        //创建MyCallable的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();

        //创建FutureTask的对象(作用:管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);

        //创建线程的对象
        Thread t1 = new Thread(ft);

        //启动线程
        t1.start();

        //获取多线程结果
        Integer res = ft.get();
        System.out.println(res);

    }
}

package com.bobo.Thread2;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return  sum;
    }
}

2.4 三种实现方式的对比

  • 实现Runnable、Callable接口
    • 好处: 扩展性强,实现该接口的同时还可以继承其他的类
    • 缺点: 编程相对复杂,不能直接使用Thread类中的方法
  • 继承Thread类
    • 好处: 编程比较简单,可以直接使用Thread类中的方法
    • 缺点: 可以扩展性较差,不能再继承其他的类

3. 多线程中常用的成员方法

在这里插入图片描述

package com.bobo.ThreadMethod;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        /*
            细节:
            1、如果我们没有给线程设置名字,线程也是有默认的名字的
                格式:Thread-X(X序号,从0开始的)
            2、如果我们要给线程设置名字,可以用set方法进行设置,也可以利用Thread中的构造方法设置
         */
        //创建线程的对象 以及 设置线程名字
        MyThread t1 = new MyThread("aaa");
        MyThread t2 = new MyThread("bbb");

        //开启线程
        t1.start();
        t2.start();

        /*
            static Thread currentThread():获取当前线程的对象
            细节:
                当JVM虚拟机启动之后,会自动的启动多条线程
                其中有一条线程就叫做main线程
                他的作用就是去调用main方法,并执行里面的代码
                在以前,我们写的所有的代码,其实都是运行在main线程当中
         */
        System.out.println(Thread.currentThread().getName());//main

        System.out.println("1111111");
        Thread.sleep(1000);
        System.out.println("2222222");

        //创建线程要执行的参数对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象 以及 设置线程名字
        Thread t3 = new Thread(mr,"ccc");
        Thread t4 = new Thread(mr,"ddd");

        //查看线程默认优先级
        System.out.println(t4.getPriority());//5
        System.out.println(t3.getPriority());//5
        System.out.println(Thread.currentThread().getPriority());//5

        //自定义线程优先级-越大越优先-优先级越大不代表一定先运行它
        t4.setPriority(10);
        t3.setPriority(1);

        //启动线程
        t3.start();
        t4.start();

        //创建线程的对象 以及 设置线程名字
        MyThread t5 = new MyThread("eee");
        MyThread1 t6 = new MyThread1("fff");


        /*
            细节:
                当其他的非守护线程执行完毕之后,守护线程会陆续结束
         */
        //设置为守护线程(备胎线程)
        t6.setDaemon(true);

        t5.start();
        t6.start();

        //创建线程的对象 以及 设置线程名字
        MyThread t7 = new MyThread("飞机✈");
        t7.start();

        //表示把t7这个线程插入到当前线程(main线程)之前
        t7.join();

        //执行在main线程当中
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程" + i);
        }

    }
}

package com.bobo.ThreadMethod;

public class MyThread extends Thread{
    public MyThread() {
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

package com.bobo.ThreadMethod;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "@" + i);
        }
    }
}

package com.bobo.ThreadMethod;

public class MyThread1 extends Thread{
    public MyThread1() {
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
            Thread.yield();//表示让出当前CPU的执行权,目的使打印更加均匀
        }
    }
}

4. 线程生命周期

在这里插入图片描述

5. 同步代码块

把操作共享数据的代码锁起来

5.1 同步代码块格式

synchronized(任意对象) { 
	多条语句操作共享数据的代码 
}

特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开

5.2 同步代码块代码实例

package com.bobo.ThreadDemo;

public class Test {
    public static void main(String[] args) {
        /*
            需求:
                   某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
         */
        //创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        //起名字
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

package com.bobo.ThreadDemo;

public class MyThread extends Thread{
    //表示这个类所有对象,都共享ticket
    static int ticket = 0;
    //锁对象,一定要是唯一的
    static Object obj = new Object();

    @Override
    public void run() {
        while (true){
            /*
            锁对象:
                this:当前线程,不唯一
                MyThread.class:当前类的字节码文件,唯一
             */
            synchronized (obj){
                if (ticket < 100){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
                }else {
                    break;
                }
            }
        }
    }
}

6. 同步方法

把synchronized关键字加到方法上

6.1 同步方法格式

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

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

6.2 同步方法代码实例

package com.bobo.ThreadDemo1;

public class Test {
    public static void main(String[] args) {
        /*
            需求:
                   某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

            利用同步方法实现
         */
        //用于创建线程的参数
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

package com.bobo.ThreadDemo1;

public class MyRunnable implements Runnable {
    int ticket = 0;

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

    private synchronized boolean method() {
        if (ticket == 100) {
            return true;
        } else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!!!");
        }
        return false;
    }
}

7.Lock锁

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了手动上锁、手动释放锁的方法

  • void lock():获得锁
  • void unlock():释放锁

Lock是接口,不能直接实例化,这里采用它的实现类ReentrantLock来实例化

  • ReentrantLock构造方法

    方法名说明
    ReentrantLock()创建一个ReentrantLock的实例

代码实例:

package com.bobo.ThreadLock;

public class Test {
    public static void main(String[] args) {
        /*
            需求:
                   某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
         */
        //创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        //起名字
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

package com.bobo.ThreadLock;

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

public class MyThread extends Thread {
    //表示这个类所有对象,都共享ticket
    static int ticket = 0;
    //必须加static,不然 锁 不唯一
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();//上锁
            try {
                if (ticket < 100) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
                } else {
                    break;
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();//解锁
            }

        }
    }
}

8. 死锁

8.1 概述

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

8.2 什么情况下会产生死锁

  1. 资源有限
  2. 同步嵌套

8.3 死锁代码实例

package com.bobo.ThreadLock1;

public class Test {
    public static void main(String[] args) {
        /*
            死锁
         */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程A");
        t2.setName("线程B");

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

package com.bobo.ThreadLock1;

public class MyThread extends Thread{
    //定义两把锁
    static Object objectA = new Object();
    static Object objectB = new Object();

    @Override
    public void run() {
        while (true){
            if ("线程A".equals(getName())){
                synchronized (objectA){
                    System.out.println("线程A拿到了A锁,准备拿B锁");
                    synchronized (objectB){
                        System.out.println("线程A拿到了B锁,顺利执行完一轮");
                    }
                }
            } else if ("线程B".equals(getName())) {
                synchronized (objectB){
                    System.out.println("线程B拿到了B锁,准备拿A锁");
                    synchronized (objectA){
                        System.out.println("线程B拿到了A锁,顺利执行完一轮");

                    }
                }
            }
        }
    }
}

9. 生产者和消费者

9.1 常见方法

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程

9.2 代码实例

package com.bobo.ProducerAndConsumer;

public class Test {
    public static void main(String[] args) {
        /*
            需求:完成生产者和消费者(等待唤程机制的代码)
                实现线程轮流交替执行的效果
         */
        Cook cook = new Cook();
        Foodie foodie = new Foodie();
        Foodie foodie1 = new Foodie();

        cook.setName("厨师");
        foodie.setName("吃货1");
        foodie1.setName("吃货2");

        cook.start();
        foodie.start();
        foodie1.start();
    }
}

package com.bobo.ProducerAndConsumer;

public class Cook extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if (Desk.count == 0){
                    //面料不足
                    break;
                }else{
                    //判断桌子上是否有食物
                    if (Desk.foodFlag == 1){
                        //桌子上有面条
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //桌子上没有面条
                        System.out.println("cook做了一碗");

                        //修改桌子上食物状态
                        Desk.foodFlag = 1;

                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }

                }
            }
        }
    }
}

package com.bobo.ProducerAndConsumer;

public class Desk {
    /*
        控制生产者和消费者的执行
     */

    //是否有面条 0:没有面条 1:有面条
    public static int foodFlag = 0;

    //限制cook 一天内可做的面条碗数总个数
    public static int count = 10;

    //锁对象
    public static Object lock = new Object();

}

package com.bobo.ProducerAndConsumer;

public class Foodie extends Thread{
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock){
                if (Desk.count == 0){
                    //面料不足
                    break;
                }else {
                    //面料充足
                    //判断桌子上是否有面条
                    if (Desk.foodFlag == 0){
                        //桌子上没有面条-等着做-给厨师发信号让他做
                        try {
                            //让当前线程与锁进行绑定
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        //唤醒跟这把锁绑定的所有线程
                        Desk.lock.notifyAll();
                    }else {
                        //桌子上有面条-吃面条-给厨师发信号-修改桌子状态
                        //把吃的总数 -1
                        Desk.count--;

                        //开始吃
                        System.out.println(getName() + "开吃,面材料还够做" + Desk.count + "碗");

                        //吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();

                        //修改桌子状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

10. 阻塞队列

10.1 阻塞队列继承结构

在这里插入图片描述

  • 常见BlockingQueue:

    ArrayBlockingQueue: 底层是数组,有界

    LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值

  • BlockingQueue的核心方法:

    put(anObject): 将参数放入队列,如果放不进去会阻塞

    take(): 取出第一个数据,取不到会阻塞

10.2 代码实例

package com.bobo.BlockQueue;

import java.util.concurrent.ArrayBlockingQueue;

public class Test {
    public static void main(String[] args) {
        /*
            需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
            细节:
                生产者和消费者必须使用同一个阻塞队列
         */
        //创建阻塞队列的对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        //创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        c.start();
        f.start();
    }
}

11. 线程状态

通过源码我们可以看到Java中的线程存在6种状态,每种线程状态的含义如下

线程状态具体含义
NEW一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
RUNNABLE当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
BLOCKED当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED一个完全运行完成的线程的状态。也称之为终止状态、结束状态

各个状态的转换,如下图所示:
在这里插入图片描述

12. 线程池

12.1 以前写多线程的弊端

  • 用到线程的时候就创建
  • 用完之后线程消失

12.2 线程池主要核心原理

  • ①创建一个池子,池子中是空的
  • ②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  • ③但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

12.3 线程池实现步骤

  1. 创建线程池
  2. 提交任务
  3. 所有的任务全部执行完毕,关闭线程池

12.4 线程池代码实现

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

public static ExecutorService newCachedThreadPool()   //创建一个没有上限的线程池
public static newFixedThreadPool(int nThreads)	    //创建有上限的线程池

代码实例:

package com.bobo.ThreadPool;

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

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();

        //提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());

        //销毁线程池
        pool1.shutdown();
    }
}

package com.bobo.ThreadPool;

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

public class Test1 {
    public static void main(String[] args) {
        //获取线程池对象
        ExecutorService pool1 = Executors.newFixedThreadPool(3);

        //提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());//第四个不起作用

        //销毁线程池
        pool1.shutdown();
    }
}

package com.bobo.ThreadPool;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

12.5 自定义线程池

12.5.1 创建自定义线程池对象

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

ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                6,//最大线程数(核心线程 + 空闲线程)
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
        );

12.5.2 自定义线程池(任务拒绝策略)

在这里插入图片描述

12.5.3 自定义线程池小结

自定义线程池步骤:

  • 创建一个空的池子
  • 有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程

不断的提交任务,会有以下三个临界点:

  • 当核心线程满时,再提交任务就会排队
  • 当核心线程满,队伍满时,会创建临时线程
  • 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略

12.5.3 自定义线程池拓展

什么是最大并行数?
答:4核8线程,最大并行数为8

线程池多大?
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mandela688

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

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

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

打赏作者

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

抵扣说明:

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

余额充值