Java 线程

//*****************************************************580***********************************************************************//
//***********                                         进程                                                ***************//
//*******************************************************************************************************************************//
                                       进程是程序的一次执行过程,或是正在运行的一个程序。
                                            是动态过程:有它自身的产生、存在和消亡的过程
//****************************************************580************************************************************************//
//*************                                        线程                                                  **************//
//*******************************************************************************************************************************//
                    1.线程由进程创建的,是进程的一个实体
                    2.一个进程可以拥有多个线程
                    举例: 迅雷下载多个文件   迅雷整体:进程    每个下载任务:对应一个线程线程
                    也就是说:一个进程----> 可以拥有多个线程
//*******************************************************************************************************************************//
//***********************************************************581****************************************************************//
//*******************************************************************************************************************************//
                        1.单线程: 同一个时刻,只允许执行一个线程
                        2.多线程: 同一个时刻,可以执行多个线程
                        比如: 一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
                        3.并发: 同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说:单核cpu实现的多任务就是并发。
                        4.并行: 同一个时刻,多个任务同时执行。多核cpu可以实现并行。 并发和并行
//*************************************************************************************************************************//
//******************************************************581**************************************************************//
//*************************************************************************************************************************//
public class CpuNum {
    public static void main(String[] args) {

        Runtime runtime = Runtime.getRuntime();

        //Runtime.getRuntime() 返回一个 Runtime 对象,该对象代表当前 JVM 实例。
                 // 可以使用这个对象来执行各种操作,例如获取虚拟机参数、启动和停止 JVM 实例、获取平台信息等等。

        //获取当前电脑的cpu数量/核心数
        int cpuNums = runtime.availableProcessors();
        System.out.println("当前有cpu 个数=" + cpuNums);


    }
}
//*************************************************************************************************************************//
//*************************************************************************************************************************//
//*************************************************************************************************************************//
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {

        //创建Cat对象,可以当做线程使用
        Cat cat = new Cat();

        //老韩读源码
        /*
            (1)
            public synchronized void start() {
                start0();
            }
            (2)
            //start0() 是本地方法,是JVM调用, 底层是c/c++实现
            //真正实现多线程的效果, 是start0(), 而不是 run。
               你可以理解成,JVM机通过 start0 底层调用 run !! 而不是直接调用run
         */
            private native void start0();

         */

        cat.start();//启动线程-> 最终会执行cat的run方法

        cat.run();//run方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
                    //所以,我们采用:cat.start();  让Cat自己单独运行

        //说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
        //这时 主线程和子线程是交替执行..
        System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字main
        for(int i = 0; i < 60; i++) {
            System.out.println("主线程 i=" + i);
            //让主线程休眠
            Thread.sleep(1000);
        }
         也就是一个进程结束了,并不意味着它其中的线程结束,一个线程结束也不意味着它其中的子线程结束
    }
}

//老韩说明
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的run方法
/*
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 */



class Cat extends Thread {

    int times = 0;
    @Override
    public void run() {//重写run方法,写上自己的业务逻辑

        while (true) {
            //该线程每隔1秒。在控制台输出 “喵喵, 我是小猫咪”
            System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
            //让该线程休眠1秒 ctrl+alt+t
            try {                //try-catch是保证 :该线程在sleep时还是能感知响应,能够响应中断,不会睡死
                Thread.sleep(1000);      //执行完,有可能线程中断!!!导致程序退出。所以执行try-catch 异常捕捉
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 80) {
                break;//当times 到80, 退出while, 这时线程也就退出..
            }
        }
    }
}
//*************************************************************************************************************************//
//*****************************************************585****************************************************************//
//*************************************************************************************************************************//
                                                 线程基本使用
                                            线程应用案例2-实现Runnable接口
            说明:
                1.java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了
                2.iava设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程

                Thread (类)-------> Runnable (接口) ----------> FunctionalInterface
                            要么,直接继承 Thread,用它的 start方法。
                            要么,实现接口 Runnable,用它的。。。。。      Runnable 只有 run !!但是,不能直接用。不然就是普通方法了
                                                         Thread a = new Thread(dog);  //多写一个~~ 利用静态代理实现。
                                                         a.start();

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();

        dog.start(); //这里不能调用start !!! 因为Runnable 只有 run 方法!!没有start 方法!!!
        //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
        Thread thread = new Thread(dog);
        thread.start();


//   模拟 【静态代理模式】
        Tiger tiger = new Tiger();//实现了 Runnable
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

class Animal {
}

class Tiger extends Animal implements Runnable {

    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫....");
    }
}

//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy

    private Runnable target = null;//属性,类型是 Runnable

    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定(运行类型Tiger)
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start() {
        start0();  //这个方法时真正实现多线程方法
    }

    public void start0() {   //start0() 才是真正调用 run()方法
        run();
    }
}              没有 start0 ,无法执行 run() !!!


class Dog implements Runnable { //通过实现Runnable接口,开发线程

    int count = 0;

    @Override
    public void run() { //普通方法
        while (true) {
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());

            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break;
            }
        }
    }
}

