Java JUC&多线程 基础完整版

1、 多线程的第一种启动方式之继承Thread类

优点: 比较简单,可以直接使用Thread类中的方法,缺点: 可以拓展性比较差,不能再继承其他的类

线程类MyThread

public class MyThread extends Thread {


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

执行类ThreadDemo

public class ThreadDemo {

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

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

        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }

}

2、多线程的第二种启动方式之实现Runnable接口

优点: 拓展性强,实现该接口的同时可以继承其他的类,缺点: 相对复杂,不能直接使用Thread类中的方法

线程类MyRunnable

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        // 线程要执行的代码
        for (int i = 0; i < 100; i++) {
            // 先获取当前线程的对象
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName() + "Hello World");
        }
    }
}

执行类ThreadDemo

public class ThreadDemo {

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

        // 任务对象
        MyRunnable myRun = new MyRunnable();
        // 线程对象
        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);

        // 给线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");
        // 开启线程
        t1.start();
        t2.start();
    }
}

3、多线程的第三种实现方式之实现Callable接口

优点: 拓展性强,实现该接口的同时可以继承其他的类,相对复杂,不能直接使用Thread类中的方法

线程类MyCallable

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        // 求1~100之间的和
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum = sum + i;
        }
        return sum;
    }
}

执行类ThreadDemo

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*
         * 多线程的第三种实现方式
         *   特点: 可以获取到多线程的运行结果
         *  1. 创建一个MyCallable类实现Callable接口
         *  2. 重写call (是有返回值的,表示多线程运行的结果)
         *  3. 创建MyCallable的对象 (表示多线程要执行的任务)
         *  4. 创建FutureTask的对象 (管理多线程运行的结果)
         *  5. 创建Thread类的对象 并启动(表示线程)
         */

        // 创建MyCallable对象 (表示要执行的多线程的任务)
        MyCallable mc = new MyCallable();
        // 创建FutureTask的对象 (作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        // 创建线程对象
        Thread t1 = new Thread(ft);
        // 启动线程
        t1.start();

        // 获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);

    }
}

4、多线的常用成员方法

  • String getName() 返回此线程的名称
  • void setName(String name) 设置线程名称(构造方法也可以设置名称)
    • 细节
      • 1.如果不设置线程名称,线程也是有默认序号的,从0开始格式为Thread-X
      • 2.如果给线程设置名称可以使用setName和子类的构造方法
    • 细节
      • 当jvm虚拟机启动后会自动启动多条线程,其中就有一个线程名字为main他的作用就是调用main方法,执行里面的代码
  • static void sleep(long time) 让线程休眠指定的时间, 单位为毫秒
    • 细节
      • 哪条线程执行到了这个方法,那么哪条线程就会在这里停留相对于的时间方法的参数就表示睡眠的时间,单位为毫秒时间到了后线程会自动苏醒,并继续执行

线程类MyThread

public class MyThread extends Thread {

    // 构造方式设置线程名称
    public MyThread(String name) {
        super(name);
    }

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

执行类ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        // 1.创建线程的对象
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");


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

        // 哪条线程执行到这个方法,此时获取的就是哪条线程的对象
        Thread thread = Thread.currentThread();
        System.out.println("main方法线程" + thread.getName());
    }
}

5、线程的优先级

  • setPriority(int newPriority) 设置线程优先级
  • final int getPriority() 获取线程优先级

线程类MyRunnable

public class MyRunnable implements Runnable {

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

执行类ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {
        /*
         * setPriority(int newPriority)  设置线程优先级
         * final int getPriority() 获取线程优先级
         *
         */
        // 创建线程要执行的参数对象
        MyRunnable mr = new MyRunnable();
        // 创建线程对象
        Thread t1 = new Thread(mr, "飞机");
        Thread t2 = new Thread(mr, "坦克");

        // 设置优先级
        t1.setPriority(1);
        t2.setPriority(10);

        // 查看线程优先级
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());

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

6、守护线程

final void setDaemon(boolean on) 设置为守护线程(备胎线程),当其他的非守护线程执行结束后,守护线程会陆续结束,当非守护线程结束后,守护线程就没有存在的必要了。

线程类MyThread1,MyThread2

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

执行类ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        // 线程2设置为守护线程
        t2.setDaemon(true);

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

7、线程的让出

public static void yield() 出让线程/礼让线程,让出当前执行线程CPU的执行权

线程类MyThread

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
            // 出让当前CPU的执行权
            Thread.yield();
        }
    }
}

