Thread多线程(创建,方法,安全,通信,线程池,并发,并行,线程的生命周期)【全详解】

目录

1.多线程概述

2.多线程的创建

3.Thread的常用方法

4.线程安全

5.线程同步

6.线程通信

7.线程池

8.其它细节知识:并发、并行

9.其它细节知识:线程的生命周期


1.多线程概述

线程是什么?

线程(Thread)是一个程序内部的一条执行流程。

程序中如果只有一条执行流程,那这个程序就是单线程的程序。

多线程是什么?

多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。

2.多线程的创建

方式一:继承Thread类

实现步骤:

定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法

创建MyThread类的对象

调用线程对象的start()方法启动线程(启动后还是执行run方法的)

代码实现

java 复制代码
package com.itheima.day11.teacher.thread01;
/*
  start()  开启新线程的方法   ---  线程开启功能
  run()  线程执行的代码       ---  线程任务
   继承方法
       MyThread 既是一个线程对象(负责线程开启)  又是一个线程任务(写需要执行线程任务)
       耦合性高!!
       线程对象 -- 线程任务 分离!!
 */
public class MyThread extends Thread{

    @Override
    public void run() {
        /*
          每个新的线程 都循环输出 十次
         */
        for (int i = 0; i < 10; i++) {
           // 代码执行过程中 可以获取到当前正在运行的线程对象
            Thread thread = Thread.currentThread();
            System.out.println("新的线程"+thread.getName()+"正在执行"+i);
        }
    }
}


-----------------------
package com.itheima.day11.teacher.thread01;

public class Test {

    public static void main(String[] args) {
        System.out.println("当前是一个程序的入口"+Thread.currentThread().getName());
        System.out.println("程序入口也是一个线程 这个线程叫主线程..");

        //主线程执行过程中 再产生新的线程。
        /*
          线程的创建方式一
              1: 创建一个类 继承 Thread线程类
              2: 手动重写run方法。----就是新的线程对象要执行的内容.
              3: 创建 子类对象(线程对象)。
              4: 调用start方法 开启这个新的线程。
              当开启完了 程序中出现两个线程了 一个是 主线程  一个新建的线程。
              每个线程将来都是独立的空间。
         */
        MyThread t1 = new MyThread();
        t1.start();
        // 开启新的线程

        for (int i = 0; i < 10; i++) {
            System.out.println("线程"+Thread.currentThread().getName()+"正在执行"+i);
        }
    }
}


--------------------
package com.itheima.day11.teacher.thread01;

public class Test2 {
    /*
            线程的创建方式一
                1: 创建一个类 继承 Thread线程类
                2: 手动重写run方法。----就是新的线程对象要执行的内容.
                3: 创建 子类对象(线程对象)。
                4: 调用start方法 开启这个新的线程。
                当开启完了 程序中出现两个线程了 一个是 主线程  一个新建的线程。
                每个线程将来都是独立的空间。
            多个线程执行
               每次执行效果 不尽相同 因为 CPU的高速切换 没有规律

             Thread 代表线程对象的类
                Thread.currentThread() 获取 执行当前代码的线程。
             普通方法
                 getName() 获取线程的名字 名字默认 Thread-0  Thread-1 ....
                 可以设置名字
                 setName("...")
           */
    public static void main(String[] args) {
        //程序入口也是一个线程 这个线程叫主线程..
        System.out.println("当前是一个程序的入口"+Thread.currentThread().getName());
        //主线程执行过程中 再产生新的线程。
        MyThread t1 = new MyThread();
        t1.setName("小迪迪");
        t1.start();
        // 开启新的线程

        // 这还是main中
        for (int i = 0; i < 10; i++) {
            System.out.println("线程"+Thread.currentThread().getName()+"正在执行"+i);
        }
    }
}

优缺点:

优点:编码简单

缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展

方式二:实现Runnable接口

实现步骤:

定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

创建MyRunnable任务对象

把MyRunnable任务对象交给Thread处理。

调用线程对象的start()方法启动线程

java 复制代码
package com.itheima.day11.teacher.thread02;
/*
  线程任务类 里面只有线程任务 run方法
 */
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.itheima.day11.teacher.thread02;