//*************************************************************************************************************************//
//*************************************************************586*****************************************************//
//*************************************************************************************************************************//
public class Thread03 {
    public static void main(String[] args) {

        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();//启动第1个线程
        thread2.start();//启动第2个线程
        //...

    }
}

class T1 implements Runnable {

    int count = 0;

    @Override
    public void run() {
        while (true) {
            //每隔1秒输出 “hello,world”,输出10次
            System.out.println("hello,world " + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 60) {
                break;
            }
        }
    }
}

class T2 implements Runnable {

    int count = 0;

    @Override
    public void run() {
        //每隔1秒输出 “hi”,输出5次
        while (true) {
            System.out.println("hi " + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count == 50) {
                break;
            }
        }
    }
}
//*************************************************************************************************************************//
//*************************************************************************************************************************//
//*************************************************************************************************************************//
                            继承Thread vs 实现Runnable的区别
        1)从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从idk帮助文档我们可以看到Thread类本身就实现了Runnable接口
        2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制


        class T3 implements Runnable{}
        T3 t3 = new T3("hello ");
        Thread thread01 = new Thread(t3);  // 把T3给两个线程。
        Thread threado2 = new Thread(t3);
        threado1.start();  //同时启动两次 ----> 多个线程共享一个资源
        threado2.start();
        System.out,printIn("主线程完毕”);

        class T3 extends Thread{}
        T3 t3 = new T3("hello ");
        t3.start();   //通过类实现的话,只能一个线程独立进行!!!
        t3.start();   //会报错!!!
        System.out,printIn("主线程完毕”);
//*************************************************************************************************************************//
//******************************************************587****************************************************************//
//*************************************************************************************************************************//
                                   * 使用多线程,模拟三个窗口同时售票100张 *

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

        测试
        SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        //这里我们会出现超卖..
        sellTicket01.start();//启动售票线程
        sellTicket02.start();//启动售票线程
        sellTicket03.start();//启动售票线程

    }
}


使用Thread方式

class SellTicket01 extends Thread {

    private static int ticketNum = 100;//让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {

            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }

            //休眠50毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));

        }
    }
}


//************************

* 使用多线程,模拟三个窗口同时售票100张
        */
public class SellTicket {
    public static void main(String[] args) {

        System.out.println("===使用实现接口方式来售票=====");
        SellTicket02 sellTicket02 = new SellTicket02();

        new Thread(sellTicket02).start();//第1个线程-窗口    线程共享  一个对象给三个线程进行
        new Thread(sellTicket02).start();//第2个线程-窗口
        new Thread(sellTicket02).start();//第3个线程-窗口


    }
}

实现接口方式
class SellTicket02 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum    一个对象给三个线程进行,都是同一个对象,不用 static

    @Override
    public void run() {
        while (true) {

            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }

            //休眠50毫秒, 模拟
            try {
                Thread.sleep(50);      //如果把休眠时间长一点---> 不会-1     休眠时间:特别小,容易-1
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2

        }
    }
}

            总结:出现 -1 或者别的超票行为的原因: 先判断,( 如果>=0那就-- 记下来 ) ,然后睡眠,然后执行。
如果是单线程,你的判断和行为是一一对应!   但是,如果是多线程的话,当你第一次判断后,睡眠起来时,可能当初的值变了(被那些先起来的线程-- 了)。
        所以,导致对不上。但是,你还是按照当初的值来判断。管他呢结果现在是 0/-1 ,都会 -- 。
                还有一种可能:现在票数:1   三个线程进来判断是1,>=0,那就 -- 。然后睡眠,先醒来的进行--,自己结束。
                    但是别人还没结束,后面醒来的还是以为票数是1,进行--,就会出现 -1,-2 情况。

    无论你通过接口共享一个对象,还是通过类创还能多个对象改变static值,都会存在以上的问题。
    怎么解决呢?
        有个方法:当一个线程进行完后,是0,就立刻终止所有线程。别让他们再--了。

//*************************************************************************************************************************//
//****************************                    线程终止              *********588*****************************************//
//*************************************************************************************************************************//
                                    线程终止
                    基本说明:
                        1)当线程完成任务后,会自动退出
                        2)还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式


public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();

        //如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
        //让t1 退出run方法,从而终止 t1线程 -> 通知方式

        //让主线程休眠 10 秒,再通知 t1线程退出
        System.out.println("main线程休眠10s...");
        Thread.sleep(10 * 1000);
        t1.setLoop(false);
    }
}