执行类ThreadDemo

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

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

        t1.setName("飞机");
        t2.setName("坦克");

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

8、线程插队

public final void join() 插入线程/插队线程,讲指定的线程插入到main(当前线程)之前执行

线程类MyThread

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

执行类ThreadDemo

public class ThreadDemo {

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

        MyThread t1 = new MyThread();
        t1.setName("土豆");
        t1.start();
        // 将t1(土豆) 线程插入到main线程(当前线程)之前
        t1.join();

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

9、同步代码块

在需要同步的代码块中加入synchronized(当前类字节码文件)

线程类MyThread

public class MyThread extends Thread {

    /**
     * 票号,所有的类都共享该票号
     */
    static int ticket = 0;

    @Override
    public void run() {
        while (true) {
            // 使用同步代码块枷锁,锁对象必须是唯一的才行(使用当前类字节码)
            synchronized (MyThread.class) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (ticket < 100) {
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!");
                } else {
                    break;
                }
            }
        }
    }
}

执行类ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        /*
         * 需求
         *       某电影院有100张票,只有三个买票窗口,使用多线程设计一个模拟程序来卖票
         * 利用同步代码块来完成
         */

        // 创建线程对象
        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();
    }
}

10、同步方法

将需要同步的代码抽取为一个方法,并使用synchronized进行修饰

线程类MyRunnable

public class MyRunnable implements Runnable {

    int ticket = 0;

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

    // 3.同步方法(从同步代码块中抽取出来)
    private synchronized boolean method() {
        // 4.共享代码是否到了末尾
        if (ticket == 1000) {
            return true;
        } else {
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
        }
        return false;
    }
}

执行类ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        /*
         * 需求
         *       某电影院有100张票,只有三个买票窗口,使用多线程设计一个模拟程序来卖票
         *  利用同步方法来完成
         */

        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();

    }
}

11、线程锁

lock() 配合使用可以达到synchronized相同操作

线程类MyThread

public class MyThread extends Thread {

    static int ticket = 0;

    // 只有一把锁
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 2.同步代码块
            // synchronized (MyThread.class) {
            lock.lock(); // 加锁
            // 3.判断
            try {
                if (ticket == 100) {
                    break;
                } else {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "在卖第" + ticket + "张票");
                }
            }finally {
                lock.unlock(); // 释放锁
            }
            //   }
        }

    }
}

执行类ThreadDemo

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

        /*
         * 需求:
         *      某电影院目前正在上映国产电影,共有100张票,3个窗口
         *      使用JDK的lock实现
         */
        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();
    }
}

12、死锁问题

死锁原因常见于锁的嵌套等操作,导致相互获取不到资源产生的等待问题

线程类MyThread

public class MyThread extends Thread{

    static Object objA = new Object();
    static Object objB = new Object();

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

执行类ThreadDemo

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();
    }
}

13、等待唤醒机制(消费者模式)

生产者(Cook) => 中间者(Desk) <= 消费者(Foodie)

线程类Cook,Foodie

/**
* 厨师
*/
public class Cook extends Thread {

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