public class RunnableTest {
    /*
      创建线程方式二
           1: 定义一个实现Runnable接口的 线程任务类,重写run方法。
           2: 创建一个线程任务对象。
           3: 创建线程对象 并在构造中传递线程任务对象。
           4: 开启新的线程 线程对象.start()
     */
    public static void main(String[] args) {
        // 创建线程任务对象
        MyRunnable mr = new MyRunnable();
        // 创建线程对象的同时 将线程任务传递过去 --- 线程和任务绑定
        Thread t = new Thread(mr);
        //调用start方法
        t.start();

        //主线程操作
        for (int i = 0; i <10 ; i++) {
            System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);
        }

    }
}


--------------------
package com.itheima.day11.teacher.thread02;

public class RunnableTest2 {
    /*
      创建线程方式二
           1: 定义一个实现Runnable接口的 线程任务类,重写run方法。
           2: 创建一个线程任务对象。
           3: 创建线程对象 并在构造中传递线程任务对象。
           4: 开启新的线程 线程对象.start()
     */
    public static void main(String[] args) {
        // 创建线程任务对象
        MyRunnable mr = new MyRunnable();
        //上面的操作 创建了一个外部类 实现了接口 并创建该外部类的对象  外部类对象---Runnabel接口的实现类对象
        // 创建线程对象的同时 将线程任务传递过去 --- 线程和任务绑定
        Thread t1 = new Thread(mr);//mr 是 Runnable接口的实现类对象
        //调用start方法
        t1.start();

        //
//        Thread t2  = new Thread(匿名内部类形式)  匿名内部类本质  子类对象
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);
                }
            }
        });
        t2.start();

        // 能不能用lambda  可以
        // lambda  作用简化匿名内部类   使用前提 参数是一个函数式接口  函数式接口 有且只有一个抽象方法的接口

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);
            }
        }).start();

    }
}

优缺点:

优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。

缺点:需要多一个Runnable对象。

方式三:实现Callable接口

实现步骤:

1.创建任务对象:

1.定义一个类实现Callable接口,重写call方法,封装要做的事情和要返回的数据。

2.把Callable类型的对象封装成FutureTask对象(线程任务对象)。

2.把线程任务对象封装成Thread对象。

3.调用Thread对象的start方法启动线程。

4.线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。

java 复制代码
package com.itheima.day11.teacher.thread03;

import java.util.concurrent.Callable;

/*
   Callable接口有个泛型 表示 返回的结果类型。

      返回值 call
 */
public class MyCallable implements Callable<Integer> {

    // 构造   方法 都可以访问 成员变量
    private Integer number;

    public MyCallable(Integer number){
        // 当能够调用call方法的时候  构造方法执行完了
        // 方法中没有参数  构造中可不可以设计参数
        // 把构造中传递的number 赋值给成员变量
        this.number = number;
    }

    //任务 求一个数的 绝对值
    @Override
    public Integer call() throws Exception {

        System.out.println("当前的线程:"+Thread.currentThread().getName());

        return Math.abs(number);//直接使用
    }


}


-----------------------
package com.itheima.day11.teacher.thread03;

import java.util.concurrent.Callable;

/*
   Callable接口有个泛型 表示 返回的结果类型。

      返回值 call
 */
public class MyCallable1 implements Callable<String> {
    //任务 求一个数的 绝对值
    @Override
    public String call() throws Exception {

        System.out.println("当前的线程:"+Thread.currentThread().getName());

        return "下载任务执行成功";//直接使用
    }
}


-----------------------
package com.itheima.day11.teacher.thread03;

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