class T extends Thread {
    private int count = 0;
    //设置一个控制变量
    private boolean loop = true;
    @Override
    public void run() {
        while (loop) {

            try {
                Thread.sleep(50);// 让当前线程休眠50ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 运行中...." + (++count));
        }

    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}
//*************************************************************************************************************************//
//*************************************************************************************************************************//
//*************************************************************************************************************************//
                                    线程常用方法
                常用方法第一组:
                    1.setName       //设置线程名称,使之与参数 name 相同
                    2.getName       //返回该线程的名称
                    3.start         //使该线程开始执行: Java 虚拟机底层调用该线程的 start0 方法
                    4.run           //调用线程对象 run 方法
                    5.setPriority   //更改线程的优先级
                    6.getPriority   //获取线程的优先级
                    7.sleep         // 在指定的毫秒数内让当前正在执行的线程休眠 (暂停执行)
                    8. interrupt    //中断线程      不等于:终止!!

                注意事项和细节
                    1.start 底层会创建新的线程,通过 start0来调用run。 run 就是一个简单的方法调用,不会启动新线程
                    2.线程优先级的范围
                    3.interrupt,中断线程但并没有真正的结束线程。所以一般用于: 中断正在休眠线程
                    4.sleep:线程的静态方法,使当前线程休眠


public final static int MIN_PRIORITY = 1;   // 线程可以具有的最低优先级。
public final static int NORM_PRIORITY = 5;  // 分配给线程的默认优先级。
public final static int MAX_PRIORITY = 10;  // 线程可以具有的最大优先级。

//*************************************************************************************************************************//
//*******************************************************589**************************************************************//
//*************************************************************************************************************************//
public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        //测试相关的方法
        T t = new T();
        t.setName("老韩");
        t.setPriority(Thread.MIN_PRIORITY);//1   优先级最低
        t.start();//启动子线程


        //主线程打印5 hi ,然后我就中断 子线程的休眠
        for(int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi " + i);
        }

        System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1
        t.interrupt();//当执行到这里,就会中断 t线程的休眠.

    }
}

class T extends Thread { //自定义的线程类
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                //Thread.currentThread().getName() 获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + "  吃包子~~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
                Thread.sleep(2000);//20秒
            } catch (InterruptedException e) {   //InterruptedException 是捕获到一个中断异常.
                //当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码

                System.out.println(Thread.currentThread().getName() + "被 interrupt了");
            }
        }
    }
}

//    老韩  吃包子~~~~18
//        老韩  吃包子~~~~19
//        老韩 休眠中~~~        这里你可以把 try{ Thread.sleep(200000); } 这个时间弄得特别大,你会发现,第一次时候,直接被t.interrupt();
                                        //  下次开始,你就等候很长时间~~
//        hi 0
//        hi 1
//        老韩 线程的优先级 =10
//        老韩被 interrupt了
//        继续执行
//        老韩  吃包子~~~~0
//        老韩  吃包子~~~~1
//*************************************************************************************************************************//
//*********************************************************590*************************************************************//
//*************************************************************************************************************************//
                                常用方法第二组
                        1.yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
                        2.join: 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
                             案例: main线程创建一个子线程,每隔1s 输出 hello,输出 20次,主线程每隔1秒,输出 hi,输出 20次.
                                  要求: 两个线程同时执行,当主线程输出 5次后,就让子线程运行完毕,主线程再继续

补充细节:yield:线程的礼让   当CPU 并发执行线程1 和 线程2 的时候,使用 yield,执行礼让:线程1说:CPU你要不执行以下线程1?
        如果线程2并不好费时间,CPU来得及切换,CPU说:问题不大,我来得及切换,没事,我继续执行你,再执行线程2
                                        ----> 不会礼让(不会执行线程2),继续执行完线程1,再执行线程2,再执行线程1....
        相反,线程2非常耗费时间,那CPU就不能很快切换,来不及切换,那就进行礼让:执行线程2,再继续“执行线程1的后半部分” !!!
                                        总结:不一定礼让! 就由CPU决定!

        join:线程插队:就是强制执行!
            还是上面的情况,当CPU执行线程1时候,用了t2.join ,那么立即执行线程2,执行完线程2,再继续“执行线程1的后半部分” !!!


//*************************************************************************************************************************//
//*********************************************************590************************************************************//
//*************************************************************************************************************************//
public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {

        T2 t2 = new T2();
        t2.start();

        for(int i = 1; i <= 20; i++) {
            Thread.sleep(1000);                                                     都是 : 1000
            System.out.println("主线程(小弟) 吃了 " + i  + " 包子");
            if(i == 5) {
                System.out.println("A:主线程(小弟) 让 子线程(老大) 先吃");
                //join, 线程插队
                //t2.join();// 这里相当于让t2 线程先执行完毕
                Thread.yield();//礼让,不一定成功..
                System.out.println("B:线程(老大) 吃完了 主线程(小弟) 接着吃..");
            }

        }
    }
}

class T2 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(100);                                           1.100     2.100000    3.1000
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大) 吃了 " + i +  " 包子");
        }
    }
}

分几种 sleep 情况讨论一下:
        1.(1000,100) 刚开始让大哥运行,大哥睡100,小弟1000.  相当于,大哥在小弟睡1000ms时,可以输出10次!