    @Override
    public void run() {
        // 1.循环
        while (true) {
            // 2.同步代码块
            synchronized (Desk.lock) {
                // 3.判断共享数据是否到末尾(到了)
                if (Desk.count == 0) {
                    break;
                } else {
                    // 4.判断共享数据是否到末尾(没有,执行核心逻辑)

                    // 判断桌子上是否有食物
                    if (Desk.foodFlag == 1) {
                        // 如果有 就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        // 如果没有 就制作
                        System.out.println("厨师做了一碗面条");
                        // 修改桌子上食物状态
                        Desk.foodFlag = 1;
                        // 叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
/**
* 客人
*/
public class Foodie extends Thread {

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

    @Override
    public void run() {

        // 1.循环
        while (true) {
            // 2.同步代码块
            synchronized (Desk.lock) {
                // 3.判断共享数据是否到了末尾(到了末尾,线程执行完毕)
                if (Desk.count == 0) {
                    break;
                } else {
                    // 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
                    // 先判断桌子上是否有面条
                    if (Desk.foodFlag == 0) {
                        // 没有就等待
                        try {
                            Desk.lock.wait(); // 让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        // 把吃的总数减一
                        Desk.count--;
                        // 有就开吃
                        System.out.println("吃货正在吃面条,还能吃" + Desk.count + "碗!");
                        // 吃完后唤醒厨师继续制作
                        Desk.lock.notifyAll();
                        // 修改桌子状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
/**
* 中间者 桌子
*/
public class Desk {
    /*
     * 控制生产者和消费者的执行
     */

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

    /**
     * 总个数,最多能吃10碗
     */
    public static int count = 10;

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

}

执行类ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {
        /*
         * 需求:完成生产者和消费者(等待唤醒机制)的代码
         *      实现线程轮流交替的执行效果
         *
         * 生产者 ===> 中间者 <=== 消费者
         *
         */

        // 创建线程对象
        Cook cook = new Cook();
        Foodie foodie = new Foodie();

        cook.setName("厨师");
        foodie.setName("客人");

        // 开启线程
        cook.start();
        foodie.start();

    }
}

14、阻塞队列下的等待唤醒机制

生产者和消费者必须使用同一个队列ArrayBlockingQueue

生产者 => ArrayBlockingQueue <= 消费者

线程类Cook,Foodie

/**
* 厨师
*/
public class Cook extends Thread {

    ArrayBlockingQueue<String> queue;

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


    @Override
    public void run() {

        while (true) {
            // 不断的把面条放入阻塞队列中
            try {
                // put方法底层实现了锁操作,所以无需加锁
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
/**
* 客人
*/
public class Foodie extends Thread {

    ArrayBlockingQueue<String> queue;

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

    @Override
    public void run() {
        while (true) {
            // 不断的从阻塞队列中获取面条
            // peek方法底层实现了锁操作,所以无需加锁
            String food = null;
            try {
                food = queue.take();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("客户吃了 " + food);
        }
    }
}

执行类ThreadDemo

public class ThreadDemo {

    public static void main(String[] args) {

        /*
         * 需要:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
         * 细节:
         *      生产者和消费者必须使用同一个队列
         */

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

        // 2.创建线程对象,并把阻塞队列传递过去
        Cook cook = new Cook(queue);
        Foodie foodie = new Foodie(queue);

        // 3.开启线程
        cook.start();
        foodie.start();
    }
}

线程池

1、线程池的创建

ExecutorService newCachedThreadPool() 创建一个没有上限的线程池

ExecutorService new FixedThreadPool(int nThread) 创建有上限的线程池

线程类MyRunnable

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

执行类MyThreadPoolDemo

public class MyThreadPoolDemo {

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

        /*
         * public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
         * public static ExecutorService new FixedThreadPool(int nThread) 创建有上限的线程池
         */

        fixedThreadPool();
    }

    public static void newCachedThreadPool() throws InterruptedException {
        // 1.获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();

        // 2.提交任务
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());


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

    public static void fixedThreadPool() throws InterruptedException {
        // 创建三个线程
        ExecutorService pool1 = Executors.newFixedThreadPool(3);
        // 2.提交任务
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());

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

2、自定义线程池

(核心线程数量, 最大线程数量, 空闲线程最大存活时间, 任务队列, 创建线程工厂, 任务的拒绝策略)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
	参数一: 核心线程数量      不能小于0,
	参数二: 最大线程数       不能小于等于0,最大数量 >= 核心线程数量,
	参数三: 控线线程最大存活时间      不能小于0,
	参数四: 线程存活时间单位        用TimeUnit指定,
	参数五: 任务队列        不能为null,
	参数六: 创建线程工厂      不能为null,
	参数七: 任务的拒绝策略     不能为null
);

例子,MyThreadPoolDemo1

public class MyThreadPoolDemo1 {

    public static void main(String[] args) {
        // 自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                // 核心线程数量,不能小于0
                3,
                // 最大线程数不能小于等于0,最大数量 >= 核心线程数量
                6,
                // 控线线程最大存活时间
                60,
                // 线程存活时间单位,用TimeUnit指定
                TimeUnit.SECONDS,
                // 任务队列
                new ArrayBlockingQueue<>(3),
                // 创建线程工厂
                Executors.defaultThreadFactory(),
                //  任务的拒绝策略
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        // 提交任务到线程池
        pool.submit(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }
}