public class CallableTest {
    /*
      创建线程方式三
          1:创建子类实现Callable接口 完成call方法重写。
          2:创建线程任务(自定义callable)对象.
          3:创建一个 线程任务管理对象 FutureTask封装 callable实现对象。
          4:创建线程对象 绑定线程任务管理对象
          5:调用start方法
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程任务对象
        MyCallable my = new MyCallable(-10);//带参构造
        //传参给 Thread 创建线程对象
//        Thread thread = new Thread(my);
        // Callable 是一个带有返回值的 任务--需要先交给任务管理对象 因为执行之后该线程是有返回值的 得有对象去处理返回值
        FutureTask<Integer> task = new FutureTask<>(my);
        // 在把 task对象 绑定到线程对象中
        Thread thread = new Thread(task);
        // 调用start方法
        thread.start();
       // 返回值在 Task身上
        System.out.println("任务执行结果:"+task.get());

        MyCallable1 callable1 = new MyCallable1();
        FutureTask<String> stringFutureTask = new FutureTask<String>(callable1);
        Thread thread1 = new Thread(stringFutureTask);
        thread1.start();
        String s = stringFutureTask.get();
        System.out.println(s);

        //这样写没意义,不能传参数,只能自己定义吗  
/*        FutureTask<Integer> integerFutureTask = new FutureTask<>(new Callable<Integer>() {
            int a=10;
            int b=0;
            @Override
            public Integer call() throws Exception {
                return a+b;
            }
        });

        new Thread(integerFutureTask).start();  //不执行线程是拿不到返回会值结果的
//        new Thread(new FutureTask<Integer>(()-> 10+0 )).start(); 可以这样简写,但是拿不到结果值没意义
        System.out.println(integerFutureTask.get());*/

    }
}

**3.**Thread的常用方法

java 复制代码
package com.itheima.day12.teacher.thread01;

public class MyThread extends Thread{

    public MyThread(String name){
        super(name);//把名字传递给父类的构造
    }

    //线程任务方法 将来哪个线程对象执行 在它的代码中就可以得到哪个线程对象
    @Override
    public void run() {
         // 干吕布
        Thread thread = Thread.currentThread();//获取当前线程对象
        for (int i = 1; i <=5 ; i++) {
            //每人跟吕布过招 5次
            System.out.println(thread.getName()+"大呼:吕布小儿,莫跑~~吃我"+i+"招!!");
//            System.out.println(super.getName()+"大呼:吕布小儿,莫跑~~吃我"+i+"招!!");
        }

    }
}


----------------------
package com.itheima.day12.teacher.thread01;

public class ThreadDemo {

    public static void main(String[] args) throws InterruptedException {
        //三英战吕布
        System.out.println("接下来请欣赏  三英战吕布!");
        // 每秒钟 输出  5  4  3  2  1
        for (int i = 5; i >=1 ; i--) {
            System.out.println(i);
            //休眠一秒
            Thread.sleep(1000);
        }
        MyThread bb = new MyThread("刘备");
        System.out.println(bb.getName());
        //        bb.setName("刘备");
        MyThread yy = new MyThread("关羽");

        System.out.println(yy.getName());
        MyThread ff = new MyThread("张飞");
        System.out.println(ff.getName());


        bb.start();
        yy.start();
        ff.start();



    }
}


---------------------
package com.itheima.day12.teacher.thread01;

public class ThreadDemo02 {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("宇迪和弓箭手 一起打吕布");
        //创建宇迪线程
        MyThread my = new MyThread("宇迪");
        my.start();//宇迪线程执行

        my.join();//先执行 当前 my线程对象 再执行其他的线程

        //同时总部 排除弓箭手也对 宇迪进行打击
        Thread.currentThread().setName("弓箭手");
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName()+"在发射第"+i+"只箭~");
        }

    }
}

**4.**线程安全

什么是线程安全问题?

多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

线程安全问题出现的原因?

1.存在多个线程在同时执行

2.多个线程同时访问一个共享资源

3.存在修改该共享资源的情况

用程序模拟线程安全问题

/**
出现的问题
    两人同时取钱,卡里10万,结果都取成功,卡里-10万
*/


package com.itheima.day12.teacher.asynchronize;
// 先定义账户类
public class Account {
    private String cardId;
    private double money;//余额


    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }
    /*
     设计一个取钱的方法
        参数   取得钱 金额
        返回值  不需要
     */
    public void drawMoney(double money){
        //  局部位置money 代表取的钱
        //  成员位置money 代表余额
        //  this.money -= money;
        // 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程
        String name = Thread.currentThread().getName();
        // 判断余额是否充足
        if(this.money >= money){//可以取
            System.out.println(name+"来取钱:"+money+" 成功!");
            this.money -= money;
            System.out.println(name+"取钱之后的余额:"+this.money);
        }else {
            System.out.println(name+"来取钱:余额不足,请充值后再去...");
        }
    }



    /**
     * 获取
     * @return cardId
     */
    public String getCardId() {
        return cardId;
    }

    /**
     * 设置
     * @param cardId
     */
    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    /**
     * 获取
     * @return money
     */
    public double getMoney() {
        return money;
    }

    /**
     * 设置
     * @param money
     */
    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{cardId = " + cardId + ", money = " + money + "}";
    }
}