结果就是: 大哥:1,2,3,4,5,6,7,8,9 第10次:小弟:1  大哥:10,11,12,13,14,15,16,17,18,19 第20次:小弟:2
          大哥:20 大哥已经输出完了,只能小弟慢慢的输出:3,4,5  然后,(A....)就剩小弟了,(B.....),小弟又继续输出:6,7,8,...20
        Thread.yield();   这种情况,不会礼让。因为CPU完全可以忙得过来。不会礼让~ (A....)(B....)
        t2.join();大哥:1 /大哥:2 /大哥:3 /大哥:4 .../大哥:9 /小弟:1
        /大哥:10 /大哥:11 /...大哥:18 /小弟:2/大哥:19 /大哥:20 /小弟:3 /小弟:4 /小弟:5 /(A....)(B....)/小弟:6 /...小弟:20


        2.(1000,1000) 刚开始让大哥运行,大哥睡1000,小弟1000.  睡眠时间一样。那就:小弟:1 大哥:1 小弟:2 大哥:2 小弟:3 大哥:3 ....
                你可能有个疑问:不是先让大哥运行了?怎么会小弟在前面?
                    因为,main 抢CPU的速度比较快!!同样的情况下,mian里面的小弟先执行,然后大哥。
        Thread.yield();   这种情况,不会礼让。因为CPU完全可以忙得过来。不会礼让~ (A....)(B....)
        t2.join();  小弟:1 / 大哥:1 / 小弟:2 / 大哥:2 / 小弟:3 /大哥:3/小弟:4 /
            大哥:4 / 小弟:5 / 大哥:5 /(A.先输出大哥...)/ 大哥:6 / 大哥:7 / 大哥:8 / ..... 大哥:20 /小弟:6 /小弟:7 /小弟:8 /...小弟:20

3.(1000,10000) 刚开始让大哥运行,大哥睡10000,小弟1000.  相当于,小弟在大哥睡10000ms时,可以输出10次!
        结果就是: 小弟:1,2,3,4,5,6,7,8,9 第10次:大哥:1  小弟:10,11,12,13,14,15,16,17,18 第20次:大哥:2 小弟:19,20 大哥:3,4,5...20
        t2.join();小弟:1 /小弟:2 /小弟:3 /小弟:4 /小弟:5 /(A.先输出大哥...)/大哥:1 /
                                    大哥:2 /大哥:3 /...大哥:20 /(B.大哥输出完了...)/小弟:6 /小弟:7 /...小弟:20
        Thread.yield();   这种情况,不会礼让。因为CPU完全可以忙得过来。不会礼让~ (A....)(B....)


                我草!!!!我都被晕了,妈的!哈哈哈哈哈 保证上面都是对的哈!

                我用了 (10,1000) (1000,10) 用不用 Thread.yield(); ,效果都一样!!!!
//*************************************************************************************************************************//
//*******************************************************591***************************************************************//
//*************************************************************************************************************************//
            1.主线程每隔1s,输出 hi,一共 10次
            2.当输出到 hi 5 时,启动一个子线程(要求实现Runnable),每隔1s 输出 hello,等该线程输出10次 hello后 ,退出
            3.主线程继续输出 hi,直到主线程退出4.如图,完成代码
                        其实翻译过来就是:线程插队..

public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        Thread t3 = new Thread(new T3());//创建子线程
        for (int i = 1; i <= 10; i++) {
            System.out.println("hi " + i);
            if(i == 5) {//说明主线程输出了5次 hi
                t3.start();//启动子线程 输出 hello...
                t3.join();//立即将t3子线程,插入到main线程,让t3先执行
            }
            Thread.sleep(1000);//输出一次 hi, 让main线程也休眠1s
        }
    }
}

class T3 implements Runnable {
    private int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("hello " + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}
//*************************************************************************************************************************//
//************************************************************592**********************************************************//
//*************************************************************************************************************************//
                用户线程和守护线程
                1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
                2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
                3.常见的守护线程: 垃圾回收机制
                                    //垃圾回收机制就是用户线程结束后,才执行~~


public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        //如果我们希望当main线程结束后,子线程自动结束,只需将子线程设为守护线程即可
        myDaemonThread.setDaemon(true);  //设置成守护线程
        myDaemonThread.start();     //如果不设置“守护线程”的话,子线程会一直进行下去!!!
                                // 细节:必须先设置成守护线程,再start!!不然会报异常
        for( int i = 1; i <= 10; i++) {//main线程
            System.out.println("宝强在辛苦的工作...");
            Thread.sleep(1000);
        }
    }
}

