java多线程

1.实现多线程的方法有多少种:

实现runnable接口, 继承thread类

runnable方法更好,:

1)从代码架构角度和thread类解耦,

2)不需要新建线程节省资源,

3)java单继承,影响继承其他类

区别:runnable接口是调用传入的target的run,继承thread是重写了run

总结:创建线程只有一种方法就是构造thread类,实现线程的执行单元有两种方式:

方式一:实现runnable接口的run方法,并把runnable实例传给thread类

方式二:重写thread类的run方法

2.正确的线程启动方式:

两次调用start方法会出现什么:异常,start有状态检查Threadstatus = 0,

为什么不直接调用run:start是真正启动了,native start0, 经历生命周期,run只是方法

3.如何停止线程:

使用interrupt通知

两种最佳实践:

1)catch了之后的优先选择:在方法签名中抛出异常,那么在run方法中就会强制try catch

public class Right implements Runnable{
​
    @Override
    public void run() {
        while (true){
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    private void throwInMethod() throws InterruptedException {
​
        Thread.sleep(2000);
​
    }
​
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Right());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

2)在catch子语句中调用Thread.currentThread().interrupt()来恢复设置中断状态,以便于在后续的执行中,依然能够检查到刚才发生了中断,

public class Right2 implements Runnable{
​
    @Override
    public void run() {
        while (true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println("end");
                break;
            }
​
            throwInMethod();
        }
    }
​
    private void throwInMethod()  {
​
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
​
    }
​
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Right2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

错误的方法:stop, suspend, resume

volatile 设置boolean标记,无法处理长时间阻塞:

例如:生产者生产的快,消费者消费的慢,队列满了以后生产者会阻塞

package thread.stopThread.volatileDemo;
​
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
​
/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class WrongWayCant {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<Object> storage = new ArrayBlockingQueue<>(10);
        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);
        Consumer consumer = new Consumer(storage);
        while (consumer.need()){
            System.out.println(consumer.storage.take()+"被消费了");
            Thread.sleep(100);
        }
        System.out.println("不再需要");
        producer.canceled = true;
    }
}
class Producer implements Runnable{
    volatile boolean canceled = false;
    BlockingQueue storage;
​
    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }
​
    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    storage.put(num);
                    System.out.println(num + "是100的倍数放到仓库");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("producer end");
        }
    }
}
class Consumer {
    BlockingQueue storage;
​
    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }
​
    public boolean need() {
        if (Math.random() > 0.95) {
            return false;
        } else return true;
    }
}
​

面试题:

如何停止线程:

用中断来请求,要请求方、被停止方、子方法被调用方相互配合,最后说错误的方法

如何处理不可中断的阻塞:

根据不同的类调用不同的方法

4.线程生命周期:

习惯把右边三种称为阻塞状态

面试题:

线程状态,生命周期

5.Thread和Object类的方法:

wait方法, notify方法:wait释放了锁

public class Wait {
    public static final Object object = new Object();
    static class Thread1 extends Thread{
        public void run(){
            synchronized (object){
                System.out.println(Thread.currentThread().getName()+"开始执行");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"获取到了锁");
            }
        }
    }
    static class Thread2 extends Thread{
        public void run(){
            synchronized (object){
                object.notify();
                System.out.println(Thread.currentThread().getName()+"  notify");
            }
        }
    }
​
    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
        thread1.start();
        Thread.sleep(200);
        thread2.start();
    }
}

notify和notifyall:

public class WaitAll implements Runnable {
    public static final Object resourceA = new Object();
    @Override
    public void run() {
        synchronized (resourceA){
            System.out.println(Thread.currentThread().getName()+"get A");
            try {
                System.out.println(Thread.currentThread().getName()+" start");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName()+" end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
        }
    }
​
    public static void main(String[] args) throws InterruptedException {
        WaitAll runna = new WaitAll();
        Thread thread1 = new Thread(runna);
        Thread thread2 = new Thread(runna);
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    resourceA.notifyAll();
                    System.out.println("c notify");
                }
            }
        });
        thread2.start();
        thread1.start();
        Thread.sleep(200);
        thread3.start();
    }
}

wait只释放当前的锁:

package thread.Wait;
​
/**
 * @param:
 * @return:
 * @time: 2021/7/29
 */
public class WaitAll2 implements Runnable {
    public static final Object resourceA = new Object();
    public static final Object resourceB = new Object();
    @Override
    public void run() {
        synchronized (resourceA){
            System.out.println(Thread.currentThread().getName()+"get A");
            synchronized (resourceB){
                System.out.println(Thread.currentThread().getName()+"get B");
                try {
                    resourceA.wait();
                    System.out.println(Thread.currentThread().getName()+" A end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
​
            }
​
        }
    }
​
    public static void main(String[] args) throws InterruptedException {
        WaitAll2 runna = new WaitAll2();
        Thread thread1 = new Thread(runna);
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resourceA){
                    System.out.println("rel A");
                    synchronized (resourceB){
                        System.out.println("rel B");
                    }
                }
            }
        });
​
        thread1.start();
        thread2.start();
    }
}
​

wait原理:

生产者消费者:

​
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
public class ProModel {
    static class EventStorage{
        private int maxSize;
        private LinkedList<Date> storage;
​
        public EventStorage() {
            this.maxSize = 10;
            this.storage = new LinkedList<>();
        }
        synchronized void put(){
            while (storage.size() == maxSize){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            storage.add(new Date());
            System.out.println("有了"+storage.size()+"个");
            notify();
        }
        synchronized void take(){
            while (storage.size() == 0){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
​
            System.out.println("取到了"+storage.poll());
            System.out.println("剩下了"+storage.size()+"个");
​
            notify();
        }
    }
    static class Producer implements Runnable{
        private EventStorage storage;
​
        public Producer(EventStorage storage) {
            this.storage = storage;
        }
​
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                storage.put();
            }
        }
    }
    static class Consumer implements Runnable{
        private EventStorage storage;
​
        public Consumer(EventStorage storage) {
            this.storage = storage;
        }
​
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                storage.take();
            }
        }
    }
​
    public static void main(String[] args) {
        EventStorage storage = new EventStorage();
        Producer producer = new Producer(storage);
        Consumer consumer = new Consumer(storage);
        Thread thread1 = new Thread(producer);
        Thread thread = new Thread(consumer);
        thread.start();
        thread1.start();
    }
​
}

面试:交替打印0-100

package thread.Wait;
​
/**
 * @param:
 * @return:
 * @time: 2021/7/29
 */
public class PrintOddEven implements Runnable{
    private static int count;
    private static final Object lock = new Object();
    public static void main(String[] args) {
        new Thread(new PrintOddEven(), "偶数").start();
        new Thread(new PrintOddEven(), "奇数").start();
    }
​
    public void run() {
        while (count <= 100) {
            synchronized (lock) {
                    System.out.println(Thread.currentThread().getName()
                            + ":" + count++);
                    lock.notify();
                    if (count <= 100)
                    {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
​
            }
        }
    }
}
​

面试:生产者消费者

为什么wait需要在同步代码块中使用,sleep不需要:通信可靠,防止死锁

为什么wait三个定义在object,sleep定义在thread: 每个类都是一把锁,对象头保存了monitor预留

调用Thread.wait会怎样:

sleep方法让线程进入waiting,不释放锁,休眠期间被中断抛出异常并清除中断。

面试题:wait, sleep,异同

相同:阻塞,响应中断

不同:wait在同步方法中,释放锁,指定时间,所属类

join:新的线程加入了我们,所以要等他执行完再出发。

用法:main等待子线程执行完毕

join期间线程处于waiting状态

yield方法:释放我的cpu时间片

6.线程的各个属性:

守护线程和普通线程:整体无区别,是否影响jvm退出,作用不同

是否需要设置为守护线程:不应该

7.未捕获异常:

主线程可以发现异常,子线程不可以:

子线程异常无法用传统方法捕获

解决方法:

1)手动在每个run里try

2)uncaughtExceptionHandler:

自己实现处理异常:

public class MyUncaughtException implements Thread.UncaughtExceptionHandler {
    private String name;

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

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Logger logger = Logger.getAnonymousLogger();
        logger.log(Level.WARNING, "异常"+t.getName(), e);
        System.out.println(name + "捕获了异常"+t.getName()+"异常"+e);
    }
}
public class Use implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtException("捕获器1"));

        new Thread(new Use(), "MyThread-1").start();
        Thread.sleep(300);
        new Thread(new Use(), "MyThread-2").start();
        Thread.sleep(300);
        new Thread(new Use(), "MyThread-3").start();
        Thread.sleep(300);
        new Thread(new Use(), "MyThread-4").start();
    }


    @Override
    public void run() {
        throw new RuntimeException();
    }
}

8.多线程导致的问题:

线程安全:

线程不安全:

1、运行结果错误:a++多线程消失请求现象

public class Multi implements Runnable{
    static Multi instance = new Multi();
    int index;
    static AtomicInteger realIndex = new AtomicInteger();
    static AtomicInteger wrongCount = new AtomicInteger();
    final boolean[] marked = new boolean[100000];
    static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
    static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("表面:"+instance.index);
        System.out.println("real : " + realIndex.get());
        System.out.println("error : " + wrongCount.get());
    }
    @Override
    public void run() {
        marked[0]=true;
        for (int i = 0; i < 10000; i++) {
            try {
                cyclicBarrier2.reset();
                cyclicBarrier1.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            index++;
            try {
                cyclicBarrier1.reset();
                cyclicBarrier2.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            realIndex.incrementAndGet();
            synchronized (instance){
                if (marked[index] && marked[index-1]){
                    System.out.println("发生错误"+index);
                    wrongCount.incrementAndGet();
                }
            }

            marked[index] = true;
        }
    }
}

2、活跃性问题:死锁、活锁、饥饿

public class Multi2 implements Runnable{
    int flag;
    static Object o1 = new Object();
    static Object o2 = new Object();

    public static void main(String[] args) {
        Multi2 r1 = new Multi2();
        Multi2 r2 = new Multi2();
        r1.flag = 1;
        r2.flag = 0;
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);
        thread1.start();
        thread2.start();
    }
    @Override
    public void run() {
        if (flag==1){
            synchronized (o1){
                System.out.println("flag = "+flag);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println("1");
                }
            }
        }
        if (flag==0){
            synchronized (o2){
                System.out.println("flag = "+flag);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println("0");
                }
            }
        }
    }
}

3、对象发布和初始化:

1)、方法返回一个private对象

public class Multi3 {
    private Map<String , String > states;

    public Multi3() {
        this.states = new HashMap<>();
        states.put("1", "周一");
    }
    public Map<String , String > getStates(){
        return states;
    }

    public static void main(String[] args) {
        Multi3 multi3 = new Multi3();
        Map<String, String> states = multi3.getStates();
        states.remove("1");
        System.out.println(states.get("1"));
    }
}

2)、未完成初始化把对象提供给外界

构造函数没初始化

class Point{
    private final int x, y;

    public Point(int x, int y) throws InterruptedException {
        this.x = x;
        Multi4.point = this;
        Thread.sleep(10);
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}
public class Multi4 {
    static Point point;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new PointMaker());
        thread.start();
        Thread.sleep(5);
        if (point != null) System.out.println(point);
    }
}
class PointMaker implements Runnable{
    @Override
    public void run() {
        try {
            new Point(1, 1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注册监听

public class MultiThreadsError5 {

    int count;

    public MultiThreadsError5(MySource source) {
        source.registerListener(new EventListener() {
            @Override
            public void onEvent(Event e) {
                System.out.println("\n我得到的数字是" + count);
            }

        });
        for (int i = 0; i < 10000; i++) {
            System.out.print(i);
        }
        count = 100;
    }

    public static void main(String[] args) {
        MySource mySource = new MySource();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mySource.eventCome(new Event() {
                });
            }
        }).start();
        MultiThreadsError5 multiThreadsError5 = new MultiThreadsError5(mySource);
    }

    static class MySource {

        private EventListener listener;

        void registerListener(EventListener eventListener) {
            this.listener = eventListener;
        }

        void eventCome(Event e) {
            if (listener != null) {
                listener.onEvent(e);
            } else {
                System.out.println("还未初始化完毕");
            }
        }

    }

    interface EventListener {

        void onEvent(Event e);
    }

    interface Event {

    }
}

构造函数中运行线程

public class Multi6 {
    private Map<String , String > states;

    public Multi6() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                states = new HashMap<>();
                states.put("1", "周一");
            }
        }).start();

    }
    public Map<String , String > getStates(){
        return states;
    }

    public static void main(String[] args) throws InterruptedException {
        Multi6 multi6 = new Multi6();
        Thread.sleep(100);
        String s = multi6.getStates().get("1");
        System.out.println(s);
    }
}

解决方法:

1、返回副本,解决(方法返回一个private对象)

public Map<String , String > getImStates(){
    return new HashMap<>(states);
}

2、工厂模式修复(注册监听)

/**
 * 描述:     用工厂模式修复刚才的初始化问题
 */
public class MultiThreadsError7 {

    int count;
    private EventListener listener;

    private MultiThreadsError7(MySource source) {
        listener = new EventListener() {
            @Override
            public void onEvent(MultiThreadsError5.Event e) {
                System.out.println("\n我得到的数字是" + count);
            }

        };
        for (int i = 0; i < 10000; i++) {
            System.out.print(i);
        }
        count = 100;
    }

    public static MultiThreadsError7 getInstance(MySource source) {
        MultiThreadsError7 safeListener = new MultiThreadsError7(source);
        source.registerListener(safeListener.listener);
        return safeListener;
    }

    public static void main(String[] args) {
        MySource mySource = new MySource();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mySource.eventCome(new MultiThreadsError5.Event() {
                });
            }
        }).start();
        MultiThreadsError7 multiThreadsError7 = new MultiThreadsError7(mySource);
    }

    static class MySource {

        private EventListener listener;

        void registerListener(EventListener eventListener) {
            this.listener = eventListener;
        }

        void eventCome(MultiThreadsError5.Event e) {
            if (listener != null) {
                listener.onEvent(e);
            } else {
                System.out.println("还未初始化完毕");
            }
        }

    }

    interface EventListener {

        void onEvent(MultiThreadsError5.Event e);
    }

    interface Event {

    }
}

四种情况总结:

1、访问共享的变量资源

2、所有依赖时序的操作

3、数据之间存在捆绑关系 ip和端口号

4、使用其他类的时候,对方没有声明线程安全 hashmap

多线程性能

调度:上下文切换:内核再cpu上对于进程线程进行

1)存储进程状态 2)检索恢复进程状态 3)跳转程序计数器指向的位置

  • 上下文:保存现场

  • 缓存开销:缓存失效

  • 密集上下文切换时机:抢锁,io

面试总结:

java内存模型

1、jvm内存结构 ,java内存模型,java对象模型

jvm内存结构和java虚拟机的运行时区域有关

java内存模型和并发有关

java对象模型和java对象在虚拟机中的表现形式有关

JMM规范

重排序

代码

 */
public class OutOfOrderExecution {

    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for (; ; ) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            CountDownLatch latch = new CountDownLatch(3);

            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        latch.countDown();
                        latch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    a = 1;
                    x = b;
                }
            });
            Thread two = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        latch.countDown();
                        latch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    b = 1;
                    y = a;
                }
            });
            two.start();
            one.start();
            latch.countDown();
            one.join();
            two.join();

            String result = "第" + i + "次(" + x + "," + y + ")";
            if (x == 1 && y == 1) {
                System.out.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }


}

可见性

/**
 * 描述:     演示可见性带来的问题
 */
public class FieldVisibility {
    //错误的b=3;a=1

     int a = 1;
     int b = 2;

    private void change() {
        a = 3;
        b = a;
    }


    private void print() {
        System.out.println("b=" + b + ";a=" + a);
    }

    public static void main(String[] args) {
        while (true) {
            FieldVisibility test = new FieldVisibility();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }

    }


}

volatile 强制线程看到更改的变量

主内存和本地内存

happens-before

原则:锁操作,volatile,join

volatile

不适用a++:

/**
 * 描述:     不适用于volatile的场景
 */
public class NoVolatile implements Runnable {

    volatile int a;
    AtomicInteger realA = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Runnable r =  new NoVolatile();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(((NoVolatile) r).a);
        System.out.println(((NoVolatile) r).realA.get());
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            a++;
            realA.incrementAndGet();
        }
    }
}

不适用于依赖于之前的状态

/**
 * 描述:     volatile不适用的情况2
 */
public class NoVolatile2 implements Runnable {

    volatile boolean done = false;
    AtomicInteger realA = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Runnable r =  new NoVolatile2();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(((NoVolatile2) r).done);
        System.out.println(((NoVolatile2) r).realA.get());
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            flipDone();
            realA.incrementAndGet();
        }
    }

    private void flipDone() {
        done = !done;
    }
}

适用纯赋值:

/**
 * 描述:     volatile适用的情况1
 */
public class UseVolatile1 implements Runnable {

    volatile boolean done = false;
    AtomicInteger realA = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Runnable r =  new UseVolatile1();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(((UseVolatile1) r).done);
        System.out.println(((UseVolatile1) r).realA.get());
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            setDone();
            realA.incrementAndGet();
        }
    }

    private void setDone() {
        done = true;
    }
}

适用触发器:

/**
 * 描述:     
 */
public class FieldVisibility {

    int a = 1;
    volatile int b = 2;

    private void change() {
        a = 3;
        b = a;
    }


    private void print() {
        System.out.println("b=" + b + ";a=" + a);
    }

    public static void main(String[] args) {
        while (true) {
            FieldVisibility test = new FieldVisibility();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }

    }


}

对比syn:

原子性

单例模式

饿汉式:jvm保证线程安全,类一加载就完成了

public class Singleton1 {
    private final static Singleton1 INSTANCE = new Singleton1();
    private Singleton1(){

    }
    public static Singleton1 getInstance(){
        return INSTANCE;
    }
}
public class Singleton2 {
    private final static Singleton2 INSTANCE;
    static {
        INSTANCE = new Singleton2();
    }
    private Singleton2(){

    }
    public static Singleton2 getInstance(){
        return INSTANCE;
    }
}

懒汉式:

线程安全,性能不好

public class Singleton3 {
    private  static Singleton3 INSTANCE;

    private Singleton3(){

    }
    public synchronized static Singleton3 getInstance(){
        if (INSTANCE == null){
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }
}

线程不安全

public class Singleton4 {
    private  static Singleton4 INSTANCE;

    private Singleton4(){

    }
    public  static Singleton4 getInstance(){
        if (INSTANCE == null){
            INSTANCE = new Singleton4();
        }
        return INSTANCE;
    }
}
public class Singleton5 {
    private  static Singleton5 INSTANCE;
    private Singleton5(){

    }
    // 解锁后其余线程会继续创建覆盖
    public  static Singleton5 getInstance(){
        if (INSTANCE == null){
            synchronized (Singleton5.class){
                INSTANCE = new Singleton5();
            }
        }
        return INSTANCE;
    }
}

推荐使用:双重检查,

public class Singleton6 {
    private volatile  static Singleton6 INSTANCE;
    private Singleton4(){

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

一次检查,第四种会创建多次实例,第三种安全但是性能不好

最安全的,volatile可见性保证第二个线程能看到第一个线程给Instance做的改变,禁止重排序保证创建对象不会返回空指针

静态内部类:

public class Singleton7 {
    private volatile   static Singleton7 INSTANCE;
    private Singleton7(){

    }
    private static class SingletonInstance{
        private static final Singleton7 INSTANCE = new Singleton7();
    }
    public  static Singleton7 getInstance(){
       return SingletonInstance.INSTANCE;
    }
}

枚举:推荐用

public enum  Singleton8 {
   INSTANCE;
   public void whatever(){
       
   }
}

总结:

饿汉:简单,但是没有懒加载

懒汉:有线程安全问题

静态内部类:可用

双重检查:面试用

枚举:最好,写法简单,线程安全,反编译是静态对象,避免反序列化破坏单例

面试题:

总结

死锁

public class DeadLock implements Runnable{
    static Object o1 = new Object();
    static Object o2 = new Object();
    int flag;

    @Override
    public void run() {
        if (flag == 1){
            synchronized (o1){
                System.out.println("flag = "+flag);
                synchronized (o2){
                    System.out.println("拿到o2");
                }
            }
        }
        if (flag == 0){
            synchronized (o2){
                System.out.println("flag = "+flag);
                synchronized (o1){
                    System.out.println("拿到o1");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock lock1 = new DeadLock();
        DeadLock lock2 = new DeadLock();
        lock1.flag = 1;
        lock2.flag = 0;
        Thread thread1 = new Thread(lock1);
        Thread thread2 = new Thread(lock2);
        thread1.start();
        thread2.start();;
    }
}

银行转账:

public class DeadLock1 implements Runnable{
    static Account a = new Account(500);
    static Account b = new Account(500);
    int flag;
    static class Account{
        int balance;

        public Account(int balance) {
            this.balance = balance;
        }
    }
    @Override
    public void run() {
        if (flag == 1){
            try {
                transferMoney(a, b, 200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (flag == 0){
            try {
                transferMoney(b, a, 200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void transferMoney(Account from, Account to, int amount) throws InterruptedException {
        synchronized (from){
//            Thread.sleep(500);
            synchronized (to){
                if (from.balance - amount < 0){
                    System.out.println("余额不足");
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println("成功转账"+amount+"元");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DeadLock1 lock1 = new DeadLock1();
        DeadLock1 lock2 = new DeadLock1();
        lock1.flag = 1;
        lock2.flag = 0;
        Thread thread1 = new Thread(lock1);
        Thread thread2 = new Thread(lock2);
        thread1.start();
        thread2.start();;
        thread1.join();
        thread2.join();
        System.out.println("a的"+a.balance);
        System.out.println("b的"+b.balance);
    }
}

定位死锁

threadMXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        if (deadlockedThreads != null && deadlockedThreads.length > 0) {
            for (int i = 0; i < deadlockedThreads.length; i++) {
                ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
                System.out.println("发现死锁" + threadInfo.getThreadName());
            }
        }

修复死锁

避免策略:

/**
 * 描述:     转账时候遇到死锁,一旦打开注释,便会发生死锁
 */
public class TransferMoney implements Runnable {

    int flag = 1;
    static Account a = new Account(500);
    static Account b = new Account(500);
    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 0;
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("a的余额" + a.balance);
        System.out.println("b的余额" + b.balance);
    }

    @Override
    public void run() {
        if (flag == 1) {
            try {
                transferMoney(a, b, 200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (flag == 0) {
            try {
                transferMoney(b, a, 200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void transferMoney(Account from, Account to, int amount) throws InterruptedException {
        class Helper{
            public void transfer(){
                if (from.balance - amount < 0){
                    System.out.println("余额不足");
                }
                from.balance -= amount;
                to.balance += amount;
                System.out.println("成功转账"+amount+"元");
            }
        }
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);
        if (fromHash < toHash){
            synchronized (from){
                synchronized (to){
                    new Helper().transfer();
                }
            }
        }
        else if (fromHash > toHash){
            synchronized (to){
                synchronized (from){
                    new Helper().transfer();
                }
            }
        }else {
            synchronized (lock){
                synchronized (to){
                    synchronized (from){
                        new Helper().transfer();
                    }
                }
            }
        }
    }
    static class Account {

        public Account(int balance) {
            this.balance = balance;
        }

        int balance;

    }
}

哲学家问题

public class Dining {


    public static class Philosopher implements Runnable{
        private final Object leftChopstick;
        private final Object rightChopstick;

        public Philosopher(Object leftChopstick, Object rightChopstick) {
            this.leftChopstick = leftChopstick;
            this.rightChopstick = rightChopstick;
        }

        private void doActon(String acton) throws InterruptedException {
            System.out.println(Thread.currentThread().getName()+" "+acton);
            Thread.sleep((long) (Math.random()*10));
        }
        @Override
        public void run() {
            try {
                while (true){
                    doActon("thinking ");
                    synchronized (leftChopstick){
                        doActon("拿左筷子");
                        synchronized (rightChopstick){
                            doActon("拿右筷子");
                            doActon("吃饭");
                            doActon("放下右筷子");
                        }
                        doActon("放下左筷子");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public static void main(String[] args) {
            Philosopher[] philosophers = new Philosopher[5];
            Object[] chopsticks = new Object[philosophers.length];
            for (int i = 0; i < 5; i++) {
                chopsticks[i] = new Object();
            }
            for (int i = 0; i < 5; i++) {
                Object left = chopsticks[i];
                Object right = chopsticks[(i+1)%5];
                philosophers[i] = new Philosopher(left, right);
                new Thread(philosophers[i], "哲学家"+(i+1)).start();
            }

        }
    }

解决:一个哲学家更换拿的顺序

if (i == philosophers.length-1)
    philosophers[i] = new Philosopher(right, left);
else

检测与恢复

实际开发避免死锁

lock:

public class tryLock implements Runnable {
    int flag;
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (flag == 1){
                try {
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)){
                        System.out.println("线程1获取1");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock2.tryLock(800, TimeUnit.MILLISECONDS)){
                            System.out.println("线程1获取2");
                            System.out.println("线程1获取两把");
                            lock2.unlock();
                            lock1.unlock();
                            break;
                        }else {
                            System.out.println("线程1获取2失败");
                            lock1.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    }else {
                        System.out.println("线程1获取1失败");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (flag == 0){
                try {
                    if (lock2.tryLock(800, TimeUnit.MILLISECONDS)){
                        System.out.println("线程2获取2");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock1.tryLock(800, TimeUnit.MILLISECONDS)){
                            System.out.println("线程2获取1");
                            System.out.println("线程2获取两把");
                            lock2.unlock();
                            lock1.unlock();
                            break;
                        }else {
                            System.out.println("线程2获取1失败");
                            lock2.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    }else {
                        System.out.println("线程2获取2失败");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

    }
    public static void main(String[] args) {
        tryLock r1 = new tryLock();
        tryLock r2 = new tryLock();
        r1.flag = 1;
        r2.flag = 0;
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

活锁

/**
 * 描述:     演示活锁问题
 */
public class LiveLock {

    static class Spoon {

        private Diner owner;

        public Spoon(Diner owner) {
            this.owner = owner;
        }

        public Diner getOwner() {
            return owner;
        }

        public void setOwner(Diner owner) {
            this.owner = owner;
        }

        public synchronized void use() {
            System.out.printf("%s吃完了!", owner.name);


        }
    }

    static class Diner {

        private String name;
        private boolean isHungry;

        public Diner(String name) {
            this.name = name;
            isHungry = true;
        }

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                if (spoon.owner != this) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                Random random = new Random();
                if (spouse.isHungry && random.nextInt(10) < 9) {
                    System.out.println(name + ": 亲爱的" + spouse.name + "你先吃吧");
                    spoon.setOwner(spouse);
                    continue;
                }

                spoon.use();
                isHungry = false;
                System.out.println(name + ": 我吃完了");
                spoon.setOwner(spouse);

            }
        }
    }


    public static void main(String[] args) {
        Diner husband = new Diner("牛郎");
        Diner wife = new Diner("织女");

        Spoon spoon = new Spoon(husband);

        new Thread(new Runnable() {
            @Override
            public void run() {
                husband.eatWith(spoon, wife);
            }
        }, "牛郎").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                wife.eatWith(spoon, husband);
            }
        }, "织女").start();
    }
}

饥饿

面试

syn关键字:

用法:对象锁和类锁

对象锁包括方法锁和同步代码块锁:

同步代码块:this和自己建对象,手动指定锁的对象

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest implements Runnable{
    static ThreadTest instance = new ThreadTest();
    Object lock1 = new Object();
    Object lock2 = new Object();
    @Override
    public void run() {
        synchronized (lock1){
            System.out.println("lock1 "+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock1 end");
        }
        synchronized (lock1){
            System.out.println("lock2"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock2 end");
        }

    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

方法锁:修饰普通方法,锁对象默认为this

类锁:是Class对象的锁,只能在同一时刻被一个对象拥有

加在static方法上:

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest2 implements Runnable{
    static ThreadTest2 instance1 = new ThreadTest2();
    static ThreadTest2 instance2 = new ThreadTest2();

    @Override
    public void run() {
        try {
            method();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static synchronized void method() throws InterruptedException {
        System.out.println("方法"+Thread.currentThread().getName());
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName()+" end");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

.class代码块:

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest3 implements Runnable{
    static ThreadTest3 instance1 = new ThreadTest3();
    static ThreadTest3 instance2 = new ThreadTest3();

    @Override
    public void run() {
        try {
            method();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public  void method() throws InterruptedException {
        synchronized (ThreadTest3.class){
            System.out.println("方法"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" end");
        }

    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

面试常考:

1.两个线程同时访问一个对象的同步方法:非并行

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest4 implements Runnable{
    static ThreadTest4 instance = new ThreadTest4();

    @Override
    public void run() {
        synchronized (this){
            System.out.println("this"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock end");
        }

    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

2.两个线程同时访问2个对象的同步方法:非并行

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest4 implements Runnable{
    static ThreadTest4 instance1 = new ThreadTest4();
    static ThreadTest4 instance2 = new ThreadTest4();
    @Override
    public void run() {
        synchronized (this){
            System.out.println("this"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock1 end");
        }

    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

3.两个线程同时访问syn静态方法:非并行

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest2 implements Runnable{
    static ThreadTest2 instance1 = new ThreadTest2();
    static ThreadTest2 instance2 = new ThreadTest2();

    @Override
    public void run() {
        try {
            method();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static synchronized void method() throws InterruptedException {
        System.out.println("方法"+Thread.currentThread().getName());
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName()+" end");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

4.同时访问同步与非同步方法:并行

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest5 implements Runnable{
    static ThreadTest5 instance = new ThreadTest5();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            try {
                method();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            try {
                method1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized   void method() throws InterruptedException {
        {
            System.out.println("方法"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" end");
        }

    }
    public  void method1() throws InterruptedException {
        {
            System.out.println("无锁方法"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" end");
        }

    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

5.访问同一个对象的不同的普通同步方法:非并行

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest6 implements Runnable{
    static ThreadTest6 instance = new ThreadTest6();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            try {
                method();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            try {
                method1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void method() throws InterruptedException {
        {
            System.out.println("方法1"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" end");
        }

    }
    public synchronized   void method1() throws InterruptedException {
        {
            System.out.println("方法2"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" end");
        }

    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

6.同时访问静态syn方法和非静态syn方法:并行

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest7 implements Runnable{
    static ThreadTest7 instance = new ThreadTest7();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            try {
                method();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            try {
                method1();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static synchronized void method() throws InterruptedException {
        {
            System.out.println("静态方法1"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" end");
        }

    }
    public synchronized   void method1() throws InterruptedException {
        {
            System.out.println("方法2"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" end");
        }

    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

7.方法抛出异常,释放锁:并行

package syn;

/**
 * @param:
 * @return:
 * @time: 2021/7/26
 */
public class ThreadTest8 implements Runnable{
    static ThreadTest8 instance = new ThreadTest8();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            try {
                method1();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else {
            try {
                method2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void method1() {
        {
            System.out.println("异常方法1"+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);

            } catch (Exception e) {
                e.printStackTrace();
            }
            throw new RuntimeException();
        }

    }
    public synchronized void method2() throws InterruptedException {
        {
            System.out.println("方法2"+Thread.currentThread().getName());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" end");
        }

    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

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

总结:

1.一把锁只能被一个线程获取,没有拿到锁的必等待,对应1和5

2.每个实例都对应自己的一把锁,不同实例之间不影响,锁对象是.class和static时候,所有对象共同同一把类锁,对应2,3,4,6.

3.抛出异常会释放锁。

被syn的方法里调用没有被syn的方法,不是线程安全的。

syn的性质:

可重入性,不可中断

原理:加锁和释放锁的原理:内置锁

等价:

public synchronized void method(){
       System.out.println("syn");
   }
   void method2(){
       lock.lock();
       try {
           System.out.println("lock");
       }finally {
           lock.unlock();
       }
   }

可重入原理:加锁次数计数器

jvm跟踪对象被加锁的次数,相同的线程在此对象上再次获得锁时候,锁会减少,到0了释放

可见性原理:java内存模型

缺陷:

效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在试图获得锁的线程。

不灵活:加锁和释放的时机单一,每个锁仅有单一的条件,可能是不够的。

无法知道是否成功得到锁

面试题:注意点:锁对象不能为空,作用域不宜过大,避免死锁

Lock和syn选择:线程工具、syn

多线程访问同步方法的各种情况

总结:jvm自动通过monitor加锁解锁,保证同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值