---------------
package com.itheima.day12.teacher.asynchronize;
/*
  取钱线程类
 */
public class DrawMoney extends Thread{
    // 定义一个账户的成员变量  --初始化交给了构造 由外界传递进来
    private Account account;
    //  构造第一个参数 是 取钱的账户对象  第二参数 表示线程的名字
    public DrawMoney(Account account,String name){
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        //取钱就是在账户里面 减少余额
        // 调用账户对象 的 取钱方法
        // 小明线程 小红线程 操作的 账户是同一个
        // 在测试类创建 一个账户 传递进来
        account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)
    }
}


------------------
package com.itheima.day12.teacher.asynchronize;

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个账户对象   小明和小红 共享账户   卡号        余额
        Account account = new Account("ICBC-114",100000);
        //创建两个线程对象 分别代表小红 和 小明
        // 线程对象中传递 共享的账户 以及 线程名字
        new DrawMoney(account,"小明").start();
        new DrawMoney(account,"小红").start();
    }
}

5.线程同步

1.认识线程同步: 线程同步是解决线程安全问题的方案

2.线程同步的思想:

1.让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

2.加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。

3.线程同步的解决方案

方式一:同步代码块

作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。

写法:

synchronized(同步锁) { 访问共享资源的核心代码 }

同步锁的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。

锁对象的使用规范:

建议使用共享资源作为锁对象

对于实例方法建议使用this作为锁对象

对于静态方法建议使用字节码(类名.class)对象作为锁对象

java 复制代码
package com.itheima.day12.teacher.synchronize01;
// 先定义账户类
public class Account {
    private String cardId;
    private double money;//余额


    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }
    /*
     设计一个取钱的方法
        参数   取得钱 金额
        返回值  不需要
     */
    public void drawMoney(double money){
       // 使用同步代码块方式加锁  保证两个线程只要一个线程在修改共享资源
        //  锁对象怎么选择  --- 只要保证 两个线程对象将来公用一把锁
        synchronized (this){ //() 里面的对象具备唯一性 "lock" 字符串一旦创建不能能改
                   //直接写字符串  可以 但是不规范  开发规范当前共享资源  一般普通方法写 this  静态方法写 类名.class
            //  局部位置money 代表取的钱
            //  成员位置money 代表余额
            //  this.money -= money;
            // 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程
            String name = Thread.currentThread().getName();
            // 判断余额是否充足
            if(this.money >= money){//可以取
                System.out.println(name+"来取钱:"+money+" 成功!");
                this.money -= money;
                System.out.println(name+"取钱之后的余额:"+this.money);
            }else {
                System.out.println(name+"来取钱:余额不足,请充值后再去...");
            }
        }


    }



    /**
     * 获取
     * @return cardId
     */
    public String getCardId() {
        return cardId;
    }

    /**
     * 设置
     * @param cardId
     */
    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    /**
     * 获取
     * @return money
     */
    public double getMoney() {
        return money;
    }

    /**
     * 设置
     * @param money
     */
    public void setMoney(double money) {
        this.money = money;
    }

    public String toString() {
        return "Account{cardId = " + cardId + ", money = " + money + "}";
    }
}


-----------------------
package com.itheima.day12.teacher.synchronize01;

/*
  取钱线程类
 */
public class DrawMoney extends Thread{
    // 定义一个账户的成员变量  --初始化交给了构造 由外界传递进来
    private Account account;
    //  构造第一个参数 是 取钱的账户对象  第二参数 表示线程的名字
    public DrawMoney(Account account, String name){
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        //取钱就是在账户里面 减少余额
        // 调用账户对象 的 取钱方法
        // 小明线程 小红线程 操作的 账户是同一个
        // 在测试类创建 一个账户 传递进来
        account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)
    }
}