class MyDaemonThread extends Thread {
    public void run() {
        for (; ; ) {//无限循环
            try {
                Thread.sleep(1000);//休眠1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
        }
    }
}
//*************************************************************************************************************************//
//**********************************************************593************************************************************//
//*************************************************************************************************************************//
                                线程的生命周期
线程状态:线程可以处于以下状态之一:
                    NEW                 尚未启动的线程处于此状态。
                    RUNNABLE            在Java虚拟机中执行的线程处于此状态。
                    BLOCKED             被阻塞等待监视器锁定的线程处于此状态。
                    WAITING             正在等待另一个线程执行特定动作的线程处于此状态。
                    TIMED WAITING       正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
                    TERMINATED          已退出的线程处于此状态

线程状态转化图
起点---->new (状态)----start()---> Runnable(状态) {Ready(状态)-----> Running(状态)} ---> Teminated(状态)---->结束
    Runnable(状态)---> 等待进入同步代码快的锁-----> Blocked(状态)-------获得锁------>Runnable(状态)
    Runnable(状态)---> o.wait() / t.join()-----> Waiting(状态)-----o.notify()/o.notifyAll()------>Runnable(状态)
    Runnable(状态)---> t.join(time)/o.wait(time)/Thread.sleep(time)-----> TimedWaiting(状态)-------时间结束----->Runnable(状态)
                                             ................线程被挂起............
                                             ↓                                  ↑
                                        Ready(状态)----线程被调度器选中执行----> Running(状态)
                                             ↑                                   ↓
                                             ...............Thread.yeild..........    //通过这里也能很清晰的看到:yeild由系统决定

补充一个细节:站在 main 线程角度,看子线程:如果此时,子线程在睡觉,那就:TIMED_WAITING
                                      如果此时,子线程还没start,那就 NEW
                                      如果此时,子线程结束了,那就TERMINATED
                                      如果此时,子线程睡眠时间短一点的话,会出现RUNNABLE状态!CPU决定

public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + "第一次状态 " + t.getState());   //状态 NEW
        t.start();

        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println(t.getName() + "  while 状态 " + t.getState());
                //第一次:状态 RUNNABLE      后面都是:TIMED_WAITING   因为线程睡眠时间是main主线程的100倍,比较明显
                        //如果你把线程的睡眠时间调小一点,会出现 RUNNABLE  TIMED_WAITING 叠加状态!! 都是由CPU决定
            Thread.sleep(100);
        }

        System.out.println(t.getName() + "  最后结束 状态 " + t.getState());
              // Thread-0  最后结束 状态 TERMINATED

    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

//*************************************************************************************************************************//
//*********************************************************594**********************************************************//
//*************************************************************************************************************************//
                                                 Synchronized
   线程同步机制
        1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
        2.也可以这里理解: 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,
                                            其他线程才能对该内存地址进行操作。

        同步具体方法-Synchronized
        1.同步代码块
            synchronized (对象){  //得到对象的锁,才能操作同步代码
                    //需要被同步代码;
        }

        2.synchronized还可以放在方法声明中,表示整个方法-为同步方法
            public synchronized void m (String name){
                    //需要被同步的代码
        }

        3.如何理解:就好像多个小雪人上厕所前,先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可以一个一个使用厕所了。
                         不然,很有可能多个人一起上,互相拉在身上了(有点变态~~)
//*************************************************************************************************************************//
//********************************************************595*************************************************************//
//*************************************************************************************************************************//
public class SellTicket {
    public static void main(String[] args) {

        //测试
//        SellTicket01 sellTicket01 = new SellTicket01();
//        SellTicket01 sellTicket02 = new SellTicket01();
//        SellTicket01 sellTicket03 = new SellTicket01();
//
//        //这里我们会出现超卖..
//        sellTicket01.start();//启动售票线程
//        sellTicket02.start();//启动售票线程
//        sellTicket03.start();//启动售票线程


        System.out.println("===使用实现接口方式来售票=====");
        SellTicket02 sellTicket02 = new SellTicket02();

        new Thread(sellTicket02).start();//第1个线程-窗口
        new Thread(sellTicket02).start();//第2个线程-窗口
        new Thread(sellTicket02).start();//第3个线程-窗口


    }
}




//实现接口方式
class SellTicket02 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum

    @Override
    public synchronized void run() {                  我就在这里多加了一个:synchronized
        while (true) {                          第一个先执行。大师这是个死循环,会导致第一个先进来的,通过死循环,把所有都执行完,在退出

            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                break;
            }

            //休眠50毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2

        }
    }
}

输出结果:
            窗口 Thread-0 售出一张票 剩余票数=99
            窗口 Thread-0 售出一张票 剩余票数=98
            窗口 Thread-0 售出一张票 剩余票数=97
                       ............
            窗口 Thread-0 售出一张票 剩余票数=2
            窗口 Thread-0 售出一张票 剩余票数=1
            窗口 Thread-0 售出一张票 剩余票数=0
            售票结束...    //第一个线程进来,直接都干完了!!
            售票结束...    //第二个线程进来,发现ticketNum == 0 ,直接退出
            售票结束...    //第三个线程进来,发现ticketNum == 0 ,直接退出
//*************************************************************************************************************************//
        为什么多次一举用run调用m方法,因为只有start才能开子线程.start只能调用run方法

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

        //测试一把
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口

    }
}


//实现接口方式, 使用synchronized实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean loop = true;//控制run方法变量

    public synchronized void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法

        if (ticketNum <= 0) {
            System.out.println("售票结束...");
            loop = false;
            return;
        }


        //休眠50毫秒, 模拟
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
    }


    @Override
    public void run() {
        while (loop) {

            sell();//sell方法是一共同步方法
        }
    }
}

