Java多线程 - 学习 + 记录

文章目录

线程状态转移图

在这里插入图片描述

thread.start() & thread.run() 的区别

示例

/**
 * thread.start() 和 thread.run()的区别
 */
public class StartAndRun implements Runnable{
    @Override
    public void run() {
        System.out.println("Thread name: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StartAndRun());

        System.out.println("-------thread.run()-------");
        thread.run();

        Thread.sleep(1000);

        System.out.println("-------thread.start()-------");
        thread.start();
    }
}

在这里插入图片描述

代码:chapter2#StartAndRun

getId() 的作用

getId() 方法的作用是取得线程的唯一标识。

示例

public class GetId implements Runnable{
    @Override
    public void run() {
        System.out.println("Run thread id: " + Thread.currentThread().getId());
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new GetId());
        thread.start();

        Thread.sleep(1000);

        System.out.println("Main thread id: " + Thread.currentThread().getId());
    }
}

在这里插入图片描述

代码:chapter3#GetId

停止线程

在Java中有3种方法可以终止正在运行的线程:

  1. 线程正常执行结束。
  2. 使用stop、suspend、resume强制终止线程。(这三个方法都是过期方法,不推荐使用)
  3. 使用interrupt方法中断线程。

interrupt() 方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。

isInterrupted()

isInterrupted() 方法测试线程是否已经是中断状态,但不清除状态标志。

示例

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

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new IsInterrupted());

        System.out.println("Thread starting..");
        thread.start();

        Thread.sleep(1);

        System.out.println("Thread interrupting..");
        thread.interrupt();

        System.out.println("Thread isInterrupt = " + thread.isInterrupted());

        Thread.sleep(1000);

        System.out.println("Thread isInterrupt = " + thread.isInterrupted());
    }
}

在这里插入图片描述

代码:chapter4#Interrupted

yield() 方法

yield() 方法的作用是:放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。

注释掉:Thread.yeild() 方法
示例

public class Yield implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("sub thread, i = " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Yield());
        thread.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("Main thread, i = " + i);
//            Thread.yield();
        }
    }
}

在这里插入图片描述
不注释:Thread.yeild() 方法
示例

public class Yield implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("sub thread, i = " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Yield());
        thread.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("Main thread, i = " + i);
            Thread.yield();
        }
    }
}

在这里插入图片描述

代码:chapter5#Yield

实例变量非线程安全的例子

  • 如果实例变量是:方法内部的私有变量,则不存在“非线程安全”的问题。这是方法内部的变量是私有的特性所造成的。
  • 如果多个线程共同访问同一个对象的实例变量,则有可能出现“非线程安全”的问题。

示例

public class TestClass {
    private int num = 0;

    public void getTargetName(String targetName) throws InterruptedException {
        if ("ThreadA".equals(targetName)) {
            num = 100;
            System.out.println("ThreadA is end!");
            Thread.sleep(1000);
        } else {
            num = 200;
            System.out.println("ThreadB is end!");
            Thread.sleep(1000);
        }
        System.out.println("num result is " + num);
    }

    public static void main(String[] args) {
        TestClass testClass = new TestClass();

        Thread threadA = new Thread(() -> {
            try {
                testClass.getTargetName("ThreadA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();

        Thread threadB = new Thread(() -> {
            try {
                testClass.getTargetName("ThreadB");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadB.start();
    }
}

在这里插入图片描述

代码:chapter6#TestClass

synchronized

关键字synchronized可以保证在同一刻,只有一个线程可以执行某一个方法或某一个代码块。它包含两个特征:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。

多个对象多个锁

多个对象多个锁,关键字synchronized 取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁。

示例

/**
 * 创建两个SynchronizedOne对象,分别用两个线程调用。
 * SynchronizedOne对象的方法加了关键字:synchronized,但是结果还是异步的
 */
public class SynchronizedOne {
    private int num = 0;

    synchronized public void getTargetName(String targetName) throws InterruptedException {
        if ("ThreadA".equals(targetName)) {
            num = 100;
            System.out.println("ThreadA is end!");
            Thread.sleep(1000);
        } else {
            num = 200;
            System.out.println("ThreadB is end!");
            Thread.sleep(1000);
        }
        System.out.println("num result is " + num);
    }

    public static void main(String[] args) {
        SynchronizedOne synchronizedOne = new SynchronizedOne();

        Thread threadA = new Thread(() -> {
            try {
                synchronizedOne.getTargetName("ThreadA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();

        SynchronizedOne synchronizedTwo = new SynchronizedOne();
        Thread threadB = new Thread(() -> {
            try {
                synchronizedTwo.getTargetName("ThreadB");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadB.start();
    }
}

在这里插入图片描述

代码:chapter7#SynchronizedOne

使用同一个对象后
示例

public class SynchronizedTwo {
    private int num = 0;

    synchronized public void getTargetName(String targetName) throws InterruptedException {
        if ("ThreadA".equals(targetName)) {
            num = 100;
            System.out.println("ThreadA is end!");
            Thread.sleep(1000);
        } else {
            num = 200;
            System.out.println("ThreadB is end!");
            Thread.sleep(1000);
        }
        System.out.println("num result is " + num);
    }

    public static void main(String[] args) {
        SynchronizedTwo synchronizedTwo = new SynchronizedTwo();

        Thread threadA = new Thread(() -> {
            try {
                synchronizedTwo.getTargetName("ThreadA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();

        Thread threadB = new Thread(() -> {
            try {
                synchronizedTwo.getTargetName("ThreadB");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadB.start();
    }
}

在这里插入图片描述

代码:chapter7#SynchronizedTwo

脏读

线程A先持有对象A线程B可以以异步的方式调用对象A中非synchronized类型的方法。若synchronized和非synchronized方法调用了同一个实例变量,则有可能出现脏读的情况。

示例

/**
 * 主线程 启动 子线程 去修改username + password
 * 在子线程修改的过程中(Thread.sleep(1000)),主线程读取对象中的username + password
 * 则有几率出现脏读的情况
 */
public class SynchronizedThree {
    private String userName = "admin";
    private String password = "admin";

    synchronized public void setValue(String userName, String password) throws InterruptedException {
        this.userName = userName;

        Thread.sleep(1000);

        this.password = password;
        System.out.println("setValue method -> userName = " + this.userName + ", password = " + this.password);
    }

    public void getValue() {
        System.out.println("getValue method -> userName = " + this.userName + ", password = " + this.password);
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedThree synchronizedThree = new SynchronizedThree();
        Thread thread = new Thread(() -> {
            try {
                synchronizedThree.setValue("root", "root");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        Thread.sleep(10);

        synchronizedThree.getValue();
    }
}

在这里插入图片描述

代码:chapter7#SynchronizedThree

两个方法都加上关键字synchronized
示例

public class SynchronizedFour {
    private String userName = "admin";
    private String password = "admin";

    synchronized public void setValue(String userName, String password) throws InterruptedException {
        this.userName = userName;

        Thread.sleep(1000);

        this.password = password;
        System.out.println("setValue method -> userName = " + this.userName + ", password = " + this.password);
    }

    synchronized public void getValue() {
        System.out.println("getValue method -> userName = " + this.userName + ", password = " + this.password);
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedFour synchronizedFour = new SynchronizedFour();
        Thread thread = new Thread(() -> {
            try {
                synchronizedFour.setValue("root", "root");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        Thread.sleep(10);

        synchronizedFour.getValue();
    }
}

在这里插入图片描述

代码:chapter7#SynchronizedFour

synchronized锁重入

synchronized 方法/块的内部调用本类的其他synchronized 方法/块时,是永远可以得到锁的。父子类继承也符合synchronized锁重入的条件。

示例

/**
 * Main & Sub类的方法都加上关键字synchronized
 * Sub类循环调用Main的方法
 */
public class Main {
    public int i = 10;

    synchronized public void operateIMainMethod() throws InterruptedException {
        i--;
        System.out.println("Main print i = " + i);
        Thread.sleep(1000);
    }
}
public class Sub extends Main {
    synchronized public void operateISubMethod() throws InterruptedException {
        while (i > 0) {
            i--;
            System.out.println("Sub print i = " + i);
            Thread.sleep(1000);
            this.operateIMainMethod();
        }
    }

    public static void main(String[] args) {
        Sub sub = new Sub();
        Thread thread = new Thread(() -> {
            try {
                sub.operateISubMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
    }
}

在这里插入图片描述

代码:chapter8

synchronized同步语句块

示例

/**
 * 加了关键字synchronized的代码块内的执行顺序是:同步的
 * 没有加关键字synchronized的代码块内执行顺序是:异步的
 */
public class Task {
    private String taskName;

    public void logTaskName(String inputTaskName) throws InterruptedException {
        System.out.println("Begin!");

        Thread.sleep(1000);

        synchronized (this) {
            this.taskName = inputTaskName;
            System.out.println("The task name result is " + this.taskName + ";The input task name is " + inputTaskName);
            System.out.println("End!");
        }
    }

    public static void main(String[] args) {
        Task task = new Task();
        Thread threadA = new Thread(() -> {
            try {
                task.logTaskName("A");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();

        Thread threadB = new Thread(() -> {
            try {
                task.logTaskName("B");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadB.start();
    }
}

在这里插入图片描述

代码:chapter9#Task

静态同步synchronized 方法与synchronized(class) 代码块

  • 关键字synchronized可以加在static静态方法上,对当前的 *.java 文件对应的class类进行持锁。
    • synchronized static 和 synchronized(class) 的作用是一样的
  • 关键字synchronized可以加在非static静态方法上,是给当前的对象上锁。

示例

/**
 * 两个线程调用的是两个对象内的同步方法
 * 得到的结果总是同步的
 */
public class Service {
    synchronized public static void printA() throws InterruptedException {
        System.out.println("Print A begin");
        Thread.sleep(1000);
        System.out.println("Print A end");
    }

    synchronized public static void printB() throws InterruptedException {
        System.out.println("Print B begin");
        System.out.println("Print B end");
    }

    public static void main(String[] args) {
        Service service1 = new Service();
        Service service2 = new Service();

        Thread threadA = new Thread(() -> {
            try {
                service1.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();

        Thread threadB = new Thread(() -> {
            try {
                service2.printB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadB.start();
    }
}

在这里插入图片描述

代码:chapter10#Service

去掉同步方法上的static
示例

/**
 * 去掉同步方法上的static
 * 得到的结果是异步的
 * 这时候synchronized获得的锁是:对象锁
 */
public class Service2 {
    synchronized public void printA() throws InterruptedException {
        System.out.println("Print A begin");
        Thread.sleep(1000);
        System.out.println("Print A end");
    }

    synchronized public void printB() throws InterruptedException {
        System.out.println("Print B begin");
        System.out.println("Print B end");
    }

    public static void main(String[] args) {
        Service2 service1 = new Service2();
        Service2 service2 = new Service2();

        Thread threadA = new Thread(() -> {
            try {
                service1.printA();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();

        Thread threadB = new Thread(() -> {
            try {
                service2.printB();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadB.start();
    }
}

在这里插入图片描述

代码:chapter10#Service2

死锁

死锁产生的条件是:多线程各自持有不同的锁,并互相视图获取对方个已持有的锁,导致双方各自无限等待。
避免死锁的方法是:多线程获取锁的顺序要一致。

示例

/**
 * 分别调用同一个对象的同一个方法,根据传入的值不同,走不同的流程。
 * 流程中互相调用对方的锁,双方都在等对方释放锁,最终导致死锁。
 */
public class DeadLock {
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void logTaskName(String taskName) throws InterruptedException {
        /**
         * 从lock1 切换到 lock2
         */
        if ("A".equals(taskName)) {
            synchronized (lock1) {
                System.out.println("taskName = " + taskName);
                Thread.sleep(3000);

                synchronized (lock2) {
                    System.out.println("lock1 -> lock2");
                }
            }
        }

        /**
         * 从lock2 切换到 lock1
         */
        if ("B".equals(taskName)) {
            synchronized (lock2) {
                System.out.println("taskName = " + taskName);
                Thread.sleep(3000);

                synchronized (lock1) {
                    System.out.println("lock2 -> lock1");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DeadLock deadLock = new DeadLock();
        Thread threadA = new Thread(() -> {
            try {
                deadLock.logTaskName("A");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread threadB = new Thread(() -> {
            try {
                deadLock.logTaskName("B");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        threadA.start();
        threadB.start();
    }
}

在这里插入图片描述

代码:chapter12#DeadLock

volatile

关键字volatile 的主要作用是:使变量在多个线程间可见。强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

  • volatile只能修饰于变量
  • volatile能保证数据的可见性,但不能保证原子性

关键字volatile 主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。

参考资料

线程安全(上)–彻底搞懂volatile关键字

线程间通信

wait()

  • 作用:使当前执行代码的线程进行等待,直到接到通知被中断为止。
  • 在调用 wait() 之前,线程必须获得该对象的对象级别锁,即:只能在同步方法或同步块中使用。
  • wait() 方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
  • wait(long):当超过设定的时间还是没有其他线程唤醒此线程,则会自动唤醒。

notify()

  • 作用是:用来通知那些等待该对象的对象锁的其他线程。
  • 调用notify()之前,也需要获得该对象的对象级别锁
  • 调用notify()之后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,当前线程才会释放锁。

notifyAll()

  • notifyAll()方法可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。
  • 一般优先级最高的线程最先执行,

示例

public class WaitAndNotify {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();

        // wait()
        Thread waitThread = new Thread(() -> {
            try {
                synchronized (object) {
                    System.out.println("wait() -> begin");
                    object.wait();
                    System.out.println("wait() -> end");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        // notify()
        Thread notifyThread = new Thread(() -> {
            try {
                synchronized (object) {
                    System.out.println("notify() -> begin");
                    object.notify();
                    System.out.println("notify() -> end");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        waitThread.start();
        notifyThread.start();
    }
}

在这里插入图片描述

代码:chapter11#WaitAndNotify

当 interrupt() 遇到 wait()

当线程处于 wait() 状态时,调用线程对象的 interrupt() 方法,会出现 InterruptedException 异常。

示例

/**
 * 先让线程调用wait()方法,接着再调用interrupt()方法。
 */
public class WaitAndInterrupted {
    public Object lockObject = new Object();

    public void waitMethod() throws InterruptedException {
        synchronized (lockObject) {
            System.out.println("begin wait..");
            lockObject.wait();
            System.out.println("end wait..");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WaitAndInterrupted waitAndInterrupted = new WaitAndInterrupted();
        Thread thread = new Thread(() -> {
            try {
                waitAndInterrupted.waitMethod();
            } catch (InterruptedException e) {
                System.out.println("Throw InterruptedException...");
            }
        });

        thread.start();
        thread.interrupt();
    }
}

在这里插入图片描述

代码:chapter13#WaitAndInterrupted

notify() 和 notifyAll()

notify() 只能随机唤醒“一个”线程。
notifyAll() 可以唤醒所有线程。

示例1:notify()

public class NotifyOneThread {

    public void testNotifyOneThread(Object lock) throws InterruptedException {
        synchronized (lock) {
            System.out.println("begin wait.." + Thread.currentThread().getName());
            lock.wait();
            System.out.println("end wait.." + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NotifyOneThread notifyOneThread = new NotifyOneThread();
        Object lock = new Object();
        Thread threadOne = new Thread(() -> {
            try {
                notifyOneThread.testNotifyOneThread(lock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread threadTwo = new Thread(() -> {
            try {
                notifyOneThread.testNotifyOneThread(lock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread threadThree = new Thread(() -> {
            try {
                notifyOneThread.testNotifyOneThread(lock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread notifyThread = new Thread(() -> {
            synchronized (lock) {
                lock.notify();
//                lock.notifyAll();
            }
        });

        threadOne.start();
        threadTwo.start();
        threadThree.start();

        Thread.sleep(1000);
        notifyThread.start();
    }
}

在这里插入图片描述
示例2:notifyAll()

public class NotifyOneThread {

    public void testNotifyOneThread(Object lock) throws InterruptedException {
        synchronized (lock) {
            System.out.println("begin wait.." + Thread.currentThread().getName());
            lock.wait();
            System.out.println("end wait.." + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NotifyOneThread notifyOneThread = new NotifyOneThread();
        Object lock = new Object();
        Thread threadOne = new Thread(() -> {
            try {
                notifyOneThread.testNotifyOneThread(lock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread threadTwo = new Thread(() -> {
            try {
                notifyOneThread.testNotifyOneThread(lock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread threadThree = new Thread(() -> {
            try {
                notifyOneThread.testNotifyOneThread(lock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread notifyThread = new Thread(() -> {
            synchronized (lock) {
//                lock.notify();
                lock.notifyAll();
            }
        });

        threadOne.start();
        threadTwo.start();
        threadThree.start();

        Thread.sleep(1000);
        notifyThread.start();
    }
}

在这里插入图片描述

代码:chapter14#NotifyOneThread

生产者/消费者

多生产者与多消费者导致的死锁

假死:所有线程都进入了等待状态。
主要原因:调用 notify() 方法时,唤醒的是同类。即:生产者唤醒生产者,消费者唤醒消费者。
解决方法:调用 notifyAll() 方法,将所有等待的线程都唤醒。

示例1:notify()

public class FakeDead {
    public String value = "";

    // 生产者
    public void producer(Object lock) throws InterruptedException {
        synchronized (lock) {
            while (!value.equals("")) {
                System.out.println("生产者: " + Thread.currentThread().getName() + " waiting☆");
                lock.wait();
            }
            System.out.println("生产者: " + Thread.currentThread().getName() + " running☆");
            value = System.currentTimeMillis() + " --- " + Thread.currentThread().getName();
            lock.notify();
        }
    }

    // 消费者
    public void consumer(Object lock) throws InterruptedException {
        synchronized (lock) {
            while (value.equals("")) {
                System.out.println("消费者: " + Thread.currentThread().getName() + " waiting★");
                lock.wait();
            }
            System.out.println("消费者: " + Thread.currentThread().getName() + " running★");
            value = "";
            lock.notify();
        }
    }

    public static void main(String[] args) {
        Object lock = new Object();
        FakeDead fakeDead = new FakeDead();

        for (int i = 0; i < 2; i++) {
            // 创建消费者线程,循环的调用consumer方法
            Thread consumerThread = new Thread(() -> {
                try {
                    while (true) {
                        fakeDead.consumer(lock);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            // 创建生产者线程,循环的调用producer方法
            Thread producerThread = new Thread(() -> {
                try {
                    while (true) {
                        fakeDead.producer(lock);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            
            consumerThread.setName("consumer - " + i);
            producerThread.setName("producer - " + i);

            consumerThread.start();
            producerThread.start();
        }
    }
}

2个消费者 和 2个生产者 最后都进入了waiting状态了
在这里插入图片描述

代码:chapter15#FakeDead

示例2:notifyAll()

public class FakeDead2 {
    public String value = "";

    // 生产者
    public void producer(Object lock) throws InterruptedException {
        synchronized (lock) {
            while (!value.equals("")) {
                System.out.println("生产者: " + Thread.currentThread().getName() + " waiting☆");
                lock.wait();
            }
            System.out.println("生产者: " + Thread.currentThread().getName() + " running☆");
            value = System.currentTimeMillis() + " --- " + Thread.currentThread().getName();
            lock.notifyAll();
        }
    }

    // 消费者
    public void consumer(Object lock) throws InterruptedException {
        synchronized (lock) {
            while (value.equals("")) {
                System.out.println("消费者: " + Thread.currentThread().getName() + " waiting★");
                lock.wait();
            }
            System.out.println("消费者: " + Thread.currentThread().getName() + " running★");
            value = "";
            lock.notifyAll();
        }
    }

    public static void main(String[] args) {
        Object lock = new Object();
        FakeDead2 fakeDead = new FakeDead2();

        for (int i = 0; i < 2; i++) {
            // 创建消费者线程,循环的调用consumer方法
            Thread consumerThread = new Thread(() -> {
                try {
                    while (true) {
                        fakeDead.consumer(lock);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            // 创建生产者线程,循环的调用producer方法
            Thread producerThread = new Thread(() -> {
                try {
                    while (true) {
                        fakeDead.producer(lock);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            consumerThread.setName("consumer - " + i);
            producerThread.setName("producer - " + i);

            consumerThread.start();
            producerThread.start();
        }
    }
}

解决上面假死的情况
在这里插入图片描述

代码:chapter15#FakeDead2

因条件判断引起的线程异常

例子:使用一个生产者向堆栈List对象中放入数据,而多个消费者从List堆栈中取出数据。List的最大容量是:1。

示例1:使用 if 判断条件

public class IfCondition {
    private List list = new ArrayList(1);

    /**
     * 数组大小 = 1 时,线程释放资源,进入等待
     * 数组大小 != 1 时, 数组大小 + 1
     */
    public void push(Object lock) {
        synchronized (lock) {
            try {
                if (list.size() == 1) {
                    lock.wait();
                }
                list.add("anyString = " + Math.random());
                lock.notify();
                System.out.println("Current thread name = " + Thread.currentThread().getName() + " push = " + list.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 数组大小 = 0 时,线程释放资源,进入等待
     * 数组大小 != 0 时, 数组大小 - 1
     */
    public void pop(Object lock) {
        synchronized (lock) {
            try {
                if (list.size() == 0) {
                    lock.wait();
                }
                list.remove(0);
                lock.notify();
                System.out.println("Current thread name = " + Thread.currentThread().getName() + " pop = " + list.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 主线程
     *
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        IfCondition ifCondition = new IfCondition();

        /**
         * 一个生产者
         */
        Thread pushThread = new Thread(() -> {
            while (true) {
                ifCondition.push(lock);
            }
        });
        pushThread.start();
        Thread.sleep(1000);
        
        /**
         * 多个消费者
         */
        for (int i = 0; i < 3; i++) {
            Thread popThread = new Thread(() -> {
                while (true) {
                    ifCondition.pop(lock);
                }
            });
            popThread.setName("popThread - " + i);
            popThread.start();
        }
    }
}

程序很容易就抛出异常
在这里插入图片描述

代码:chapter16#IfCondition

示例2:使用 while 判断条件
除了 if 条件 改成 while 条件外,lock.notify() 也要改成 lock.notifyAll()。不然,解决了异常的问题,还会遇到死锁的问题。

public class WhileCondition {
    private List list = new ArrayList(1);

    /**
     * 数组大小 = 1 时,线程释放资源,进入等待
     * 数组大小 != 1 时, 数组大小 + 1
     */
    public void push(Object lock) {
        synchronized (lock) {
            try {
                while (list.size() == 1) {
                    lock.wait();
                }
                list.add("anyString = " + Math.random());
                lock.notifyAll();
                System.out.println("Current thread name = " + Thread.currentThread().getName() + " push = " + list.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 数组大小 = 0 时,线程释放资源,进入等待
     * 数组大小 != 0 时, 数组大小 - 1
     */
    public void pop(Object lock) {
        synchronized (lock) {
            try {
                while (list.size() == 0) {
                    lock.wait();
                }
                list.remove(0);
                lock.notifyAll();
                System.out.println("Current thread name = " + Thread.currentThread().getName() + " pop = " + list.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 主线程
     *
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        WhileCondition ifCondition = new WhileCondition();

        /**
         * 一个生产者
         */
        Thread pushThread = new Thread(() -> {
            while (true) {
                ifCondition.push(lock);
            }
        });
        pushThread.start();
        Thread.sleep(1000);

        /**
         * 多个消费者
         */
        for (int i = 0; i < 3; i++) {
            Thread popThread = new Thread(() -> {
                while (true) {
                    ifCondition.pop(lock);
                }
            });
            popThread.setName("popThread - " + i);
            popThread.start();
        }
    }
}

代码:chapter16#WhileCondition

join()

在主线程创建并启动子线程后,如果子线程中要进行大量的耗时计算,主线程将会早于子线程结束。如果主线程需要等待子线程完成后完成,则可以使用:join() 方法。

join() 方法的作用是:使子线程正常执行线程任务,使得主线程进入阻塞,直到子线程执行完后再执行。

join(long)

方法join(long):设定等待的时间。

public class Join {

    public void execute() throws InterruptedException {
        System.out.println("Begin");
        Thread.sleep(1000);
        System.out.println("End");
    }

    public static void main(String[] args) throws InterruptedException {
        Join join = new Join();
        Thread thread = new Thread(() -> {
            try {
                join.execute();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        thread.start();
        thread.join();

        System.out.println("Main thread end.");
    }
}

在这里插入图片描述

代码:chapter17#Join

ThreadLocal

ThreadLocal:用于存放属于每个线程自己的值。

public class ThreadLocalTest {
    private static ThreadLocal threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            testMethod();
        });

        Thread threadB = new Thread(() -> {
            testMethod();
        });

        threadA.setName("ThreadA");
        threadB.setName("ThreadB");
        threadA.start();
        threadB.start();
    }

    private static void testMethod() {
        System.out.println(Thread.currentThread().getName() + " beginning...");
        threadLocal.set("My thread name is " + Thread.currentThread().getName());
        try {
            Thread.sleep(5000);
            System.out.println("Test threadlocal in " + Thread.currentThread().getName() + "; My threadlocal value is " + threadLocal.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

代码:chapter18#ThreadLocalTest

Lock

ReentrantLock

使用Reentrantlock达到和Synchronized同样的效果。

public class ReentrantLockTest {
    private Lock lock = new ReentrantLock();

    public void methodA() {
        try {
            lock.lock();

            System.out.println("Method A begin -> " + Thread.currentThread().getName());

            Thread.sleep(2000);

            System.out.println("Method A end -> " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void methodB() {
        try {
            lock.lock();

            System.out.println("Method B begin -> " + Thread.currentThread().getName());

            Thread.sleep(2000);

            System.out.println("Method B end -> " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockTest reentrantLockTest = new ReentrantLockTest();

        for (int i = 0; i < 6; i++) {
            Thread threadA = new Thread(() -> {
                reentrantLockTest.methodA();
            });

            Thread threadB = new Thread(() -> {
                reentrantLockTest.methodB();
            });

            threadA.setName("ThreadA-" + i);
            threadB.setName("ThreadB-" + i);

            threadA.start();
            threadB.start();
        }
    }
}

在这里插入图片描述

代码:chapter19#ReentrantLockTest

Condition的await & signal

Reentrantlock 配合 Condition 后,也可以实现像 Synchronized 配合 wait & notify 的 等待 & 通知的功能。

不同点

  • 一个Lock对象中可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。
  • notify() & notifyAll() 方法进行通知时,被通知的对象是由JVM随机选择的结果。
public class ConditionTest {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("Await begin time is " + new Date());
            condition.await();
            System.out.println("Await end time is " + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signal() {
        lock.lock();
        System.out.println("Signal begin time is " + new Date());
        condition.signal();
        System.out.println("Signal end time is " + new Date());
        lock.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        ConditionTest conditionTest = new ConditionTest();

        Thread threadAwait = new Thread(() -> {
            conditionTest.await();
        });

        Thread threadSignal = new Thread(() -> {
            conditionTest.signal();
        });

        threadAwait.start();
        Thread.sleep(3000);
        threadSignal.start();
    }
}

到目前为止,conditionawait() & signal()/signalAll() 实现的效果和 wait() & notify()/notifyAll() 一样。
在这里插入图片描述

代码:chapter20#ConditionTest

多个Condition

在同一个实例中,创建两个Condition对象,然后实现只通知其中某一个Condition对象。

public class ConditionTest2 {
    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println("Begin await A time is " + new Date() + "; Current thread name is " + Thread.currentThread().getName());
            conditionA.await();
            lock.unlock();
            System.out.println("End await A time is " + new Date() + "; Current thread name is " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println("Begin await B time is " + new Date() + "; Current thread name is " + Thread.currentThread().getName());
            conditionB.await();
            lock.unlock();
            System.out.println("End await B time is " + new Date() + "; Current thread name is " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void signalA() {
        lock.lock();
        System.out.println("Begin signal A time is " + new Date());
        conditionA.signalAll();
        lock.unlock();
    }

    public void signalB() {
        lock.lock();
        System.out.println("Begin signal B time is " + new Date());
        conditionB.signalAll();
        lock.unlock();
    }

    /**
     * 创建两个线程,两个Condition对象,进入wait状态
     * 之后调用其中一个Condition对象的signalAll方法
     * 查看最终结果是不是只有ConditionA对象会被唤醒
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        ConditionTest2 conditionTest2 = new ConditionTest2();

        Thread threadA = new Thread(() -> {
            conditionTest2.awaitA();
        });

        Thread threadB = new Thread(() -> {
            conditionTest2.awaitB();
        });

        threadA.setName("ThreadA");
        threadB.setName("ThreadB");

        threadA.start();
        threadB.start();

        Thread.sleep(1000);
        conditionTest2.signalA();
    }
}

结果是:只有ConditionA被唤醒了,ConditionB依旧处于wait状态。
在这里插入图片描述

代码:chapter20#ConditionTest2

公平锁与非公平锁

Lock分为“公平锁”和“非公平锁”

  • 公平锁:表示线程获取锁的顺序是按照线程加锁的顺序来分配的,先到先得。
  • 非公平锁:就是线程获得锁的机会是随机的,这个可能会造成某些线程一直拿不到锁。

示例1:公平锁

public class ConditionTest4 {
    // 注意这里创建Lock对象时,传入的参数是:true
    private Lock lock = new ReentrantLock(true);

    public void serviceMethod() {
        lock.lock();
        System.out.println("Current Thread: " + Thread.currentThread().getName() + " is locking..");
        lock.unlock();
    }

    public static void main(String[] args) {
        ConditionTest4 conditionTest4 = new ConditionTest4();

        // 创建10个线程
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                System.out.println("Current Thread: " + Thread.currentThread().getName() + " is running..");
                conditionTest4.serviceMethod();
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

结果是:总是按顺序获得锁
在这里插入图片描述

代码:chapter20#ConditionTest3

示例2:非公平锁

public class ConditionTest4 {
    // 注意这里创建Lock对象时,传入的参数是:false
    private Lock lock = new ReentrantLock(false);

    public void serviceMethod() {
        lock.lock();
        System.out.println("Current Thread: " + Thread.currentThread().getName() + " is locking..");
        lock.unlock();
    }

    public static void main(String[] args) {
        ConditionTest4 conditionTest4 = new ConditionTest4();

        // 创建10个线程
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                System.out.println("Current Thread: " + Thread.currentThread().getName() + " is running..");
                conditionTest4.serviceMethod();
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

结果是:不按顺序获得锁
在这里插入图片描述

代码:chapter20#ConditionTest4

getHoldCount()

getHoldCount():查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。

public class GetHoldCount {
    private ReentrantLock lock = new ReentrantLock();

    public void serviceMethod() {
        lock.lock();
        System.out.println("ServiceMethod getHoldCount = " + lock.getHoldCount());
        serviceMethod2();
        lock.unlock();
    }

    public void serviceMethod2() {
        lock.lock();
        System.out.println("ServiceMethod2 getHoldCount = " + lock.getHoldCount());
        serviceMethod3();
        lock.unlock();
    }

    public void serviceMethod3() {
        lock.lock();
        System.out.println("ServiceMethod3 getHoldCount = " + lock.getHoldCount());
        lock.unlock();
    }

    public static void main(String[] args) {
        GetHoldCount getHoldCount = new GetHoldCount();
        getHoldCount.serviceMethod();
    }
}

在这里插入图片描述

代码:chapter20#GetHoldCount

getQueueLength()

getQueueLength():返回正等待获取某锁的线程估计数。

public class GetQueueLength {
    private ReentrantLock lock = new ReentrantLock();

    public void serviceMethod() {
        try {
            lock.lock();
            System.out.println("Thread: " + Thread.currentThread().getName() + " is locking...");
            Thread.sleep(3000);
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        GetQueueLength getQueueLength = new GetQueueLength();

        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                getQueueLength.serviceMethod();
            });

            thread.setName("Thread-" + i);
            thread.start();
        }

        Thread.sleep(1000);
        System.out.println("GetQueueLength = " + getQueueLength.lock.getQueueLength());
    }
}

在这里插入图片描述

getWaitQueueLength(Condition condition)

getWaitQueueLength(Condition condition):返回等待与此锁相关的给定条件Condition的线程估计数。

eg:
有5个线程,每个线程都执行了同一个condition对象的 await() 方法,则调用getWaitQueueLength(Condition condition)方法时,返回的值为:5。

/**
 * 创建5个线程,依次进入wait状态
 */
public class GetWaitQueueLength {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void serviceMethod() {
        try {
            lock.lock();
            System.out.println("Thread: " + Thread.currentThread().getName() + " is locking..");
            condition.await();
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void signalMethod() {
        lock.lock();
        System.out.println("GetWaitQueueLength = " + lock.getWaitQueueLength(condition));

        condition.signal();
        System.out.println("After signal(), GetWaitQueueLength = " + lock.getWaitQueueLength(condition));
    }

    public static void main(String[] args) throws InterruptedException {
        GetWaitQueueLength getWaitQueueLength = new GetWaitQueueLength();

        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                getWaitQueueLength.serviceMethod();
            });

            thread.setName("Thread-" + i);
            thread.start();
        }

        Thread.sleep(2000);
        getWaitQueueLength.signalMethod();
    }
}

在这里插入图片描述

tryLock()

作用:仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。

public class TryLock {
    private ReentrantLock lock = new ReentrantLock();

    public void tryLockMethod() {
        if (lock.tryLock()) {
            System.out.println(Thread.currentThread().getName() + " 获得锁");
        } else {
            System.out.println(Thread.currentThread().getName() + " 未获得锁");
        }
    }

    public static void main(String[] args) {
        TryLock tryLock = new TryLock();

        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(() -> {
                tryLock.tryLockMethod();
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

在这里插入图片描述

代码:chapter20#TryLock

tryLock(long timeout, TimeUnit unit)

作用是:如果锁定在给定的等待时间内没有被另一个线程保持,且当前线程未中断,则获取该锁定。

awaitUninterruptibly()

当使用condition.await()时,线程调用interrupt()后,会抛出异常。
当使用condition.awaitUninterruptibly()时,则不会抛出异常。

public class AwaitUninterruptibly {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void awaitMethod() {
        try {
            lock.lock();
            System.out.println("Before waiting..");
            condition.await();
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void awaitUninterruptibly() {
        lock.lock();
        System.out.println("Before waiting...");
        condition.awaitUninterruptibly();
        lock.unlock();
    }

    public static void main(String[] args) {
        AwaitUninterruptibly awaitUninterruptibly = new AwaitUninterruptibly();

        Thread thread1 = new Thread(() -> {
            awaitUninterruptibly.awaitUninterruptibly();
        });

        thread1.start();
        thread1.interrupt();

        Thread thread = new Thread(() -> {
            awaitUninterruptibly.awaitMethod();
        });

        thread.start();
        thread.interrupt();
    }
}

在这里插入图片描述

代码:chapter20#AwaitUninterruptibly

ReentrantReadWriteLock类

之前使用的ReentrantLock具有完全互斥排他的效果,即同一时间只有一个先线程可以执行lock.lock()后面的代码。这样虽然保证了实例变量的线程安全性,但效率却非常低下。

ReentrantReadWriteLock提供了两种锁:共享锁 & 排他锁。根据需要使用不同的锁,以提升代码的运行速度。

  • 读 + 读(共享锁)
  • 读 + 写(排他锁)
  • 写 + 写(排他锁)

读 + 读(共享锁)

public class ReadAndRead {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void readMethod() throws InterruptedException {
        lock.readLock().lock();
        System.out.println("获得读锁 -> " + Thread.currentThread().getName());
        Thread.sleep(1000);
        lock.readLock().unlock();
    }

    public static void main(String[] args) {
        ReadAndRead readAndRead = new ReadAndRead();

        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(() -> {
                try {
                    readAndRead.readMethod();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

在这里插入图片描述

代码:chapter21#ReadAndRead

写 + 写(排他锁)

public class WriteAndWrite {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void writeMethod() throws InterruptedException {
        lock.writeLock().lock();
        System.out.println("获得写锁 -> " + Thread.currentThread().getName() + " " + new Date());
        Thread.sleep(5000);
        lock.writeLock().unlock();
    }

    public static void main(String[] args) {
        WriteAndWrite writeAndWrite = new WriteAndWrite();

        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(() -> {
                try {
                    writeAndWrite.writeMethod();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

5秒后才能获得写锁
在这里插入图片描述

代码:chapter21#WriteAndWrite

读 + 写(排他锁)

public class WriteAndRead {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void writeMethod() throws InterruptedException {
        lock.writeLock().lock();
        System.out.println("获得写锁 -> " + Thread.currentThread().getName() + " " + new Date());
        Thread.sleep(5000);
        lock.writeLock().unlock();
    }

    public void readMethod() {
        lock.readLock().lock();
        System.out.println("获得读锁 -> " + Thread.currentThread().getName() + " " + new Date());
        lock.readLock().unlock();
    }

    public static void main(String[] args) {
        WriteAndRead writeAndRead = new WriteAndRead();
        Thread threadWrite = new Thread(() -> {
            try {
                writeAndRead.writeMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        threadWrite.setName("Thread-Write");
        threadWrite.start();

        Thread threadRead = new Thread(() -> {
            writeAndRead.readMethod();
        });

        threadRead.setName("Thread-Read");
        threadRead.start();

    }
}

5秒后才能获得读锁
在这里插入图片描述

代码:chapter21#WriteAndRead

StampedLock

对于 ReentrantReadWriteLock 而言,只有读 + 读 是共享锁的,其他的都是互斥锁。
Java 8引入了新的读写锁,StampedeLock。不同之处在于:StampedeLock 在读的过程中允许写锁后写入。存在的问题是:之前读的数据可能会不一致,所以需要额外的操作来判断在读的过程中是否有写的操作。

public class ReadAndWrite {
    private final StampedLock stampedLock = new StampedLock();

    private String name = "name1";

    public void writeName(String name) {
        // 获取写锁
        long stamp = stampedLock.writeLock();
        this.name = name;
        stampedLock.unlockWrite(stamp);
    }

    public void readName() throws InterruptedException {
        // 获得一个乐观锁,得到一个版本号
        long stamp = stampedLock.tryOptimisticRead();
        String tempName = this.name;

        // 让读线程睡0.1s,让写操作进行
        Thread.sleep(1000);
        // 判断在读的过程中是否有写入的操作,如果版本号检验失败,即:读的期间值发生了改变,则通过悲观锁进行读操作
        if (!stampedLock.validate(stamp)) {
            // 获取一个悲观锁,读锁
            stamp = stampedLock.readLock();
            tempName = this.name;
            stampedLock.unlockRead(stamp);
        }
        System.out.println("Name = " + tempName);
    }

    public static void main(String[] args) {
        ReadAndWrite readAndWrite = new ReadAndWrite();

        Thread readThread = new Thread(() -> {
            try {
                readAndWrite.readName();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        readThread.start();

        Thread writeThread = new Thread(() -> {
           readAndWrite.writeName("name2");
        });
        writeThread.start();
    }
}

区别

  • ReadWriteLock 相比,StampedLock 的代码更加复杂
  • StampedLock 是不可重入锁,不能再一个线程中反复获取同一个锁

单例模式与多线程

饿汉模式

饿汉模式:在类加载时就完成了初始化,项目启动时比较慢。但是是线程安全的。

/**
 * 多个线程获取Hungry对象
 * 对象的HashCode是一致的
 */
public class Hungry {
    private static Hungry hungry = new Hungry();

    public static Hungry getInstance() {
        return hungry;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " -> " + Hungry.getInstance().hashCode());
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

在这里插入图片描述

代码:chapter22#Hungry

懒汉模式

懒汉模式 - 非线程安全版

/**
 * 懒汉模式 - 非线程安全版
 * 在new LazyPattern()前,让线程睡眠1s
 * 导致其他线程都能够进到 lazyPattern == null 的分支中,得到不同的LazyPattern对象
 */
public class LazyPattern {
    private static LazyPattern lazyPattern;

    private LazyPattern() {

    }

    public static LazyPattern getInstance() throws InterruptedException {
        if (lazyPattern == null) {
            Thread.sleep(1000);
            lazyPattern = new LazyPattern();
        }
        return lazyPattern;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " -> " + LazyPattern.getInstance().hashCode());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

结果:得到的对象不一致
在这里插入图片描述

代码:chapter22#LazyPattern

懒汉模式 - 方法锁

在方法上加synchronized关键字

/**
 * 懒汉模式 - 极端线程安全版
 * 直接给getInstance()方法加锁
 * 即使后面对象已经被实例化了,在获取对象时,都需要获取锁、释放锁的操作
 */
public class LazyPattern2 {
    private static LazyPattern2 lazyPattern;

    private LazyPattern2() {

    }

    public synchronized static LazyPattern2 getInstance() {
        if (lazyPattern == null) {
            lazyPattern = new LazyPattern2();
        }
        return lazyPattern;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " -> " + LazyPattern2.getInstance().hashCode());
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

在这里插入图片描述

代码:chapter22#LazyPattern2

懒汉模式 - 代码块双重判断锁

/**
 * 懒汉模式 - 双重判断锁
 */
public class LazyPattern3 {
    private static LazyPattern3 lazyPattern;

    private LazyPattern3() {

    }

    /**
     * 假设:
     * 线程A获取到锁,并且创建对象:LazyPattern3
     * 在线程A创建对象的过程中(未创建完)
     * 线程B判断第一个:lazyPattern == null,得到的结果为:False
     * 则:线程B获取到了线程A创建的不完整的LazyPattern3对象
     * @return
     */
    public static LazyPattern3 getInstance() {
        if (lazyPattern == null) {
            synchronized (LazyPattern3.class) {
                if (lazyPattern == null) {
                    lazyPattern = new LazyPattern3();
                }
            }
        }
        return lazyPattern;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " -> " + LazyPattern3.getInstance().hashCode());
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

在这里插入图片描述

代码:chapter22#LazyPattern3

懒汉模式 - 代码块双重判断锁 + volatile

/**
 * 懒汉模式 - 双重判断锁 + volatile
 * 关于为什么加上:volatile后,可以解决双重判断锁的问题
 * 可以参考:https://www.cnblogs.com/jing-an-feng-shao/p/10275001.html
 */
public class LazyPattern4 {
    private static volatile LazyPattern4 lazyPattern;

    private LazyPattern4() {

    }

    public static LazyPattern4 getInstance() {
        if (lazyPattern == null) {
            synchronized (LazyPattern4.class) {
                if (lazyPattern == null) {
                    lazyPattern = new LazyPattern4();
                }
            }
        }
        return lazyPattern;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " -> " + LazyPattern4.getInstance().hashCode());
            });

            thread.setName("Thread-" + i);
            thread.start();
        }
    }
}

在这里插入图片描述

代码:chapter22#LazyPattern4

使用enum枚举类实现单例模式

在使用枚举类时,构造方法会被自动调用,可以通过这个特性实现单例模式。

public class EnumSingleton {
    public enum InternalEnumSingleton {
        readAndReadObject;

        private ReadAndRead readAndRead;

        private InternalEnumSingleton() {
            System.out.println("创建EnumSingleton对象");
            readAndRead = new ReadAndRead();
        }

        public ReadAndRead getReadAndRead() {
            return readAndRead;
        }
    }

    public static ReadAndRead getReadAndRead() {
        return InternalEnumSingleton.readAndReadObject.getReadAndRead();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                System.out.println(EnumSingleton.getReadAndRead().hashCode());
            });

            thread.start();
        }
    }
}

在这里插入图片描述

代码:chapter22#EnumSingleton

Concurrent集合

接口非线程安全线程安全
ListArrayListCopyOnWriteArrayList
MapHashMapConcurrentHashMap
SetHashSet / TreeSetCopyOnWriteArraySet
QueueArrayDeque / LinkedListArrayBlockingQueue / LinkedBlockingQueue
DequeArrayDeque / LinkedListLinkedBlockingDeque
  • 使用java.util.concurrent包提供的线程安全的并发集合可以大大简化多线程编程
  • 多线程同时读写并发集合是安全的
  • 尽量使用Java标准库提供的并发集合,避免自己编写同步代码

ExecutorService线程池

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

public class CachedThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService cacheThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 100; i++) {
            cacheThreadPool.execute(new Thread(() -> {
                System.out.println("Thread name = " + Thread.currentThread().getName());
            }));
        }
        cacheThreadPool.shutdown();
    }
}

在这里插入图片描述

代码:chapter23#CachedThreadPoolTest

newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

public class FixedThreadPool {
    public static void main(String[] args) {
        // 创建定长的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        // 遍历20次,线程数应在 ≤ 5
        for (int i = 0; i < 20; i++) {
            executorService.execute(new Thread(() -> {
                System.out.println("Thread name = " + Thread.currentThread().getName());
            }));
        }
        executorService.shutdown();
    }
}

在这里插入图片描述

代码:chapter23#FixedThreadPoolTest

newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

public class SingleThreadExecutorTest {
    public static void main(String[] args) {
        // 创建单线程线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 遍历10次,每次遍历的线程池应是同一个
        for (int i = 0;i < 10; i++) {
            executorService.execute(new Thread(() -> {
                System.out.println("Thread name = " + Thread.currentThread().getName());
            }));
        }

        executorService.shutdown();
    }
}

在这里插入图片描述

代码:chapter23#SingleThreadExecutorTest

newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行

一次性任务,在指定延迟时间后执行一次(.schedule)

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

// 一次性任务,在指定延迟时间后执行一次
scheduledExecutorService.schedule(new Thread(() -> {
    System.out.println("schedule method -> " + Thread.currentThread().getName());
}), 1, TimeUnit.SECONDS);

定时任务,每x秒执行一次(.scheduleAtFixedRate)

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

/**
 * initialDelay: 多少时间后执行
 * period: 间隔时间
 * 即:5秒后执行,每3秒执行一次
 */
System.out.println("当前时间 -> " + new Date());
scheduledExecutorService.scheduleAtFixedRate(new Thread(() -> {
    System.out.println("scheduleAtFixedRate method -> " + Thread.currentThread().getName() + "; 目前时间为:" + new Date());
}), 5, 3, TimeUnit.SECONDS);

在这里插入图片描述

代码:chapter23#ScheduledThreadPoolTest

定时任务,每x秒执行一次,线程任务执行时间大于x秒后的现象(.scheduleAtFixedRate)

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

/**
 * initialDelay: 多少时间后执行
 * period: 间隔时间
 * 即:5秒后执行,每3秒执行一次
 *
 * 设定线程执行的任务所花费的时间大于3秒,那么线程的执行情况应该是怎么样的?
 */
System.out.println("当前时间 -> " + new Date());
scheduledExecutorService.scheduleAtFixedRate(new Thread(() -> {
    try {
        Thread.sleep(4000);
        System.out.println("scheduleAtFixedRate method -> " + Thread.currentThread().getName() + "; 目前时间为:" + new Date());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}), 5, 3, TimeUnit.SECONDS);

结果是:在线程执行完成后,立刻执行下一个任务
在这里插入图片描述

代码:chapter23#ScheduledThreadPoolTest

定时任务,每x秒执行一次,线程任务执行时间大于x秒后的现象(.scheduleWithFixedDelay)

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

/**
 * initialDelay: 多少时间后执行
 * period: 间隔时间
 * 即:5秒后执行,每3秒执行一次
 *
 * 设定线程执行的任务所花费的时间大于3秒,那么线程的执行情况应该是怎么样的?
 */
System.out.println("当前时间 -> " + new Date());
scheduledExecutorService.scheduleWithFixedDelay(new Thread(() -> {
    try {
        Thread.sleep(4000);
        System.out.println("scheduleAtFixedRate method -> " + Thread.currentThread().getName() + "; 目前时间为:" + new Date());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}), 5, 3, TimeUnit.SECONDS);

结果是:在线程执行完成后,继续等待3秒后,再执行下一个任务
在这里插入图片描述

代码:chapter23#ScheduledThreadPoolTest

ExecutorService方法介绍

awaitTermination(long timeout, TimeUnit unit)

在所有任务执行了shundown() 或 timeout 或 interrupted()之前,都会一直阻塞着。

public class AwaitTerminationTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // 线程任务在2s后执行完成
        executorService.execute(new Thread(() -> {
            try {
                Thread.sleep(2000);
                System.out.println("Thread name = " + Thread.currentThread().getName() + "; Current time = " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));

        /**
         * 启动线程后,立刻执行shutdown()
         * 但是这时候,线程是还没有执行完成的
         */
        executorService.shutdown();

        if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
            System.out.println("Time1, awaitTermination = false, 线程未执行完!" );
        } else {
            System.out.println("Time1, awaitTermination = true, 线程已执行完!" );
        }

        // 主线程等待子线程执行完
        Thread.sleep(2000);
        if (executorService.awaitTermination(1, TimeUnit.SECONDS)) {
            System.out.println("Time2, awaitTermination = true, 线程已执行完!" );
        } else {
            System.out.println("Time2, awaitTermination = false, 线程未执行完!" );
        }
    }
}

在这里插入图片描述

代码:chapter24#AwaitTerminationTest

invokeAll(Collection<? extends Callable> tasks)

执行给定的任务,当所有任务完成时,返回其执行结果

/**
 * 实现Callable<String>接口,并且返回String类型的结果,返回结果具体情况具体分析
 */
public class CallableClass implements Callable<String> {
    String name;

    public CallableClass(String name) {
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        return "Name = " + this.name;
    }
}
public class InvokeAllTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        List<Callable<String>> tasks = new ArrayList<>();
        tasks.add(new CallableClass("A"));
        tasks.add(new CallableClass("B"));

        List<Future<String>> futures = executorService.invokeAll(tasks);

        for (Future<String> future : futures) {
            if (future.isDone() && !future.isCancelled()) {
                System.out.println(future.get());
            }
        }
    }
}

在这里插入图片描述

代码:chapter24#InvokeAllTest

invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit)

执行给定的任务,当所有任务完成或者超过了设定的时间时,返回其执行结果

/**
 * 实现Callable<String>接口,并且返回String类型的结果,返回结果具体情况具体分析
 */
public class CallableClass implements Callable<String> {
    String name;

    public CallableClass(String name) {
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        String name = this.name;
        if (this.name.equals("B")) {
            Thread.sleep(2000);
        }
        return "Name = " + this.name;
    }
}
/**
 * name = A时,瞬间执行完任务
 * name = B时,需要等待2s
 * 我们invokeAll设定的时间在1s
 * 所以,最终得到的结果只有name = A
 */
public class InvokeAllTest2 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        List<Callable<String>> tasks = new ArrayList<>();
        tasks.add(new CallableClass("A"));
        tasks.add(new CallableClass("B"));

        List<Future<String>> futures = executorService.invokeAll(tasks, 1, TimeUnit.SECONDS);

        for (Future<String> future : futures) {
            if (future.isDone() && !future.isCancelled()) {
                System.out.println(future.get());
            }
        }

        executorService.shutdown();
    }
}

在这里插入图片描述

代码:chapter24#InvokeAllTest2

invokeAny(Collection<? extends Callable> tasks)

执行给定的任务,返回其中一个最先成功完成的任务的结果

/**
 * 实现Callable<String>接口,并且返回String类型的结果,返回结果具体情况具体分析
 */
public class CallableClass implements Callable<String> {
    String name;

    public CallableClass(String name) {
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        String name = this.name;
        if (this.name.equals("A")) {
            Thread.sleep(2000);
        }
        return "Name = " + this.name;
    }
}
/**
 * 因为name = A的任务需要等待2s的时间
 * 所以B会先于A执行完
 * 最终得到的结果是:Name = B
 */
public class InvokeAny {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        List<Callable<String>> tasks = new ArrayList<>();
        tasks.add(new CallableClass("A"));
        tasks.add(new CallableClass("B"));
        tasks.add(new CallableClass("C"));

        String futureResult = executorService.invokeAny(tasks);

        System.out.println("Result = " + futureResult);
    }
}

在这里插入图片描述

代码:chapter24#InvokeAnyTest

invokeAny(Collection<? extends Callable> tasks, long timeout, TimeUnit unit)

执行给定的任务,如果在超时之前完成任务,则返回任务中的结果。
任务超时的话,则会抛出Exception。

/**
 * 实现Callable<String>接口,并且返回String类型的结果,返回结果具体情况具体分析
 */
public class CallableClass implements Callable<String> {
    String name;

    public CallableClass(String name) {
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        String name = this.name;
        if (this.name.equals("B")) {
            Thread.sleep(2000);
        }
        return "Name = " + this.name;
    }
}
/**
 * 执行name = B的任务需要2s的时间
 * 我们期待在1s的时间拿到任务的结果
 * 此时会抛出Exception
 */
public class InvokeAny2 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        List<Callable<String>> tasks = new ArrayList<>();
        tasks.add(new CallableClass("B"));

        String futureResult = null;
        try {
            futureResult = executorService.invokeAny(tasks, 1, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            futureResult = "Exception";
        }

        System.out.println(futureResult);

        executorService.shutdown();
    }
}

在这里插入图片描述

代码:chapter24#InvokeAnyTest2

shutdown()

有序关闭之前触发的任务,不处理后续新加的任务。

isShutdown()

如果此执行程序已关闭,则返回true。

/**
 * 执行任务,任务完成时间需要:2s
 * 在调用shutdown()前后,查看isShutdown()的值的前后变化
 */
public class IsShutdownTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        executorService.execute(new Thread(() -> {
            try {
                System.out.println("Sub thread is running..");
                Thread.sleep(2000);
                System.out.println("Sub thread is down..");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));

        Thread.sleep(1000);

        System.out.println("Before execute shutdown method, " + "isShutdown = " + executorService.isShutdown());

        executorService.shutdown();
        System.out.println("After execute shutdown method, " + "isShutdown = " + executorService.isShutdown());

    }
}

在这里插入图片描述

代码:chapter24#IsShutdownTest

isTerminated()

isTerminated()需要所有的任务都结束了,才会返回true。
isShutdown()只要程序调用了shundown()后,就会返回true。

/**
 * 执行任务,任务完成时间需要:2s
 * 在调用shutdown()前后,查看isTerminated()的值的前后变化
 */
public class IsTerminatedTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        executorService.execute(new Thread(() -> {
            try {
                System.out.println("Sub thread is running..");
                Thread.sleep(2000);
                System.out.println("Sub thread is down..");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }));

        System.out.println("Before execute shutdown method, " + "isTerminated = " + executorService.isTerminated());

        executorService.shutdown();
        System.out.println("After execute shutdown method, but tasks still running, " + "isTerminated = " + executorService.isTerminated());
        Thread.sleep(3000);
        System.out.println("After execute shutdown method and tasks is down, " + "isTerminated = " + executorService.isTerminated());
    }

}

在这里插入图片描述

代码:chapter24#IsTerminatedTest

shutdownNow()

尝试停止所有正在执行的任务,中止正在等待的任务的处理,并返回正在等待执行的任务的列表

/**
 * 给定一个单线程
 * 提交5次执行的任务
 * 任务中:
 * (1)如果 随机数 % 2 == 0,则线程进入睡眠5s
 * (2)如果 随机数 % 2 != 0,则线程立刻执行完毕
 * 主线程中,睡眠1s后,查看待执行的线程数量
 */
public class ShutdownNowTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 5; i++) {
            executorService.submit(new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " is running..");

                    int result = (int) (Math.random() * 10);
                    System.out.println(Thread.currentThread().getName() + " result = " + result);
                    if (result % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + " is sleeping..");
                        Thread.sleep(5000);
                    }

                    System.out.println(Thread.currentThread().getName() + " is down..");
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " throws exception");
                }
            }));
        }

        Thread.sleep(1000);
        List<Runnable> runnables = executorService.shutdownNow();
        System.out.println("Waiting list size = " + runnables.size());
    }
}

总共有5个任务要执行,前2个任务都是瞬间完成了,第3个任务进入了睡眠,第4、5个任务处于待执行的阶段。
所以结果是:2个任务执行完成,1个任务进入睡眠,2个任务待执行。
在执行了shutdownNow()后,进入睡眠的线程任务会被中断,并抛出异常
在这里插入图片描述

代码:chapter24#ShutdownNowTest

submit(Callable task)

提交待执行的任务,并且得到任务返回的结果。

public class SubmitTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // submit(Callable<T> task)
        for (int i = 0; i < 5; i++) {
            Future<String> future = executorService.submit(new CallableClass("A"));
            System.out.println(future.get());
        }
    }
}

在这里插入图片描述

代码:chapter24#SubmitTest

submit(Runnable task)

提交一个Runnable任务用于执行,并返回一个代表该任务的Future。(不知道这个有什么作用)
future.get() = null

public class SubmitTest2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // submit(Runnable task)
        for (int i = 0; i < 5; i++) {
            Future future = executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("TEST");
                }
            });

            System.out.println(future);
        }
    }
}

在这里插入图片描述

代码:chapter24#SubmitTest2

submit(Runnable task, T result)

同上,但是可以自己指定返回的结果是什么。
future.get() = result

public class SubmitTest3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        // submit(Runnable task, T result)
        for (int i = 0; i < 5; i++) {
            Future future = executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("TEST");
                }
            }, "I'm Result");

            System.out.println(future.get());
        }
    }
}

在这里插入图片描述

代码:chapter24#SubmitTest3

Future

Future表示异步执行结果。Future提供了一些方法,以用于查看任务是否执行完成。

  • 在任务完成后,可以通过get()的方式得到返回的结果
  • 可以用cancel()的方式取消任务
  • 任务一旦完成后,将不能够再被取消掉

get()

将会阻塞线程,直到拿到返回值或者抛出异常

get(long timeout, TimeUnit unit)

在指定的时间内拿不到返回值,则抛出java.util.concurrent.TimeoutException异常

/**
 * 在符合this.number % 2 == 0条件下
 * 线程将会睡眠5s
 */
public class CallableTestClass implements Callable<String> {

    int number;

    public CallableTestClass(int number) {
        this.number = number;
    }

    @Override
    public String call() throws Exception {
        if (this.number % 2 == 0) {
            Thread.sleep(5000);
        }
        return "Current thread name = " + Thread.currentThread().getName();
    }
}
/**
 * 设定get(long timeout, TimeUnit unit)在1s中获取任务返回值,否则抛出异常
 */
public class FutureGetTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 1; i <= 2; i++) {
            CallableTestClass callableTestClass = new CallableTestClass(i);
            Future future = executorService.submit(callableTestClass);

            System.out.println("Method get(long timeout, TimeUnit unit) -> " + future.get(1, TimeUnit.SECONDS));
            System.out.println("Method get() -> " + future.get());
        }
    }
}

在这里插入图片描述
关于Future.cancel(false)

代码:chapter25#FutureGetTest

参考资料

书:《Java多线程编程核心技术》— 高洪岩
Interface ExecutorService
廖雪峰的多线程学习资料
Java 四种线程池
Java Platform SE 7
Class CompletableFuture
CompletableFuture
Java Fork/Join 框架

代码

https://gitee.com/Protector_hui/concurrency-study.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值