---------------------
package com.itheima.day12.teacher.synchronize01;

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个账户对象   小明和小红 共享账户   卡号        余额
        Account account = new Account("ICBC-114",100000);
        //创建两个线程对象 分别代表小红 和 小明
        // 线程对象中传递 共享的账户 以及 线程名字
        new DrawMoney(account,"小明").start();
        new DrawMoney(account,"小红").start();
    }
}

方式二:同步方法

作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

写法:

修饰符 synchronized 返回值类型 方法名称(形参列表) { 操作共享资源的代码 }

同步方法底层原理:

1.同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。

2.如果方法是实例方法:同步方法默认用this作为的锁对象。

3.如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

是同步代码块好还是同步方法好一点?

范围上:同步代码块锁的范围更小,同步方法锁的范围更大。

可读性:同步方法更好。

java 复制代码
package com.itheima.day12.teacher.synchronize02;
// 先定义账户类
public class Account {
    private String cardId;
    private double money;//余额


    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }
    /*
     设计一个取钱的方法
        参数   取得钱 金额
        返回值  不需要

          同步方法
             就是将锁 固定到方法上了  整个方法上锁了
          方法声明位置 加入 synchronized 关键字
     */
    public synchronized void drawMoney(double money){
            //  局部位置money 代表取的钱
            //  成员位置money 代表余额
            //  this.money -= money;
            // 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程
            String name = Thread.currentThread().getName();
            // 判断余额是否充足
            if(this.money >= money){//可以取
                System.out.println(name+"来取钱:"+money+" 成功!");
                this.money -= money;
                System.out.println(name+"取钱之后的余额:"+this.money);
            }else {
                System.out.println(name+"来取钱:余额不足,请充值后再去...");
            }


    }



    /**
     * 获取
     * @return cardId
     */
    public String getCardId() {
        return cardId;
    }

    /**
     * 设置
     * @param cardId
     */
    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    /**
     * 获取
     * @return money
     */
    public double getMoney() {
        return money;
    }

    /**
     * 设置
     * @param money
     */
    public void setMoney(double money) {
        this.money = money;
    }

    public String toString() {
        return "Account{cardId = " + cardId + ", money = " + money + "}";
    }
}


-----------------
package com.itheima.day12.teacher.synchronize02;

/*
  取钱线程类
 */
public class DrawMoney extends Thread{
    // 定义一个账户的成员变量  --初始化交给了构造 由外界传递进来
    private Account account;
    //  构造第一个参数 是 取钱的账户对象  第二参数 表示线程的名字
    public DrawMoney(Account account, String name){
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        //取钱就是在账户里面 减少余额
        // 调用账户对象 的 取钱方法
        // 小明线程 小红线程 操作的 账户是同一个
        // 在测试类创建 一个账户 传递进来
        account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)
    }
}


-----------------
package com.itheima.day12.teacher.synchronize02;

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个账户对象   小明和小红 共享账户   卡号        余额
        Account account = new Account("ICBC-114",100000);
        //创建两个线程对象 分别代表小红 和 小明
        // 线程对象中传递 共享的账户 以及 线程名字
        new DrawMoney(account,"小明").start();
        new DrawMoney(account,"小红").start();
    }
}

方式三:Lock锁

Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。

Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

java 复制代码
package com.itheima.day12.teacher.lock;

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

// 先定义账户类
public class Account {
    private String cardId;
    private double money;//余额


    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }
    /*
     设计一个取钱的方法
        参数   取得钱 金额
        返回值  不需要
         Lock 都是加锁原理
           底层 都是每次只允许一个线程加锁 加锁之后才能访问,手动解锁 其他线程可以再加锁进来。
           灵活的加锁和释放锁

           两个方法
              lock() 加锁  unlock() 释放锁

          Lock接口 用它的实现类
     */
    //成员位置创建一个 Lock锁对象 锁对象不能被改 所以 加上final修饰
    private final Lock lock = new ReentrantLock();

    public  void drawMoney(double money){
            //  局部位置money 代表取的钱
            //  成员位置money 代表余额
            //  this.money -= money;
            // 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程
            String name = Thread.currentThread().getName();
            // 判断余额是否充足
           lock.lock();//加锁
            if(this.money >= money){//可以取
                System.out.println(name+"来取钱:"+money+" 成功!");
                this.money -= money;
                System.out.println(name+"取钱之后的余额:"+this.money);

            }else {
                System.out.println(name+"来取钱:余额不足,请充值后再去...");
            }
        lock.unlock();//释放锁

    }



    /**
     * 获取
     * @return cardId
     */
    public String getCardId() {
        return cardId;
    }

    /**
     * 设置
     * @param cardId
     */
    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    /**
     * 获取
     * @return money
     */
    public double getMoney() {
        return money;
    }

    /**
     * 设置
     * @param money
     */
    public void setMoney(double money) {
        this.money = money;
    }

    public String toString() {
        return "Account{cardId = " + cardId + ", money = " + money + "}";
    }
}