输出结果:
            窗口 Thread-1 售出一张票 剩余票数=9
            窗口 Thread-1 售出一张票 剩余票数=8
            窗口 Thread-1 售出一张票 剩余票数=7
            窗口 Thread-2 售出一张票 剩余票数=6
            窗口 Thread-2 售出一张票 剩余票数=5
            窗口 Thread-2 售出一张票 剩余票数=4
            窗口 Thread-2 售出一张票 剩余票数=3
            窗口 Thread-2 售出一张票 剩余票数=2
            窗口 Thread-2 售出一张票 剩余票数=1
            窗口 Thread-2 售出一张票 剩余票数=0
            售票结束...
            售票结束...
            售票结束...
//*************************************************************************************************************************//
//*********************************************************595************************************************************//
//*************************************************************************************************************************//
                                            互斥锁
        基本介绍:
                1.Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
                2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
                3.关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时表明该对象在任一时刻只能由一个线程访问
                4.同步的局限性:导致程序的执行效率要降低
                5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
                6.同步方法(静态的)的锁为当前类本身
        
        this是针对对象来说的(因为,创建对象,采用this)     static是针对类的(因为,类加载就static加载)
        static  是在类加载时候 就有的  而对象 是类加载后根据类构造方法创建的 ,所有对象都有static 方法 ,故this不了


1. public synchronized void sell() {    就是一个同步方法,这时锁在 this对象
                    //代码块
        }

2. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
public void sell() {
synchronized (this) {
                 //代码块
                }
        }

3.  class SellTicket03 implements Runnable {
        Object object = new Object();

        public void sell() {
            synchronized (object) {
                //代码块
            }
        }                              }

       细节:
               SellTicket03 sellTicket03 = new SellTicket03();
               new Thread(sellTicket03).start();//第1个线程-窗口
               new Thread(sellTicket03).start();//第2个线程-窗口
               new Thread(sellTicket03).start();//第3个线程-窗口

            都是同一个 sellTicket03 对象!!!!所以,操作的Object 都是同一个Object !!!!

4.
        同步方法(静态的)的锁为当前类本身
        1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
        public synchronized static void m1() {}

        2. 如果在静态方法中,实现一个同步代码块.
        public static  void m2() {
        synchronized (SellTicket03.class) {   //不能用this !!!!
                System.out.println("m2");
                         }
                }

        static 是在类加载时候 就有的  而对象 是类加载后根据类构造方法创建的 ,所有对象都有static 方法 ,故this不了

        就是说,任何一个对象都有互斥锁,而Synchronized是为了调对象的互斥锁,所以括号里this也好,
                其他对象也好都可以满足,只要是个对象就行;而静态方法没有对象也就没有互斥锁所以直接调用类的方式

        互斥锁。
        注意事项和细节
        1.同步方法如果没有使用static修饰: 默认锁对象为this
        2.如果方法使用static修饰,默认锁对象:当前类.class
        3.实现的落地步骤:
             需要先分析上锁的代码
             选择同步代码块或同步方法
             要求多个线程的锁对象为同一个即可

要求多个线程的锁对象为同一个即可:
            new SellTicket01.start();
            new SellTicket01.start();
            class SellTicket01 extends Thread{
                private static int ticketNum = 100; //让多个线程共享 ticketNum
                public void m1{
                    synchronized (this){
                        System.out.println("hello");
                    }
                }
            }
            这么做的话,m1是锁不住的!!因为,你每个线程都是new出来的。所以每个线程的锁都不同。
                                            人3
             门3   人3 人3 人3 人3            人2
             门2   人2 人2 人2 人2 -------->  人1
        马桶:门1   人1 人1 人1 人1           马桶

如果:
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口
                          三个线程,都是通过一个对象弄出来的。所以,门是一样的!!
              人3 人3 人3 人3
              人2 人2 人2 人2
    马桶:门   人1 人1 人1 人1

