Java基础学习 ---多线程

144 阅读20分钟

多线程

Java基础学习---点击进入学习

640 (7).jpg

多线程

1. 相关名词解释

1.1 程序和进程

程序:一系列代码指令的集合,软件属于程序的表现方式之一。是一种静态的表现形式。

进程:进行中的应用程序,属于一种动态的表现形式。

进程拥有独立的内存资源和CPU运算资源等等。

1.2 线程

线程属于CPU调度执行、运算的最小单位。

1.3 进程和线程的关系

线程是包含在进程之中的,一个进程至少包含一个线程,否则将无法执行。

1.4 线程的数量

线程不是越多越好,要结合实际的硬件环境,合适的才是最好的。

2. 线程的执行

单核心CPU下,多个线程随机轮流交替执行,因为切换的频率非常快,所以宏观上是同时执行的,微观是轮流交替执行的。

3. 并发和并行

并发:同时发生,轮流交替执行。

并行:真正意义上的同时执行。

4. 创建线程方式

调用start()方法 和 调用run()方法的区别?

​ 调用start()方法会开启新的线程

​ 调用run()方法 不会开启新的线程

4.1 继承Thread类

创建线程方式1:继承Thread类 重写run方法

run方法内为当前线程要执行的代码


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 10:58
 *  创建线程方式1:继承Thread类 重写run方法
 *  run方法内为当前线程要执行的代码
 *
 *  调用start()方法 和 调用run()方法的区别?
 *      调用start()方法会开启新的线程
 *      调用run()方法 不会开启新的线程
 *
 */
public class MyThread1 extends Thread{
    @Override
    public void run() {
        for(int i = 0;i < 20;i++){
            System.out.println(Thread.currentThread().getName() + "线程执行了" + i);
        }
    }

    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();

        myThread1.setName("线程A");

        myThread1.start();


        MyThread1 myThread11 = new MyThread1();

        myThread11.setName("-----线程B-----");

        myThread11.start();







    }

}

4.2 实现Runnable接口

创建线程方式2:实现Runnable接口 重写run方法


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 11:04
 *  创建线程方式2:实现Runnable接口 重写run方法
 */
public class MyThread2 implements  Runnable{
    @Override
    public void run() {
        for(int i = 0;i < 20;i++){
            System.out.println(Thread.currentThread().getName() + "线程执行了" + i);
        }
    }

    public static void main(String[] args) {
        MyThread2 runnable = new MyThread2();

        Thread thread = new Thread(runnable);

        thread.setName("___线程A___");

        thread.start();

        Thread thread1 = new Thread(runnable);

        thread1.setName("******线程B******");

        thread1.start();

    }
}

5. 线程的状态

线程的状态:创建 就绪 运行 阻塞 运行 死亡


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 14:00
 *  线程的状态:创建  就绪   运行   阻塞  运行  死亡
 */
public class TestThreadStatus extends Thread{
    @Override
    public void run() { // 运行
        System.out.println(Thread.currentThread().getName() + "开始执行");

        try {
            Thread.sleep(3000); // 阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

        System.out.println(Thread.currentThread().getName() + "执行完毕");
    } // 死亡

    public static void main(String[] args) {
        TestThreadStatus thread = new TestThreadStatus(); // 创建

        thread.setName("线程A");

        thread.start(); // 就绪
    }
}

6. 线程调度常用的方法

6.1 线程优先级

线程优先级:1最低 10最高 默认为5 优先级高的线程获取CPU资源的概率较大 但并不能保证一定会优先执行

setPriority(int newPriority) 设置优先级 可以传入1~10之间的整数

也可以写静态常量 MAX_PRIORITY MIN_PRIORITY NORM_PRIORITY

getPriority() 获取优先级


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 14:10
 *  线程优先级:1最低 10最高 默认为5 优先级高的线程获取CPU资源的概率较大 但并不能保证一定会优先执行
 *
 *  setPriority(int newPriority) 设置优先级 可以传入1~10之间的整数
 *      也可以写静态常量  MAX_PRIORITY   MIN_PRIORITY    NORM_PRIORITY
 *  getPriority() 获取优先级
 */
public class TestThreadPriority extends Thread{
    @Override
    public void run() {
        for (int i = 1;i <= 10;i++){
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }

    public static void main(String[] args) {
        TestThreadPriority th1 = new TestThreadPriority();

        TestThreadPriority th2 = new TestThreadPriority();

        th1.setPriority(MAX_PRIORITY); // 10

        th2.setPriority(1); // MIN_PRIORITY

        th1.setName("线程A");

        th2.setName("*****线程B*****");

        th1.start();
        th2.start();

    }
}

6.2 线程休眠

sleep(long millis) 休眠指定的时间 时间过后自动恢复继续执行


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 14:00
 * 
 */
public class TestThreadStatus extends Thread{
    @Override
    public void run() { 
        System.out.println(Thread.currentThread().getName() + "开始执行");
        try {
            Thread.sleep(3000); // 休眠3秒钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i = 0;i <10;i++){
            System.out.println(Thread.currentThread().getName() + "===" + i);
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    } 

    public static void main(String[] args) {
        TestThreadStatus thread = new TestThreadStatus(); 

        thread.setName("线程A");

        thread.start(); 
    }
}

6.3 线程插队

线程插队:等待插队线程执行完毕 或者 执行指定的时间 之后 被插队线程再执行

join() 等待这个线程死亡。

join(long millis) 等待这个线程死亡最多 millis毫秒。

join(long millis, int nanos) 等待最多 millis毫秒加上 nanos纳秒这个线程死亡。


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 14:19
 *  线程插队:等待插队线程执行完毕 或者 执行指定的时间 之后 被插队线程再执行
 *      join()  等待这个线程死亡。
 *      join(long millis) 等待这个线程死亡最多 millis毫秒。
 *      join(long millis, int nanos) 等待最多 millis毫秒加上 nanos纳秒这个线程死亡。
 */
public class TestThreadJoin extends Thread{
    @Override
    public void run() {
        for(int i = 1;i <= 50;i++){
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "====" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestThreadJoin th1 = new TestThreadJoin();
        th1.setName("------线程A------");
        th1.start();


        for(int i = 1;i < 20;i++){
            System.out.println(Thread.currentThread().getName() + "****" + i);
            Thread.sleep(20); // 每次执行休眠200毫秒
            if(i == 10){
                th1.join(200,5686); // 让th1线程插队 直到 th1线程执行完毕
            }
        }

    }




}

6.4 线程礼让

线程的礼让: 表示当前线程向调度器发出信息,愿意做出让步,但是调度器可以忽略这一点。

static yield()


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 14:31
 *  线程的礼让: 表示当前线程向调度器发出信息,愿意做出让步,但是调度器可以忽略这一点。
 *
 *  static yield()
 */
public class TestThreadYield extends Thread{
    @Override
    public void run() {
        for(int i = 1;i <= 20;i++){
            if(i == 5){
                System.out.println("线程礼让");
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }

    public static void main(String[] args) {
        TestThreadYield th1 = new TestThreadYield();
        TestThreadYield th2 = new TestThreadYield();

        th1.setName("线程A");
        th2.setName("****线程B****");


        th1.start();
        th2.start();


    }


}

7. 同步关键字

多个并发线程访问同一资源的同步代码块时 ​ 同一时刻只能有一个线程进入synchronized(this)同步代码块 ​ 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定 ​ 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码

synchronized 关键字 同步锁

可以用于

​ 修饰代码块 : 表示同一时间只能有一个线程执行此代码块

​ 修饰方法 : 表示同一时间只能有一个线程执行此方法

关于同步代码块中书写的this关键字:

​ this表示当前对象 如果需要同步效果 那么必须保证多个线程 锁定是同一个对象才可以

​ 否则将不会有同步效果 因为 Runnable实现类只需要创建一个对象 可以传入不同的多个线程对象中

​ 所以直接写this比较方便 相当于多个线程对象 共享同一个 Runnable实现类对象

同步代码块实现线程安全:


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 15:35
 *  使用多线程模拟多人抢票 : 三个人抢10张票
 *
 *  synchronized 关键字 同步锁
 *  可以用于
 *      修饰代码块 : 表示同一时间只能有一个线程执行此代码块
 *      修饰方法 : 表示同一时间只能有一个线程执行此方法
 *
 *  关于同步代码块中书写的this关键字:
 *  this表示当前对象 如果需要同步效果 那么必须保证多个线程 锁定是同一个对象才可以
 *  否则将不会有同步效果 因为 Runnable实现类只需要创建一个对象 可以传入不同的多个线程对象中
 *  所以直接写this比较方便 相当于多个线程对象 共享同一个 Runnable实现类对象
 *
 *
 */
public class BuyTicket2 implements Runnable{
    int ticketCount = 10;


    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this){
                if(ticketCount == 0){
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "抢到了第"+ ( 10 - ticketCount) +"张票,还剩余"+ ticketCount +"张票");
            }
        }
        System.out.println("票已售完");
    }
    public static void main(String[] args) {
        BuyTicket2 runnable = new BuyTicket2();

        Thread th1 = new Thread(runnable,"赵四");
        Thread th2 = new Thread(runnable,"大拿");
        Thread th3 = new Thread(runnable,"小宝");

        th1.start();
        th2.start();
        th3.start();

    }
}

同步方法实现线程安全:

/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 15:35
 * 使用多线程模拟多人抢票 : 三个人抢10张票
 * <p>
 * synchronized 关键字 同步锁
 * 可以用于
 * 修饰代码块 : 表示同一时间只能有一个线程执行此代码块
 * 修饰方法 : 表示同一时间只能有一个线程执行此方法
 */
public class BuyTicket3 implements Runnable {
    int ticketCount = 10;

    @Override
    public synchronized void run() {
        while (ticketCount > 0) {
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "抢到了第" + (10 - ticketCount) + "张票,还剩余" + ticketCount + "张票");
        }
        System.out.println("票已售完");
    }

    public static void main(String[] args) {
        BuyTicket3 runnable = new BuyTicket3();

        Thread th1 = new Thread(runnable, "赵四");
        Thread th2 = new Thread(runnable, "大拿");
        Thread th3 = new Thread(runnable, "小宝");

        th1.start();
        th2.start();
        th3.start();

    }
}

8. 线程的通信-生产者消费者模式

线程之间的通信:生产者消费者模式

线程通信:多个线程之间数据的传输 称之为线程通信

生产者消费者模式:不属于设计模式 属于线程通信过程中非常常见的一个场景 一种现象

具体要求:

1.生产什么 消费什么

2.持续生产 持续消费 (没有生产 不能消费)

3.保证产品的完整性 (不能消费半个产品 或者 残缺的产品)

4.不能重复消费同一个产品

Object类中的两个方法 wait()方法 和 notify()方法

wait()方法 让当前线程等待 会释放锁

notify() 方法唤醒等待的线程


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 9:08
 *  台式电脑类 产品类
 *      属性:主机 显示器
 */
public class Computer {
    private String mainFrame;
    private String screen;

    private boolean flag; // false 表示可以生产 不能消费  true 可以消费 不能生产

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public String getMainFrame() {
        return mainFrame;
    }

    public void setMainFrame(String mainFrame) {
        this.mainFrame = mainFrame;
    }

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public Computer() {
    }

    public Computer(String mainFrame, String screen) {
        this.mainFrame = mainFrame;
        this.screen = screen;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "mainFrame='" + mainFrame + '\'' +
                ", screen='" + screen + '\'' +
                '}';
    }
}


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 9:09
 *  消费者线程
 */
public class Consumer extends Thread{
    private Computer computer;

    public Consumer(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void run() {
        for(int i = 1;i <= 20;i++){
            synchronized (computer){
                if(!computer.isFlag()){
                    try {
                        computer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(computer.getMainFrame() + "==" + computer.getScreen());

                computer.setFlag(false);
                computer.notify();
            }

        }
    }
}


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 9:09
 *  生产者 线程
 */
public class Producer extends Thread{
    private Computer computer;

    public Producer(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void run() {
        for(int i = 1;i <= 20;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (computer){
                // 先判断是否需要等待
                if(computer.isFlag()){
                    try {
                        computer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(i % 2 == 0){
                    computer.setMainFrame(i + "号联想主机");
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    computer.setScreen(i + "号联想显示器");
                }else{
                    computer.setMainFrame(i + "号华硕主机");
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    computer.setScreen(i + "号华硕显示器");
                }

                computer.setFlag(true);
                computer.notify();
            }

        }
    }
}


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 9:01
 *
 *  线程之间的通信:生产者消费者模式
 *
 *  线程通信:多个线程之间数据的传输 称之为线程通信
 *
 *  生产者消费者模式:不属于设计模式 属于线程通信过程中非常常见的一个场景 一种现象
 *
 *  具体要求:
 *      1.生产什么 消费什么
 *      2.持续生产 持续消费 (没有生产 不能消费)
 *      3.保证产品的完整性 (不能消费半个产品 或者 残缺的产品)
 *      4.不能重复消费同一个产品
 *
 * Object类中的两个方法 wait()方法 和 notify()方法
 *
 *  wait()方法 让当前线程等待  会释放锁
 *  notify() 方法唤醒等待的线程
 *
 */
public class Test {
    public static void main(String[] args) {

        Computer computer = new Computer();

        Producer producer = new Producer(computer);

        Consumer consumer = new Consumer(computer);

        producer.start();

        consumer.start();


    }
}

队列实现生产者消费者模式


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 9:47
 */
public class Computer {
    private String mainFrame;
    private String screen;

    public String getMainFrame() {
        return mainFrame;
    }

    public void setMainFrame(String mainFrame) {
        this.mainFrame = mainFrame;
    }

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public Computer() {
    }

    public Computer(String mainFrame, String screen) {
        this.mainFrame = mainFrame;
        this.screen = screen;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "mainFrame='" + mainFrame + '\'' +
                ", screen='" + screen + '\'' +
                '}';
    }
}


import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 9:50
 */
public class Consumer extends Thread{
    private ArrayBlockingQueue<Computer> queue;

    public Consumer(ArrayBlockingQueue<Computer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i = 1;i <= 20;i++){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                System.out.println(queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 9:48
 *  生产者线程
 */
public class Producer extends Thread{
    private ArrayBlockingQueue<Computer> queue;

    public Producer(ArrayBlockingQueue<Computer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for(int i = 1;i <= 20;i++){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Computer computer = new Computer();
            if(i % 2 == 0){
                computer.setMainFrame(i + "号联想主机");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                computer.setScreen(i + "号联想显示器");
                System.out.println("生产了第" + i  +"号联想电脑");
            }else{
                computer.setMainFrame(i + "号华硕主机");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                computer.setScreen(i + "号华硕显示器");
                System.out.println("生产了第" + i  +"号华硕电脑");
            }

            try {
                queue.put(computer);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 9:52
 */
public class Test {
    public static void main(String[] args) {
        ArrayBlockingQueue<Computer> queue = new ArrayBlockingQueue<>(10);

        Producer producer = new Producer(queue);

        Consumer consumer = new Consumer(queue);

        producer.start();

        consumer.start();

    }
}

9.wait和sleep的区别?

面试题:wait()方法 和 sleep()方法的区别?

1.所属类不同:

​ wait方法属于Object sleep方法属于Thread类

2.是否会释放锁对象不同:

​ sleep不会释放锁对象 属于获取了CPU资源 持续占用资源 在释放锁对象之前 其他线程不能再次获取同一个锁 对象

​ wait会释放锁对象 在当前线程wait期间 其他线程可以再次获取同一个锁对象

3.使用方式不同:

​ wait方法必须结合同步代码快使用 否则报异常 IllegalMonitorStateException

​ sleep方法可以单独使用

4.状态不同:

​ 调用wait方法 当前线程会进入WAITING状态

​ 调用sleep方法 当前线程会进入 TIMED_WAITING

5.唤醒方式:

​ sleep方法属于自动唤醒

​ 无参wait方法需要调用notify或者notifyAll方法唤醒


import java.time.LocalDateTime;

/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 10:43
 *  面试题:wait()方法 和 sleep()方法的区别?
 *      1.所属类不同:
 *          wait方法属于Object  sleep方法属于Thread类
 *      2.是否会释放锁对象不同:
 *          sleep不会释放锁对象 属于获取了CPU资源 持续占用资源 在释放锁对象之前 其他线程不能再次获取同一个锁对象
 *          wait会释放锁对象 在当前线程wait期间 其他线程可以再次获取同一个锁对象
 *      3.使用方式不同:
 *          wait方法必须结合同步代码快使用 否则报异常 IllegalMonitorStateException
 *          sleep方法可以单独使用
 *      4.状态不同:
 *          调用wait方法 当前线程会进入WAITING状态
 *          调用sleep方法 当前线程会进入 TIMED_WAITING
 *
 */
public class TestWaitSleep {
    public static void main(String[] args) throws InterruptedException {
        // 锁对象
        Object obj = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
//               synchronized (obj){
                   System.out.println(Thread.currentThread().getName() + "获得了锁对象");
                   try {
                       obj.wait(2000);
//                       Thread.sleep(2000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName() + "释放锁对象");
//               }
            }
        }).start();


        // 主线程休眠200毫秒
        Thread.sleep(200);

        synchronized (obj){
            System.out.println(Thread.currentThread().getName() + "获取了锁对象" + LocalDateTime.now());
        }
        System.out.println(Thread.currentThread().getName() + "释放锁对象" + LocalDateTime.now());




    }
}

调用wait和sleep会进入不同的状态


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 10:58
 */
public class TestThreadState {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (this){
                        this.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t2.start();

        System.out.println("sleep()方法进入状态:" + t1.getState());
        System.out.println("wait()方法进入状态:" + t2.getState());


    }
}

10.其他创建线程的方式

10.1 Callable接口方式

回顾我们之前创建线程的两种方式(继承Thread和实现Runnable接口),存在如下问题:

因为Thread也实现了Runnable接口 所以 这两种方式 本质是相同的 不同点在于 一个是继承父类 一个是实现接口

因为这两种方式都是重写run方法 所以

1.不能有返回值 因为run方法定义为void

2.不能声明任何异常 因为run方法没有声明任何异常

Callable 接口是JDK1.5新增的接口 可以给线程添加返回值

FutureTask构造方法支持传入一个Callable接口实现类

FutureTask类 实现类 RunnableFuture接口

RunnableFuture接口 继承自 Runnable接口

所以FutureTask对象就是Runnable实现类 可以作为参数传入到Thread对象中


import com.atguigu.test1.Test;

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

/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 11:06
 *  回顾我们之前创建线程的两种方式(继承Thread和实现Runnable接口),存在如下问题:
 *  因为Thread也实现了Runnable接口 所以 这两种方式 本质是相同的 不同点在于 一个是继承父类 一个是实现接口
 *  因为这两种方式都是重写run方法 所以
 *      1.不能有返回值    因为run方法定义为void
 *      2.不能声明任何异常  因为run方法没有声明任何异常
 *
 *
 *  Callable 接口是JDK1.5新增的接口 可以给线程添加返回值
 *
 *  FutureTask构造方法支持传入一个Callable接口实现类
 *
 *  FutureTask接口 继承自 RunnableFuture接口
 *  RunnableFuture接口 继承自 Runnable接口
 *  所以FutureTask对象就是Runnable实现类  可以作为参数传入到Thread对象中
 *
 *
 */
public class TestCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= 10;i++){
            sum += i;
        }
        System.out.println("call方法最终返回值为: " +sum);
        return sum;
    }

    public static void main(String[] args) throws Exception {
        TestCallable testCallable = new TestCallable();

        FutureTask<Integer> task = new FutureTask<>(testCallable);

        Thread thread = new Thread(task);

        thread.start();

        Integer result = task.get();

        System.out.println("result = " + result);
    }
}

10.2 线程池方式

回顾我们之前创建线程的三种方式,存在如下问题:

1.在多线程场景中,会频繁的创建以及销毁线程对象,浪费系统资源

2.之前创建的线程不能进行统一管理

3.不能执行定时任务

线程池 : 线程池属于对多个线程对象统一管理的一个容器,线程池中的线程对象,使用完毕之后,根据当前线程池设定的机制,不会理解被垃圾收集器回收,而是归还到池中,等待下次使用的时候,继续取出使用。

java.util.concurrent.Executors 类 线程池工具类

newCachedThreadPool() 根据需求创建多个线程对象的线程池

newFixedThreadPool(int nThreads) 根据指定的线程数量创建线程池

newScheduledThreadPool(int corePoolSize)创建一个开源执行定时任务的线程池

newSingleThreadExecutor() 创建只包含一个线程对象的线程池


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

/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 14:10
 *  回顾我们之前创建线程的三种方式,存在如下问题:
 *      1.在多线程场景中,会频繁的创建以及销毁线程对象,浪费系统资源
 *      2.之前创建的线程不能进行统一管理
 *      3.不能执行定时任务
 *
 *
 *  线程池 : 线程池属于对多个线程对象统一管理的一个容器,线程池中的线程对象,使用完毕之后,根据当前线程池设定的
 *  机制,不会理解被垃圾收集器回收,而是归还到池中,等待下次使用的时候,继续取出使用。
 *
 *  java.util.concurrent.Executors  类 线程池工具类
 *
 *  newCachedThreadPool()  根据需求创建多个线程对象的线程池
 *  newFixedThreadPool(int nThreads) 根据指定的线程数量创建线程池
 *  newScheduledThreadPool(int corePoolSize)创建一个开源执行定时任务的线程池
 *  newSingleThreadExecutor() 创建只包含一个线程对象的线程池
 *
 */
public class TestThreadPool {
    public static void main(String[] args) {
        ExecutorService es1 = Executors.newCachedThreadPool();

        es1.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行了");
            }
        });



        es1.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行了");
            }
        });


        ExecutorService es2 = Executors.newFixedThreadPool(5);

        es2.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行了");
            }
        });


        es2.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行了");
            }
        });


        ScheduledExecutorService es3 = Executors.newScheduledThreadPool(10);

        es3.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "延迟执行了");
            }
        }, 5, TimeUnit.SECONDS);


        ExecutorService es4 = Executors.newSingleThreadExecutor();

        es4.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "执行了");
            }
        });


    }
}

11.懒汉单例线程安全问题

之前编写的懒汉单例模式适用于单线程场景。

如果是在多线程场景下,需要做出如下改进,使用双重检查机制实现。

​ 第一重检查:是为了避免无效的排队,浪费时间。

​ 第二重检查:是为了线程安全


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 14:38
 *  懒汉单例
 */
public class LazySingleton {

    private static LazySingleton instance = null;


    private LazySingleton(){}

    public static synchronized LazySingleton getInstance1() throws InterruptedException {
        // 逻辑代码1
        // 逻辑代码2
        // 逻辑代码3
        if(instance == null){
            
            Thread.sleep(1000);
            instance = new LazySingleton();
        }
        return instance;
    }

    public static LazySingleton getInstance2() throws InterruptedException {
        // 逻辑代码1
        // 逻辑代码2
        // 逻辑代码3
        // 当任何类被类加载器加载  JVM会自动创建此类的对应 Class对象 Class<LazySingleton>
        // 此对象只会被创建一次 所以只有一份 可以保证多个线程获取的是同一个锁对象
        synchronized (LazySingleton.class) {
            if(instance == null){
                Thread.sleep(1000);
                instance = new LazySingleton();
            }
        }
        return instance;
    }

    /**
     *  双重检查懒汉单例
     * @return
     * @throws InterruptedException
     */
    public static LazySingleton getInstance3() throws InterruptedException {
        // 逻辑代码1
        // 逻辑代码2
        // 逻辑代码3
        // 当任何类被类加载器加载  JVM会自动创建此类的对应 Class对象 Class<LazySingleton>
        // 此对象只会被创建一次 所以只有一份 可以保证多个线程获取的是同一个锁对象
        if(instance == null){
            synchronized (LazySingleton.class) {
                if(instance == null){
                    Thread.sleep(1000);
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

12.锁介绍

Lock接口实现线程安全抢票


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

/**
 * @author WHD
 * @description TODO
 * @date 2023/11/28 15:35
 * 使用多线程模拟多人抢票 : 三个人抢10张票
 * 使用Lock接口 以及 其实现类 ReentrantLock(可重入锁实现线程安全)
 */
public class BuyTicket implements Runnable {
    int ticketCount = 10;
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                lock.lock();
                if (ticketCount == 0) {
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "抢到了第" + (10 - ticketCount) + "张票,还剩余" + ticketCount + "张票");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        System.out.println("票已售完");
    }

    public static void main(String[] args) {
        BuyTicket runnable = new BuyTicket();

        Thread th1 = new Thread(runnable, "赵四");
        Thread th2 = new Thread(runnable, "大拿");
        Thread th3 = new Thread(runnable, "小宝");

        th1.start();
        th2.start();
        th3.start();

    }
}

锁的介绍

​ 乐观锁 : 自旋锁 (无锁 CAS Compare And Swap)

​ 悲观锁 : synchronized

​ 轻量级锁

​ 重量级锁 : synchronized

​ 公平锁

​ 非公平锁 : synchronized

​ 可重入锁:同一个线程对象重复的获得同一个锁对象 不应该产生死锁 synchronized属于可重入锁

​ 死锁:死锁是因为逻辑错误导致的多个线程竞争同一个资源 僵持不下


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 15:30
 *  锁的介绍
 *      乐观锁 : 自旋锁 (无锁 CAS Compare And Swap)
 *      悲观锁 : synchronized
 *
 *      轻量级锁
 *      重量级锁 : synchronized
 *
 *      公平锁
 *      非公平锁 : synchronized
 *
 *  可重入锁:同一个线程对象重复的获得同一个锁对象 不应该产生死锁  synchronized属于可重入锁
 *  死锁:死锁是因为逻辑错误导致的多个线程竞争同一个资源 僵持不下
 */
public class Note {
    public void m1(){
        synchronized (this){
            System.out.println("同步代码块1");
            synchronized (this){
                System.out.println("同步代码块2");
            }
        }
    }

    public static void main(String[] args) {
        Note note = new Note();
        note.m1();
    }

}

自实现可重入锁


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 15:50
 */
public class TestReentrantLock {
    private static ReentrantLock lock = new ReentrantLock();
    public static void m1() throws InterruptedException {
        System.out.println("m1 method start");
        lock.lock();
        m2();
        lock.unlock();
        System.out.println("m1 method end");
    }

    public static void m2() throws InterruptedException {
        System.out.println("m2 method start");
        lock.lock();
        lock.unlock();
        System.out.println("m2 method end");
    }

    public static void main(String[] args) throws InterruptedException {
        m1();
    }
}

class ReentrantLock{
    private boolean isLocked; // 是否上锁
    private int lockedCount; // 上锁次数
    private Thread currentLockedThread; // 当前上锁对象

    public synchronized void lock() throws InterruptedException {
        Thread thread = Thread.currentThread();
        if(isLocked && currentLockedThread != thread){
            this.wait();
        }
        isLocked = true;
        lockedCount++;
        currentLockedThread = thread;

    }

    public synchronized  void unlock(){
        Thread thread = Thread.currentThread();

        if(currentLockedThread == thread){
            lockedCount--;
            if(lockedCount == 0){
                notify();
                isLocked = false;
            }
        }

    }
}

自实现不可重入锁


/**
 * @author WHD
 * @description TODO
 * @date 2023/11/29 15:44
 */
public class TestUnReentrantLock {
    private static UnReentrantLock lock = new UnReentrantLock();
    public static void m1() throws InterruptedException {
        System.out.println("m1 method start");
        lock.lock();
        m2();
        lock.unlock();
        System.out.println("m1 method end");
    }

    public static void m2() throws InterruptedException {
        System.out.println("m2 method start");
        lock.lock();
        lock.unlock();
        System.out.println("m2 method end");
    }

    public static void main(String[] args) throws InterruptedException {
        m1();
    }

}

class UnReentrantLock{
    private boolean isLocked;

    public synchronized  void lock() throws InterruptedException {
        if(isLocked){
            this.wait();
        }
        isLocked = true;
    }

    public synchronized  void unlock(){
        this.notify();
        isLocked = false;
    }

}