---------------------
package com.itheima.day12.teacher.lock;

/*
  取钱线程类
 */
public class DrawMoney extends Thread{
    // 定义一个账户的成员变量  --初始化交给了构造 由外界传递进来
    private Account account;
    //  构造第一个参数 是 取钱的账户对象  第二参数 表示线程的名字
    public DrawMoney(Account account, String name){
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        //取钱就是在账户里面 减少余额
        // 调用账户对象 的 取钱方法
        // 小明线程 小红线程 操作的 账户是同一个
        // 在测试类创建 一个账户 传递进来
        account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)
    }
}


---------------------
package com.itheima.day12.teacher.lock;

public class ThreadTest {

    public static void main(String[] args) {
        //创建一个账户对象   小明和小红 共享账户   卡号        余额
        Account account = new Account("ICBC-114",100000);
        //创建两个线程对象 分别代表小红 和 小明
        // 线程对象中传递 共享的账户 以及 线程名字
        new DrawMoney(account,"小明").start();
        new DrawMoney(account,"小红").start();
    }
}

**6.**线程通信

**1.**什么是线程通信?

当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

**2.**线程通信的常见模型(生产者与消费者模型)

生产者线程负责生产数据

消费者线程负责消费生产者生产的数据。

注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!

**3.**Object类的等待和唤醒方法:

注意事项:上述方法应该使用当前同步锁对象进行调用。

java 复制代码
package com.itheima.day12.teacher.wait_notify;

import java.util.ArrayList;
import java.util.List;

/*
   定义桌子类
       定义一个存储包子的集合 存储包子

       定义一个厨师生产包子的方法

       定义一个吃货吃包子的方法
 */
public class Desk {
    //  定义一个存储包子的集合 存储包子
    private List<String> list = new ArrayList<>();