3.  class SellTicket03 implements Runnable {
    Object object = new Object();

    public void sell() {
        synchronized (new Object()) {   //相当于:一人一个门。但是,坑位只有一个!!还是不行!!超卖更加严重~~
            //代码块
        }
    }
//*************************************************************************************************************************//
//*********************************************************596*************************************************************//
//*************************************************************************************************************************//
                                          线程的死锁
             基本介绍:
                    多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
            应用案例:
                    妈妈:你先完成作业,才让你玩手机
                    小明: 你先让我玩手机,我才完成作业
            死锁:你拿着卫生纸,我占着坑位,谁都别想上厕所!!
//*************************************************************************************************************************//
//*******************************************************596**************************************************************//
//*************************************************************************************************************************//
    这里可以这样理解,  一个对象拿到了o1,而另一个对象拿到了o2,而拿到o1的对象还需要拿到o2,但是o2已经被另一个对象拿到了,所以就产生了死锁

      模拟线程死锁

    public class DeadLock_ {
        public static void main(String[] args) {
            //模拟死锁现象
            DeadLockDemo A = new DeadLockDemo(true);
            A.setName("A线程");
            DeadLockDemo B = new DeadLockDemo(false);
            B.setName("B线程");
            A.start();
            B.start();
        }
    }


    //线程
    class DeadLockDemo extends Thread {
        static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
        static Object o2 = new Object();
        boolean flag;

        public DeadLockDemo(boolean flag) {//构造器
            this.flag = flag;
        }

        @Override
        public void run() {

            //下面业务逻辑的分析
            //1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
                             //如果线程A 得不到 o2 对象锁,就会Blocked
            //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
                             //如果线程B 得不到 o1 对象锁,就会Blocked
            if (flag) {
                synchronized (o1) {//对象互斥锁, 下面就是同步代码
                    System.out.println(Thread.currentThread().getName() + " 进入1");
                    synchronized (o2) { // 这里获得li对象的监视权
                        System.out.println(Thread.currentThread().getName() + " 进入2");
                    }

                }
            } else {
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + " 进入3");
                    synchronized (o1) { // 这里获得li对象的监视权
                        System.out.println(Thread.currentThread().getName() + " 进入4");
                    }
                }
            }
        }
    }

    这挺有意思的:一个一个解释一下:
    synchronized (o1)  说明,拿到了 o1 的锁。如果o1对象还有别的线程,就进不来! 当执行完代码块程序,再释放锁!
    synchronized (o2)  说明,拿到了 o2 的锁。如果o2对象还有别的线程,就进不来! 当执行完代码块程序,再释放锁!
    到这里,应该没问题吧?没问题!很合理。
    但是,
            DeadLockDemo A = new DeadLockDemo(true);
            DeadLockDemo B = new DeadLockDemo(false);
            这么做的话,A进来,true,就会拿到o1的锁(别的o1进不来),接着想拿o2的锁。但是,由于B,o2进不来!拿不到锁
                 此时,B进来,false,就会拿到o2的锁(别的o2进不来),接着想拿o1的锁。但是,由于A,o1进不来!拿不到锁

核心冲突:A 和 B 同时拿到了对方重要部分!!导致对方都走不了。
再核心一下:同时!!!
    方法一:如果,我让对方睡一会儿,另一方会趁着它睡觉的时间,拿到重要部分,就不会出现问题了。
            让A睡一会儿,再拿锁o1,o2. 这时候,B已经拿o2,o1 锁了。正常进行!
                    if (flag) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (o1) {//对象互斥锁, 下面就是同步代码
                    System.out.println(Thread.currentThread().getName() + " 进入1");

                    synchronized (o2) { // 这里获得li对象的监视权
                        System.out.println(Thread.currentThread().getName() + " 进入2");
                    }

                }
            }else......

方法二:双方可以同时抢。但是,让对方抢各自的!也就是说:     顺序:A:o1,o2     B:o2,o1
                            Object o1 = new Object();
                            Object o2 = new Object();
                            状况:A:o1  B:o1   A:o2  B:o2
      双方拿到的都是各自的o1,o2     A: o1,o2    B: o1,o2   不会有任何冲突!随便抢!双方都独自走出来了

方法三:双方可以同时抢。但是,让对方抢各自的!也就是说:    顺序:A:o1,o2     B:o2,o1
                        static Object o1 = new Object();
                        Object o2 = new Object();
                        状况:o1   A:o2  B:o2
A: o1    B: o2  ,但是,只有一个o1,B没法立刻拿o1,因为此刻o1被A锁着。但是,A可以拿o2.所以,等A结束之后,o2释放了,B再可以进行!
所以顺序: A: o1    B: o2  B等A, A:o2 ,A结束,再 B;o1 (A1,B2,A2,B1)

方法三:双方可以同时抢。但是,让对方抢各自的!也就是说:    顺序:A:o1,o2     B:o2,o1
                        Object o1 = new Object();
                        static Object o2 = new Object();
                        状况:o2   A:o1  B:o1
                        (A1,B2,A没法拿o2,只能等B走完,B1,A2)

        总结:三种方法解决死锁:①让某一方在拿对方重要锁的之前,睡一会儿,让对方先执行完  (这个睡眠时间很重要!!)
                            ②都非satic,每个对象独立分配锁
                            ③让其中一个属性非static,导致某一方先执行完。

            你看,同样一个视频,老师就说:避免锁。而我,想到了四种方法解决锁。就问你细不细???哈哈~~
//*************************************************************************************************************************//
//***************************************************597******************************************************************//
//*************************************************************************************************************************//
    用yield就是,你先上,我就是不开门,你先上,快点上,哈哈哈哈,门就是不开

                            释放锁
            下面操作会释放锁:
                1.当前线程的同步方法、同步代码块执行结束
                    案例:上厕所,完事出来
                2.当前线程在同步代码块、同步方法中遇到 break、return
                    案例:没有正常的完事经理叫他修改bug,不得已出来
                3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
                    案例:发现忘带纸,不得已出来
                4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
                    案例:没有正常的完事,觉得需要酝酿下,所以出来等会再进去

            下面操作不会释放锁:
                1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
                    案例:上厕所,太困了,在坑位上眯了一会
                    案例:用yield就是,你先上,我就是不开门,你先上,快点上,哈哈哈哈,门就是不开
                2.线程执行同步代码块时,其他线程调用了该线程的suspend0方法将该线程挂起该线程不会释放锁
                    提示: 应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
