多线程&JUC

文章目录

  • 进程与线程
  • 多线程的两个概念
  • 多线程的实现方式
    • 1. 继承Thread类的方式进行实现
    • 2. 实现Runnable接口的方式进行实现
    • 3. 利用Callable接口和Future接口方式实现
  • Thread常见的成员方法
  • 线程的生命周期
  • 线程安全的问题
    • 同步代码块
    • 同步方法
  • Lock锁
  • 死锁
  • 生产者和消费者(等待唤醒机制)
    • 生产者消费者常见方法
    • 生产者消费者模式代码实现
    • 等待唤醒机制(阻塞队列方式实现)
  • 线程的状态

进程与线程

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

多线程的两个概念

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

多线程的实现方式

1. 继承Thread类的方式进行实现

2. 实现Runnable接口的方式进行实现

3. 利用Callable接口和Future接口方式实现

多线程&JUC_第1张图片

Thread常见的成员方法

多线程&JUC_第2张图片

线程优先级最小是1,最大是10,默认是5;优先级越大,抢占到CPU的概率越大。
当其他非守护线程执行完毕后,守护线程会陆续结束(当非守护线程结束了,守护线程也就没有存在的必要了,不会等到执行完守护线程才结束)。

线程的生命周期

多线程&JUC_第3张图片

线程安全的问题

练习:

需求:
某电影院目前正在上映国产大片,共有100张票,而它有三个窗口卖票,请设计一个程序模拟该电影院卖票。

代码实现:

public class MyThread extends Thread {
    // 加static表示这个类的所有对象,都共享ticket数据
    static int ticket = 0;//0~99

    @Override
    public void run() {
        while (true) {
            if (ticket < 100) {
                ticket++;
                System.out.println(getName() + "正在卖第" + ticket + "张票!");
            } else {
                break;
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

上述代码存在安全问题:

  1. 相同的票出现多次
  2. 出现超出范围的票

引发原因:
线程执行时,具有随机性。
解决思路:
把操作共享数据的代码锁起来。
解决方法:

  1. 同步代码块
  2. 同步方法

同步代码块

多线程&JUC_第4张图片
注意:synchronized的锁对象可以时任意的,但一定要是唯一的

修改上述线程类代码:

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

    @Override
    public void run() {
        while (true) {
            synchronized (object) {
                if (ticket < 100) {
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!");
                } else {
                    break;
                }
            }
        }
    }
}

锁对象也可以用MyThread.class对象,因为字节码文件也是唯一的。

同步方法

多线程&JUC_第5张图片
这里我们用第二种方式实现多线程

public class MyRunnable implements Runnable {
    int ticket = 0;

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

    private synchronized boolean method() {
        if (ticket < 100) {
            ticket++;
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!");
            return false;
        } else {
            return true;
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        Thread t3 = new Thread(myRunnable);

        // 起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

Lock锁

代码实现:

public class MyThread extends Thread {
    // 加static表示这个类的所有对象,都共享ticket数据
    static int ticket = 0;//0~99
    // 注意:采用第一种实现多线程的方式,锁对象要加static关键字
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (ticket < 100) {
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!");
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

死锁

锁的嵌套会引发死锁
多线程&JUC_第6张图片
死锁的代码实现:

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锁,顺利执行完一轮");
                    }
                }
            }

        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        // 起名字
        t1.setName("线程A");
        t2.setName("线程B");

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

执行结果:
多线程&JUC_第7张图片

生产者和消费者(等待唤醒机制)

生产者消费者模式是一个十分经典的多线程协作模式
多线程&JUC_第8张图片

多线程&JUC_第9张图片

生产者消费者常见方法

多线程&JUC_第10张图片

生产者消费者模式代码实现

用来控制生产者和消费者的执行

/**
 * @date 2023/7/22
 * @deprecated 用来控制生产者和消费者的执行
 */
public class Desk {
    /**
     * 桌子上是否有食物  0:没有食物 1:有食物
     */
    public static int foodFlag = 0;
    /**
     * 最多能吃10个
     */
    public static int count = 10;

    /**
     * 锁对象   要唯一
     */
    public static Object lock = new Object();

}

消费者代码

/**
 * @date 2023/7/22
 * @deprecated 消费者
 */
public class ConsumerThread extends Thread {

    @Override
    public void run() {
        /*
         * 1. 循环
         * 2. 同步代码块
         * 3. 判断共享数据是否到了末尾(到了末尾)
         * 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
         **/

        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    // 判断桌子上是否有食物
                    if (Desk.foodFlag == 0) {
                        // 如果没有,就等待
                        try {
                            Desk.lock.wait(); // 让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // 把吃的总数-1
                        Desk.count--;
                        // 如果有,就开吃
                        System.out.println("消费者正在吃面条,还能再吃" + Desk.count + "碗!!!");
                        // 修改桌子状态
                        Desk.foodFlag = 0;
                        // 吃完之后,唤醒生产者继续生产
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

生产者代码:

/**
 * @date 2023/7/22
 * @deprecated 生产者
 */
public class ProducerThread extends Thread{

    @Override
    public void run() {
        /*
         * 1. 循环
         * 2. 同步代码块
         * 3. 判断共享数据是否到了末尾(到了末尾)
         * 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
         **/

        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    // 判断桌子上是否有食物
                    if (Desk.foodFlag == 1) {
                        // 如果有,就等待
                        try {
                            Desk.lock.wait(); // 让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // 如果没有,就开始生产
                        System.out.println("生产者生产了一碗面条!!!");
                        // 修改桌子状态
                        Desk.foodFlag = 1;
                        // 生产完之后,唤醒消费者继续吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

启动类:

public class ThreadDemo {
    public static void main(String[] args) {
        // 创建线程对象
        ConsumerThread consumer = new ConsumerThread();
        ProducerThread producer = new ProducerThread();

        // 给线程设置名字
        producer.setName("生产者");
        consumer.setName("消费者");

        // 开启线程
        producer.start();
        consumer.start();
    }
}

等待唤醒机制(阻塞队列方式实现)

多线程&JUC_第11张图片
多线程&JUC_第12张图片
细节:生产者和消费者必须使用同一个阻塞队列; 阻塞队列的put方法和take方法中就有加锁了,所以我们不需要在使用synchronized关键字或者使用Lock锁了。

消费者代码实现:

public class ConsumerThread extends Thread {

    ArrayBlockingQueue<String> queue;

    public ConsumerThread(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            // 不断从阻塞队列中获取面条
            try {
                String take = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

生产者代码实现

public class ProducerThread extends Thread {
    ArrayBlockingQueue<String> queue;

    public ProducerThread(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                queue.put("面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

启动类代码实现:

public class ThreadDemo {

    public static void main(String[] args) {

        // 创建容量为1的阻塞队列
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        // 创建线程对象
        ConsumerThread consumerThread = new ConsumerThread(queue);
        ProducerThread producerThread = new ProducerThread(queue);

        // 开启线程
        consumerThread.start();
        producerThread.start();
    }
}

注意: 上述的生产者和消费者代码中都没有打印语句是因为阻塞队列的put和take方法中实现了加锁,如果我们写的打印语句的话,打印语句是在锁的外面de,就会有重复打印的现象。但是打印语句没有操作共享数据的话是不会有问题的。

线程的状态

多线程&JUC_第13张图片

多线程&JUC_第14张图片

你可能感兴趣的:(java)