    // 定义一个厨师生产包子的方法
    public synchronized void put()  {

        try{
            //获取厨师线程对象的名称
            String name = Thread.currentThread().getName();
            //判断有没有包子
            if(list.size()==0){//没有包子 厨师要做包子
                list.add(name+"做的包子..");//生产包子 把包子放到集合中
                System.out.println("厨师:"+name+"正在做包子.....");
                //模拟做包子的时间
                Thread.sleep(2000);
                //等待和唤醒方法 一般采用共享资源进行调用
                //包子做完了
                this.notifyAll();//唤醒所有的吃货
                this.wait();//厨师进入休息 等待
            }else {
//                this.notifyAll();//唤醒所有的吃货
                this.wait();//厨师进入休息 等待 因为桌子上有包子了。
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }


    }
    //定义一个 吃货 吃包子的方法
    public synchronized  void get() {

        try{
            //获取吃货的名称
            String name = Thread.currentThread().getName();
            //先判断有没有包子
            if(list.size()==1){
                //模拟吃包子
                System.out.println("吃货:"+name+"正在吃"+list.get(0));
                list.clear();//清空
                Thread.sleep(1500);
                //吃饭之后
                //唤醒厨师
                this.notifyAll();
                //吃货休息
                this.wait();

            }else {
                //唤醒厨师
//                this.notifyAll();
                //吃货休息
                this.wait();
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }

}


---------------------
package com.itheima.day12.teacher.wait_notify;

public class QingFengBaoZiPu {

    public static void main(String[] args) {
        //开始准备吃包子
        System.out.println("进入庆丰包子铺 坐了下来 点餐");

        /*
          new Thread().start() 是开启一个新的线程

          new Thread(线程任务,线程名字).start()
             给新的线程绑定一个线程任务和线程的名字


         new Thread(()->{},线程名字).start()
           因为线程任务是 函数式接口 所以可以使用 lambda去表达
           ()->{
                //厨师要生产包子
                while(true){
                    desk.put();
                }
           }
           厨师生产包子  只要包子没有了就可以去生产  所以写了一个循环

           ()->{
                //吃货要次包子
                while(true){
                    desk.get();
                }
           }
           吃货吃包子 只要包子还有 就可以吃  所以也写了一个循环

         */


        //创建共享资源
        Desk desk = new Desk();

        //有三个厨师  线程
        new Thread(()->{
            //厨师要生产包子
            while(true){
                desk.put();
            }
        },"霍大厨").start();
        new Thread(()->{
            //厨师要生产包子
            while(true){
                desk.put();
            }
        },"雷大厨").start();
        new Thread(()->{
            //厨师要生产包子
            while(true){
                desk.put();
            }
        },"卧龙凤厨").start();
        //有两个吃货 线程
        new Thread(()->{
            //吃货要次包子
            while(true){
                desk.get();
            }
        },"乐吃货").start();
        new Thread(()->{
            //吃货要次包子
            while(true){
                desk.get();
            }
        },"李逵吃货").start();
    }
}

**7.**线程池

1.什么是线程池?

线程池就是一个可以复用线程的技术。

2.如何创建线程池?

方式一:使用ExecutorService的实现类ThreadPoolExecutor创建一个线程池对象。

java 复制代码
package com.itheima.day12.teacher.threadpool;

public class CuoZao implements Runnable{
    @Override
    public void run() {
        //任务是搓澡
        String name = Thread.currentThread().getName();

        System.out.println("号码为:"+name+"的师傅正在给客人搓澡=====盐搓---奶搓---醋搓--");

        //模拟搓澡时间
        try {
            //5秒搓一个
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


--------------
package com.itheima.day12.teacher.threadpool;

import java.util.concurrent.*;

public class Demo {

    public static void main(String[] args) throws InterruptedException {
        // 先构建一个 线程池对象
        ExecutorService pool = new ThreadPoolExecutor(
                3,//核心线程数量
                5,// 最大线程数量 = 核心线程数量+临时线程数量;
                8,//临时存活时间  时间的数量
                TimeUnit.SECONDS,// 超过核心现场后 如果有线程超过8秒中没有被使用 就销毁掉
                new ArrayBlockingQueue<>(4),//指定任务队列 任务阻塞队列  阻塞长度是4
                Executors.defaultThreadFactory(),//用户创建线程对象的工程对象 固定代码
                new ThreadPoolExecutor.CallerRunsPolicy());//任务拒绝策略 四个 我选取最后 忙不过来 找外援
        // 线程池  澡堂
        //  核心线程数量  老板招聘的 三个搓澡师傅
        //执行搓澡任务
        CuoZao cz = new CuoZao();
        //来一个顾客 搓一个顾客
        //接客
        pool.execute(cz);//核心
        pool.execute(cz);//核心
        pool.execute(cz);//核心
        //三个客人

        pool.execute(cz);//第四个客人 先等待了  核心线程为他服务
        pool.execute(cz);//第五个客人
        pool.execute(cz);//第六个客人
        pool.execute(cz);//第七个客人
        // 7-3 = 4

        pool.execute(cz);//第八个客人 触发了 招聘临时工  阻塞队列4 一旦超出阻塞队列 就增派人手
        pool.execute(cz);//第九个客人                  阻塞队列4  超出阻塞队列两个 增派两个人手
        // 第九个客人 已经有五个搓澡师傅 已经达到 最大线程数量

        pool.execute(cz);//第十个客人   阻塞队列满了 超出阻塞队列的 用两个人手  但是还少一个
        // 这个时候拒绝策略 -- 增派人手  main来处理。。。

        Thread.sleep(20000);//时间过了十一秒 没有新的任务 肯定有线程没有处理任务的 这种任务就会销毁
        System.out.println("至少空闲了12秒 已经有被销毁的线程了...销毁之后 ");

        pool.execute(cz);
        pool.execute(cz);
        pool.execute(cz);

        pool.execute(cz);
        pool.execute(cz);

        pool.shutdown();//都搓完了 把 澡堂关闭
//        pool.shutdownNow();//里面关闭 没搓完的任务回到队列中

         // 临时线程什么时候创建?  核心线程忙 + 任务队列满了。可以创建临时线程。
        // 如果临时线程也满了,触发了拒绝策略,
                        //  可能1: 找主线程帮忙处理。
                        //   可能2: 抛弃 说声对不起  异常
                       //    可能3: 抛弃 什么也不说
                       //    可能4: 抛弃对头  放入新的
    }
}

submit方法:

java 复制代码
package com.itheima.day12.teacher.threadpool;

import java.util.concurrent.Callable;

public class Download implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"正在下载程序.....");
        return "任务下载完成";
    }
}


--------------------
package com.itheima.day12.teacher.threadpool;

import java.util.concurrent.*;

public class XunLei {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(
                3,//核心线程数量
                5,// 最大线程数量 = 核心线程数量+临时线程数量;
                8,//临时存活时间  时间的数量
                TimeUnit.SECONDS,// 超过核心现场后 如果有线程超过8秒中没有被使用 就销毁掉
                new ArrayBlockingQueue<>(4),//指定任务队列 任务阻塞队列  阻塞长度是4
                Executors.defaultThreadFactory(),//用户创建线程对象的工程对象 固定代码
                new ThreadPoolExecutor.CallerRunsPolicy());//任务拒绝策略 四个 我选取最

            //线程池 处理 带返回值的任务
        Download d = new Download();//下载任务
        Future<String> f1 = pool.submit(d);
        pool.submit(d);
        pool.submit(d);
        pool.submit(d);
        pool.submit(d);

        System.out.println(f1.get());

    }
}

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

线程池的注意事项:

1、临时线程什么时候创建?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

2、什么时候会开始拒绝新任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

3.使用ExecutorService线程池对象的常用方法

void execute(Runnable command)

4.新任务拒绝策略

5.线程池处理Callable任务

使用ExecutorService线程池对象的常用方法---Future<T> submit(Callable<T> task)

6.Executors工具类实现线程池

是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

注意 :这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。

7.Executors使用可能存在的陷阱

大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

java 复制代码
package com.itheima.day12.teacher.threadpool;

import java.util.concurrent.Callable;

public class Download implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"正在下载程序.....");
        return "任务下载完成";
    }
}