//*************************************************************************************************************************//
//*****************************************************598****************************************************************//
//*************************************************************************************************************************//
            题目描述:A线程:随机输出1~100的数字,至到B线程中输入:Q,让A线程停止

    public class Homework01 {
        public static void main(String[] args) {
            A a = new A();
            B b = new B(a);//一定要注意.
            a.start();
            b.start();
        }
    }

    //创建A线程类
    class A extends Thread {
        private boolean loop = true;

        @Override
        public void run() {
            //输出1-100数字
            while (loop) {
                System.out.println((int)(Math.random() * 100 + 1));
                //休眠
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("a线程退出...");

        }

        public void setLoop(boolean loop) {//可以修改loop变量
            this.loop = loop;
        }
    }

    //直到第2个线程从键盘读取了“Q”命令
    class B extends Thread {
        private A a;
        private Scanner scanner = new Scanner(System.in);

        public B(A a) {//构造器中,直接传入A类对象
            this.a = a;
        }

        @Override
        public void run() {
            while (true) {
                //接收到用户的输入
                System.out.println("请输入你指令(Q)表示退出:");
                char key = scanner.next().toUpperCase().charAt(0);
                if(key == 'Q') {
                    //以通知的方式结束a线程
                    a.setLoop(false);
                    System.out.println("b线程退出.");
                    break;
                }
            }
        }
    }

        此题,能不能通过“守护线程”来做?
                不行。因为,守护线程是指所有线程都结束了,它才结束。但是,main刚来就结束了。A正在进行。
                        别人都停了,你再停,这时可以用守护线程。但是,你通知别人,不能用守护线程!!

        温馨提示:这里输入Q时要在1秒内迅速按下回车键,否则会一直输出数字!!别的线程会被霸占!

    还有一个细节:  B b = new B(a);    b停了,a就停!!!
//*************************************************************************************************************************//
//******************************************************599***************************************************************//
//*************************************************************************************************************************//
    而放锁外面的休眠,会使上一个进锁的对象休眠,锁会给剩下的对象抢夺,会排除上一个对象抢锁的概率,直到下一个对象进入休眠重置概率


 原理:
    t1 ------> Blocked
                        ? 获取锁----执行---结束后,释放锁--     抢完锁之后,执行完,又去抢锁!!
    t2 ------> Blocked                                ↓
     ↑------------------------------------------------↓

可能会出现一种情况:t1  t2 ,如果t1牛逼,抢的比较快,那就t1先抢到,执行代码。执行完之后又去抢!  导致,t1执行很多次。
但是,这对t2来说不公平啊~~我们希望的是,第一次,行,t1牛逼,你先执行。但是后面应该是t2.顺序执行。但是,我们这个不会这么做。
            所以,synchronized 也叫“非公平锁

还有一个小办法:如果是两个线程进行,如果你想实现顺序执行,可以在 synchronized{}之后,睡一会儿。另一个线程就会趁这个机会,进行。哈哈哈哈
就会达到顺序执行~~  细节,一定在 synchronized{}外面,但是这个线程一定经过的地方(run里面)。不要放在 synchronized{}里面。不然,这个线程睡觉,别的线程也只能干等着~~
        当然,如果有两个以上的线程,无论你怎么睡,都没用哈哈。剩下线程趁你在睡觉的时候,都会抢。还是会不公平~~
//***********************************************
public class Homework02 {
    public static void main(String[] args) {
        T t = new T();
        Thread thread1 = new Thread(t);
        thread1.setName("t1");
        Thread thread2 = new Thread(t);
        thread2.setName("t2");
        thread1.start();
        thread2.start();
    }
}

                //编程取款的线程
                //1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
                //2. 每次取出 1000
    class T implements  Runnable {
        private int money = 10000;

        @Override
        public void run() {
            while (true) {

                解读
                1. 这里使用 synchronized 实现了线程同步
                2. 当多个线程执行到这里时,就会去争夺 this对象锁
                3. 哪个线程争夺到(获取)this对象锁,就执行 synchronized 代码块, 执行完后,会释放this对象锁
                4. 争夺不到this对象锁,就blocked ,准备继续争夺
                5. this对象锁是非公平锁.

                synchronized (this) {//
                    //判断余额是否够
                    if (money < 1000) {
                        System.out.println("余额不足");
                        break;
                    }

                    money -= 1000;
                    System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
                }

                            让这个线程睡一会儿。另一个线程就会趁这个机会进行。达到顺序执行。
                //休眠1s
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

                    如果你不睡眠,直接进行,很有可能出现,只有t1在执行。
                        为哈呢?
                            因为这个数目太少了。t1一个人直接干完了。
                    如果你把数字调大一点:10000改成10000000 会出现 t1 ,t2一起干的情况~


                     接着搞坦克大战去啦~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值