-------------------
package com.itheima.day12.teacher.threadpool;

import java.util.concurrent.*;

public class XunLei2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(3);

            //线程池 处理 带返回值的任务
        Download d = new Download();//下载任务
        Future<String> f1 = pool.submit(d);
        pool.submit(d);
        pool.submit(d);
        pool.submit(d);
        pool.submit(d);

        System.out.println(f1.get());

    }
}

8.其它细节知识:并发、并行

1.进程:

正在运行的程序(软件)就是一个独立的进程。

线程是属于进程的,一个进程中可以同时运行很多个线程。

进程中的多个线程其实是并发和并行执行的。

2.并发的含义

进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

3.并行的理解

在同一个时刻上,同时有多个线程在被CPU调度执行。

4.多线程是怎么执行的?

并发和并行同时进行的

9.其它细节知识:线程的生命周期

1.线程的6种状态

2.线程6中状态互相转换

相关推荐
陌上花开࿈1 小时前
调用第三方接口
java
Aileen_0v01 小时前
【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】
android·java·人工智能·云计算·ocr·腾讯云·玩转腾讯云ocr
西猫雷婶3 小时前
python学opencv|读取图像(十九)使用cv2.rectangle()绘制矩形
开发语言·python·opencv
桂月二二3 小时前
Java与容器化:如何使用Docker和Kubernetes优化Java应用的部署
java·docker·kubernetes
liuxin334455663 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
码农W3 小时前
QT--静态插件、动态插件
开发语言·qt
ke_wu3 小时前
结构型设计模式
开发语言·设计模式·组合模式·简单工厂模式·工厂方法模式·抽象工厂模式·装饰器模式
小马爱打代码3 小时前
设计模式详解(建造者模式)
java·设计模式·建造者模式
code04号4 小时前
python脚本:批量提取excel数据
开发语言·python·excel
小王爱吃月亮糖4 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript