3.1_15 JavaSE入门 P14 【多线程】同步、匿名内部类、死锁、生命周期

相关链接



P14 【进阶】多线程、同步、匿名内部类、死锁、生命周期


  • 今日要掌握的内容:
  • 1.【应用】多线程概述 & 多线程实现
    • a.【理解】能够阐述进程与线程的概念
    • b.【应用】能够独立写出线程的两种实现方式
    • c.【理解】能够阐述两种线程实现方式的优缺点
  • 2.【理解】多线程安全问题产生 & 解决方案
    • a.【应用】能够分析多线程共享资源产生的安全问题
    • b.【应用】能够使用同步代码块解决多线程的安全问题
    • c.【应用】能够使用同步方法解决多线程的安全问题
    • d.【应用】匿名内部类练习

1 概述


  • 进程 (可执行文件, 程序(例如: .exe)
    • 进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
    • 一个程序运行后至少有一个进程,进程有多条执行路径, 合称为: 多线程
    • 简单理解为:

  • 线程 进程的执行路径(执行单元)
    • 线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线的,这个应用程序也可以称之为多线程程序
    • 8核CPU:等于有八个线程可以同时运行各类进程
    • 简单理解为: 车道

  • 单线程
    • 进程只有一条执行路径,这种执行方式叫单线程
    • 简单理解为:一辆车在一条车道上跑

  • 多线程
    • 一个程序运行后至少有一个进程,进程有多条执行路径, 合称为: 多线程

案例代码一多线程概述

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/12
 * @introduction 多线程概述
 *
 * main:主线程
 * system.out:标准的输出流(黑色字体)。 可以理解为:这是一个线程。
 * system.err:标准的错误流(红色字体)。 可以理解为:这也是一个线程。
 *
 * 1.一台电脑上可以有多个进程,这些进程之间的数据是相互隔离的
 *      //例如:qq.exe wechat.exe
 * 2.一个进程可以有多条线程,
 *      //例如:往QQ群共享放一个文件,该群中的所有用户都可以下载
 *
 */
public class Demo1MultiThread {
    public static void main(String[] args) {

        System.out.println(1);
        System.out.println(2);
        System.out.println(3);
        System.err.println(4);
        System.err.println(5);
        System.out.println(6);
        System.out.println(1/0);
        //多次执行结果顺序不相同,因为out和err是两个线程,两个线程在抢夺资源,谁抢到资源谁就先执行程序
        /*
            4
            5
            Exception in thread "main" java.lang.ArithmeticException: / by zero
                at com.groupies.base.day14.a.Demo01_多线程概述.main(Demo01_多线程概述.java:27)
            1
            2
            3
            6
         */
        /*
            1
            2
            3
            6
            4
            5
            Exception in thread "main" java.lang.ArithmeticException: / by zero
                at com.groupies.base.day14.a.Demo01_多线程概述.main(Demo01_多线程概述.java:29)
         */
    }
}

1.1 并行&并发


  • 并行
    • 两个(多个)线程同时执行. (提前: 需要多核CPU)

  • 并发
    • 两个(多个)线程同时请求执行, 但是同一瞬间, CPU只能执行一个
    • 于是就安排它们交替执行, 因为时间间隔非常短, 我们看起来好像是同时执行的, 其实不是

多线程并行和并发的区别

在这里插入图片描述


1.2 主方法是多线程的吗?


  主方法是由JVM虚拟机调用的,执行顺序为:从上到下、从左到右。


案例代码二多线程实现方式一 继承Thread类 【a.线程类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/11
 * @introduction 主方法中代码的执行是多线程的吗?
 *
 * 测试方法:
 *      如果主方法是单线程的,则进入method死循环方法无法进入到下一步function方法
 *      如果主方法是多线程的,则进入method死循环方法不影响其他线程进入下一步的function方法
 * 测试结果:
 *     未进入function方法,程序循环打印method方法内容
 * 结论:
 *     主方法是单线程的
 */
public class Demo2MainThreadTest {
    public static void main(String[] args) throws InterruptedException {
        /*
         */
        //测试类1
        method();
        //测试类2
        function();
    }

    /**
     * @introduction 测试类1
     */
    public static void method() throws InterruptedException {
        while (true){
            Thread.sleep((long) 500.00);
            System.out.println("method");
        }
    }

    /**
     * @introduction 测试类2
     */
    public static void function() throws InterruptedException {
        while (true){
            Thread.sleep((long) 500.00);
            System.out.println("function");
        }
    }
}

2 多线程实现


  • 多线程的实现方式
    • 方式一:继承Thread类
    • 方式二:实现Runnable接口
    • 方式三:结合线程池使用(实现Callable接口) — 了解即可

2.1 方式一:继承Thread类


  该如何创建线程呢?通过API中搜索,查到Thread类。通过阅读Thread类中的描述。Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程


  1 创建线程的步骤

  • 1)定义一个类(MyThread)继承Thread;
  • 2)重写Thread#run()方法;
  • 3)把要执行的代码放入run()方法;
  • 4)在测试类中,创建线程对象
  • 5)调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
  • 6)CPU执行程序的随机性
  • 7)统一线程不能重复开启,否则会报IllegalThreadStateException异常

  2 多线程的实现方式

  • 方式一:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例

    //Thread
    //返回该线程的名称。 
    String getName()  
    //改变线程名称,使之与参数 name 相同。
    void   setName(String name) 
    

  3 CPU执行程序的随机性


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


案例代码三多线程实现方式一 继承Thread类 【a.线程类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/11
 * @introduction 多线程实现方式一 继承Thread类 线程类
 *
 * 创建线程的步骤:
 *    1) 定义一个类(MyThread)继承Thread;
 *    2) 重写Thread#run()方法;
 *    3) 把要执行的代码放入run()方法;
 *    4) 在测试类中,创建线程对象
 *    5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
 *    6) CPU执行程序的随机性
 *    7) 统一线程不能重复开启,否则会报IllegalThreadStateException异常
 *
 * 多线程的实现方式:
 * 		方式1:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
 *
 * Thread
 * 		String getName()      返回该线程的名称。
 * 		void   setName(String name) 改变线程名称,使之与参数 name 相同
 */
//1)定义一个类(MyThread)继承Thread;
public class Demo3MyThread extends Thread {
    //2) 重写Thread#run()方法;
    @Override
    /**
     * @introduction 该线程要执行的操作,打印10次指定内容
     */
    public void run() {
        for (int i = 0; i < 10; i++) {
            //3) 把要执行的代码放入run()方法;
            System.out.println(getName() + ":" + i);
        }
    }
}

案例代码三多线程实现方式一 继承Thread类 【b.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/11
 * @introduction 多线程实现方式一 测试类
 *
 * 创建线程的步骤:
 *    1) 定义一个类(MyThread)继承Thread;
 *    2) 重写Thread#run()方法;
 *    3) 把要执行的代码放入run()方法;
 *    4) 在测试类中,创建线程对象
 *    5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
 *    6) CPU执行程序的随机性
 *    7) 统一线程不能重复开启,否则会报IllegalThreadStateException异常
 *
 * 多线程的实现方式:
 * 		方式1:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
 *
 * Thread
 * 		String getName()      返回该线程的名称。
 * 		void   setName(String name) 改变线程名称,使之与参数 name 相同。
 */
public class Demo3MyThreadTest {
    public static void main(String[] args) {
        //4) 在测试类中,创建线程对象
        Demo3MyThread mt = new Demo3MyThread();
        Demo3MyThread mt2 = new Demo3MyThread();
        //修改线程名字
        mt.setName("张三");
        mt2.setName("李四");


        //如果调用run()方法,只是普通的方法调用,并不会以多线程方式运行
        /*
            张三:0
            张三:1
            张三:2
            张三:3
            张三:4
            张三:5
            张三:6
            张三:7
            张三:8
            张三:9
            李四:0
            李四:1
            李四:2
            李四:3
            李四:4
            李四:5
            李四:6
            李四:7
            李四:8
            李四:9
         */
        mt.run();
        mt2.run();

        System.out.println("==========================");

        //5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
        mt.start();
        mt2.start();
        //6) CPU执行程序的随机性
        /*
            张三:0
            张三:1
            李四:0
            李四:1
            李四:2
            李四:3
            李四:4
            李四:5
            张三:2
            张三:3
            张三:4
            张三:5
            张三:6
            张三:7
            张三:8
            张三:9
            李四:6
            李四:7
            李四:8
            李四:9
         */

        //7)统一线程不能重复开启,否则会报IllegalThreadStateException异常
        try {
            mt.start();
        } catch (IllegalThreadStateException e) {
            //e.printStackTrace();
            System.out.println("IllegalThreadStateException: 非法线程状态异常");
        }
    }
}

2.1.1 细节及注意事项

  Q:为什么同样的代码,在有的电脑上执行,可能结果看着还是像单线程的?

  A:有些电脑的CPU机制对多线程任务进行优化(常见联想、小米电脑)

    先判断单核CPU线程量有多大,如果优化机制判断当前进程仅需一个CPU即可完成,则会使用单个CPU处理多线程任务

张三:0
张三:1
张三:2
张三:3
张三:4
张三:5
张三:6
张三:7
张三:8
张三:9
李四:0
李四:1
李四:2
李四:3
李四:4
李四:5
李四:6
李四:7
李四:8
李四:9

2.1.2 Thread类的成员

构造方法:
    public Thread();
    public Thread(String name);
	public Thread(Runnable target);
	public Thread(Runnable target,String name);

成员方法:
    run();			//里边定义的是线程要执行的代码,该方法会自动被start()方法调用
	start();		//开启线程,会自动调用run();
	getName();
	setName();		
	sleep();		//休眠线程,单位是:毫秒(1s=1000ms)
	currentThread;	//获取当前正在执行的线程对象(的引用)

2.2 方式二:实现Runnable接口


  创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

  为何要实现Runnable接口,Runable是啥玩意呢?继续API搜索。

  查看Runnable接口说明文档:Runnable接口用来指定每个线程要执行的任务。包含了一个 run 的无参数抽象方法,需要由接口实现类重写该方法


  创建线程的步骤

  • 1)定义一个类(MyRunnableThread),实现Runnable接口;

  • 2)重写Runnable#run()方法;

  • 3)把要执行的代码放入run()方法中;

  • 4)创建Runnable接口的子类对象;

    MyRunnableThread mrt = new MyRunnableThread();
    
  • 5)并将其作为参数传入Thread类的构造,创建线程对象

    Thread th = new Thread(mrt);
    
  • 6)开启线程

    th.start();
    

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


案例代码四多线程实现方式一 实现Runnable接口 【a.Runnable子类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/11
 * @introduction 多线程实现方式二 实现Runnable接口 Runnable实现类
 *
 * 可以理解为:Thread类的资源类
 *
 * 创建线程的步骤。
 *     1) 定义一个类(MyRunnableThread),实现Runnable接口;
 *     2) 重写Runnable#run()方法;
 *     3) 把要执行的代码放入run()方法中;
 *     4) 创建Runnable接口的子类对象;
 *         MyRunnableThread mrt = new MyRunnableThread();
 *     5) 并将其作为参数传入Thread类的构造,创建线程对象
 *         Thread th = new Thread(mrt);
 *     6) 开启线程
 *         th.start();
 *
 * Thread.currentThread().getName()
 *    Thread.currentThread() 得到当前thread对象
 *    Thread.currentThread().getName() 得到当前thread对象的名称
 */
//1) 定义一个类(MyRunnableThread),实现Runnable接口;
public class Demo4MyRunnableThread implements Runnable {
    //姓名
    String name;

    /**
     * @introduction 带参构造
     * @param name 姓名
     */
    public Demo4MyRunnableThread(String name) {
        this.name = name;
    }

    @Override
    /**
     * @introduciton 2、覆盖接口中的run方法 (Runnable接口的实现类必须实现run方法)
     */
    public void run() {
        for (int i = 0; i < 10; i++) {
            //Thread t = Thread.currentThread();
            //System.out.println(t.getName() + ":" + i);
            //链式编程
            System.out.println(Thread.currentThread().getName() + ":" + name + i);
        }
    }
}

案例代码四多线程实现方式一 实现Runnable接口 【b.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/12
 * @introduction 多线程实现方式二 实现Runnable接口 测试类
 *
 * 创建线程的步骤。
 *     1) 定义一个类(MyRunnableThread),实现Runnable接口;
 *     2) 重写Runnable#run()方法;
 *     3) 把要执行的代码放入run()方法中;
 *     4) 创建Runnable接口的子类对象;
 *         MyRunnable mr = new MyRunnable();
 *     5) 并将其作为参数传入Thread类的构造,创建线程对象
 *         Thread th = new Thread(mr);
 *     6) 开启线程
 *         th.start();
 *
 */
public class Demo4MyRunnableThreadTest {
    public static void main(String[] args) throws InterruptedException {
        //4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数
        Demo4MyRunnableThread mrt1 = new Demo4MyRunnableThread("张三");
        Demo4MyRunnableThread mrt2 = new Demo4MyRunnableThread("李四");
        Demo4MyRunnableThread mrt3 = new Demo4MyRunnableThread("林青霞");
        Demo4MyRunnableThread mrt4 = new Demo4MyRunnableThread("张曼玉");
        //3、创建Thread类的对象
        //5、调用Thread类的start方法开启线程
        //将Runnable接口的子类对象作为参数传递给Thread类的构造函数时,多线程方式
        new Thread(mrt1).start();
        new Thread(mrt2).start();
        /*
            Thread-0:张三0
            Thread-1:李四0
            Thread-1:李四1
            Thread-1:李四2
            Thread-0:张三1
            Thread-1:李四3
            Thread-1:李四4
            Thread-0:张三2
            Thread-1:李四5
            Thread-0:张三3
            Thread-1:李四6
            Thread-0:张三4
            Thread-1:李四7
            Thread-0:张三5
            Thread-1:李四8
            Thread-0:张三6
            Thread-1:李四9
            Thread-0:张三7
            Thread-0:张三8
            Thread-0:张三9
         */

        Thread.sleep(500);
        /*
            main:林青霞0
            main:林青霞1
            main:林青霞2
            main:林青霞3
            main:林青霞4
            main:林青霞5
            main:林青霞6
            main:林青霞7
            main:林青霞8
            main:林青霞9
            main:张曼玉0
            main:张曼玉1
            main:张曼玉2
            main:张曼玉3
            main:张曼玉4
            main:张曼玉5
            main:张曼玉6
            main:张曼玉7
            main:张曼玉8
            main:张曼玉9
         */
        //如果调用run()方法,只是普通的方法调用,并不会以多线程方式运行
        mrt3.run();
        mrt4.run();
    }
}

2.3 方式三:结合线程池使用(实现Callable接口)


暂略


3 多线程安全问题&解决方案


  • 出现的问题
    • 出现负数
    • 出现重复值

  • 解决方案
    • 采用同步代码块解决

3.1 多线程案例(模拟买票)


  • 需求
    • 四个窗口,卖100张票
    • 买票动作的思路
      • A:如果当前票数大于0则继续销售
      • B:为了加大出现错误的概率,我们加入:休眠线程 1s = 1000ms
      • C:如果有票,就正常的卖票即可

  • 第x张票,出现负数票的原因: while判断 + 休眠线程
    • 假设现在是最后一张票了,tickets的值应该是1,此时
    • 如果线程1抢到了资源,线程1休眠,此时还没有进入卖票的步骤
    • 此时线程2,线程3…也抢到了资源,由于票还没有卖出去,所以tickets为1
    • 线程2,线程3…可以正常通过while判断,也进入休眠的流程
    • 休眠时间结束,程序继续运行
    • 假设线程1先抢到票,打印:正在出售1号票,然后会把tickets的值改为:0
    • 假设线程2后抢到票,打印:正在出售0号票,然后会把tickets的值改为:-1
    • 假设线程3后抢到票,打印:正在出售-1号票,然后会把tickets的值改为:-2
    • 假设线程4后抢到票,打印:正在出售-2号票,然后会把tickets的值改为:-3

  • 第x张票,出现重复值的原因:tickets–
    • tickets-- 相当于 tickets = tickets - 1
    • tickets-- 做了3件事:
      • A:读值,读取tickets的值
      • B:改值,将tickets的值 - 1
      • C:赋值,将修改后的值重新赋值给tickets
    • 还没有来得及执行C的动作,此时别的线程抢走资源了,就会出现重复值

3.1.1 继承Thread类方式

案例代码五模拟买票的实现 【a.线程类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/13
 * @introduction 模拟买票的实现 线程类
 *
 * 思路:
 *    1.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)
 *    2.定义一个静态变量(tickets),记录票数
 *    3.需要区分不同窗口,所以要指定每个线程名称
 *    4.因为是四个窗口,所以需要创建四个线程对象  给线程自定义名字
 *    5.开启线程
 *    6.让负数票几率变大->
 *         6.1 增加票数  不好找
 *         6.2 每次卖票前线程sleep();
 *
 */
//2.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)
public class Demo5TicketSellMyThread extends Thread {
    //1.定义一个变量(tickets),记录票数
    private static int tickets = 100;//因为是共享数据,所以用static修饰

    /**
     * @introduction 3.需要区分不同窗口,所以要指定每个线程名称
     * @param window 窗口名称
     */
    public Demo5TicketSellMyThread(String window) {
        super(window);
    }


    @Override
    public void run() {
        /*
         * 买票动作的思路
         *  A:如果当前票数大于0则继续销售
         *  B:为了加大出现错误的概率,我们加入:休眠线程 1s = 1000ms
         *  C:如果有票,就正常的卖票即可
         */
        //A:如果当前票数大于0则继续销售
        while (tickets > 0) {
            //B 每次卖票前线程sleep();
            try {
                sleep(100 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //C:如果有票,就正常的卖票即可
            /**
             * 不指定范围,默认在this范围内查找getName方法,当前类找不到则会进入super(父类)继续寻找,如果还找不到则会报错
             */
            //System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets-- + "张票");
            //System.out.println(super.getName() + ":正在出售第" + tickets-- + "张票");
            //System.out.println(this.getName() + ":正在出售第" + tickets-- + "张票");
            System.out.println(getName() + ":正在出售第" + tickets-- + "张票");
            /*
             * 第x张票,出现负数票的原因: while判断 + 休眠线程
             * 假设现在是最后一张票了,tickets的值应该是1,此时
             *   如果线程1抢到了资源,线程1休眠,此时还没有进入卖票的步骤
             *   此时线程2,线程3..也抢到了资源,由于票还没有卖出去,所以tickets为1
             *   线程2,线程3..可以正常通过while判断,也进入休眠的流程
             *
             * 休眠时间结束,程序继续运行
             *    假设线程1先抢到票,打印:正在出售1号票,然后会把tickets的值改为:0
             *    假设线程2后抢到票,打印:正在出售0号票,然后会把tickets的值改为:-1
             *    假设线程3后抢到票,打印:正在出售-1号票,然后会把tickets的值改为:-2
             *    假设线程4后抢到票,打印:正在出售-2号票,然后会把tickets的值改为:-3
             */
            /*
             * 第x张票,出现重复值的原因:tickets--
             *   tickets-- 相当于 tickets = tickets - 1
             *   tickets-- 做了3件事:
             *      A:读值,读取tickets的值
             *      B:改值,将tickets的值 - 1
             *      C:赋值,将修改后的值重新赋值给tickets
             *   还没有来得及执行C的动作,此时别的线程抢走资源了,就会出现重复值
             */
        }
    }
}

案例代码五模拟买票的实现 【b.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/13
 * @introduction 模拟买票的实现 测试类
 */
public class Demo5TicketSellMyThreadTest {
    public static void main(String[] args) {
        //测试:卖票的动作
        //4.因为是四个窗口,所以需要创建四个线程对象  给线程自定义名字
        Demo5TicketSellMyThread mt1 = new Demo5TicketSellMyThread("窗口1");
        Demo5TicketSellMyThread mt2 = new Demo5TicketSellMyThread("窗口2");
        Demo5TicketSellMyThread mt3 = new Demo5TicketSellMyThread("窗口3");
        Demo5TicketSellMyThread mt4 = new Demo5TicketSellMyThread("窗口4");

        //5.开启线程
        mt1.start();
        mt2.start();
        mt3.start();
        mt4.start();

        /**
         * ...
         * 窗口3:正在出售第3张票
         * 窗口1:正在出售第1张票
         * 窗口3:正在出售第1张票
         * 窗口4:正在出售第0张票
         * 窗口2:正在出售第-1张票
         */
    }
}

3.1.2 实现Runnable接口方式

  (见3.2.1.2 实现Runnable接口方式)


3.2 多线程安全问题解决


3.2.1 使用同步代码块

  • 格式
synchronized(锁对象){
    //要加锁的代码
}

/* 锁对象:
 * 	 1)同步代码块的锁对象可以是任意类型的对象
 * 	 2)必须使用【同一把锁】,否则可能出现锁不住的情况
 * 
 * 同一把锁: 
 * 	 √-1) static修饰的静态对象(static Object obj = new Object();)  但不建议使用
 *	 √-2) 当前类对象的字节码文件(类名.Class) 
 *	 ×-3) this  (当前类对象) 	每个线程new一个新的对象,所以不是同一把锁 
 */

3.2.1.1 继承Thread类方式

案例代码六模拟买票的Thread方式实现-解决安全问题 【a.线程类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/13
 * @introduction 模拟买票的Thread方式实现-解决安全问题 线程类
 *
 * 多线程安全问题解决
 *      使用同步代码块 synchronized
 *          synchronized(锁对象){
 *              //要加锁的代码
 *          }
 *
 *      锁对象:
 * 	        1)同步代码块的锁对象可以是任意类型的对象
 * 	        2)必须使用【同一把锁】,否则可能出现锁不住的情况
 */
//2.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)
public class Demo6TicketSellMyThread extends Thread {
    //1.定义一个变量(tickets),记录票数
    private static int tickets = 100;//因为是共享数据,所以用static修饰,在类加载前就放入静态常量池,被所有对象共享
    //模拟一个锁对象,可以实现同步代码块,但没必要为了同步代码块专门new一个obj,直接使用本类的.class文件即可
    static Object obj = new Object();

    public Demo6TicketSellMyThread(String window) {
        super(window);
    }

    @Override
    public void run() {
        while (true) {
            /* 锁对象:
             * 	 1)同步代码块的锁对象可以是任意类型的对象
             * 	 2)必须使用【同一把锁】,否则可能出现锁不住的情况
             */
            synchronized (Demo6TicketSellMyThread.class) {//不能直接锁在while条件上,否则会导致一个窗口卖完所有票
                if (tickets < 1) {
                    break;
                }
                //B
                try {
                    sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //C
                System.out.println(getName() + ":正在出售第" + tickets-- + "张票");
            }
        }
    }
}

案例代码六模拟买票的Thread方式实现-解决安全问题 【b.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/13
 * @introduction 模拟买票的Thread方式实现-解决安全问题 测试类
 *
 * 多线程安全问题解决
 *      使用同步代码块 synchronized
 *          synchronized(锁对象){
 *              //要加锁的代码
 *          }
 *
 *      锁对象:
 * 	        1)同步代码块的锁对象可以是任意类型的对象
 * 	        2)必须使用【同一把锁】,否则可能出现锁不住的情况
 * 	            同一把锁 -> 当前类对象的字节码文件
 */
public class Demo6TicketSellMyThreadTest {
    public static void main(String[] args) {
        //测试:卖票的动作
        //4.因为是四个窗口,所以需要创建四个线程对象  给线程自定义名字
        Demo6TicketSellMyThread mt1 = new Demo6TicketSellMyThread("窗口1");
        Demo6TicketSellMyThread mt2 = new Demo6TicketSellMyThread("窗口2");
        Demo6TicketSellMyThread mt3 = new Demo6TicketSellMyThread("窗口3");
        Demo6TicketSellMyThread mt4 = new Demo6TicketSellMyThread("窗口4");

        //5.开启线程
        mt1.start();
        mt2.start();
        mt3.start();
        mt4.start();

        /** 通过同步代码块【synchronized(类名.class){}】方式解决线程安全问题
         *
         * ...
         * 窗口1:正在出售第7张票
         * 窗口1:正在出售第6张票
         * 窗口1:正在出售第5张票
         * 窗口4:正在出售第4张票
         * 窗口4:正在出售第3张票
         * 窗口4:正在出售第2张票
         * 窗口4:正在出售第1张票
         */
    }
}

3.2.1.2 实现Runnable接口方式

RunnableThread方式区别

  1) 因为四个Thread对象共用一个Runnable对象,所以tickets可以不用static修饰。

  2) 因为四个Thread对象共用一个Runnable对象,所以锁对象可以是当前类this


案例代码七模拟买票的Runnable接口方式实现-解决安全问题 【a.Runnable子类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 模拟买票的Runnable接口方式实现-解决安全问题 Runnable的实现类
 *
 * 可以理解:MyRunnable是资源类
 */
public class Demo7TicketSellMyRunnableThread implements Runnable {
    //1.定义变量,记录票
    //private static int tickets = 100; //因为是共享数据,所以用static修饰,在类加载前就放入静态常量池,被所有对象共享
    private int tickets = 100; //因为四个Thread对象共用一个Runnable对象,所以可以不用static修饰

    //2.模拟卖票
    @Override
    public void run() {
        while (true) {
            //synchronized (Demo7TicketSellMyRunnableThread.class) {
            synchronized (this) {//因为四个Thread对象共用一个Runnable对象,所以锁对象可以是当前类
                if (tickets < 1) {
                    break;
                }
                try {
                    //sleep(20);
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //System.out.println(getName() + ":正在出售第" + tickets-- + "张票");//getName()是Thread类的方法,不能使用
                //static Thread currentTrhead():返回当前正在执行的线程对象的引用
                System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets-- + "张票");
            }
        }
    }
}


案例代码七模拟买票的Runnable接口方式实现-解决安全问题 【b.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 模拟买票的Runnable接口方式实现-解决安全问题 测试类
 *
 *
 */
public class Demo7TicketSellMyRunnableThreadTest {
    public static void main(String[] args) {
        Demo7TicketSellMyRunnableThread mr = new Demo7TicketSellMyRunnableThread();

        Thread t1 = new Thread(mr,"窗口1");
        Thread t2 = new Thread(mr,"窗口2");
        Thread t3 = new Thread(mr,"窗口3");
        Thread t4 = new Thread(mr,"窗口4");

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

4 匿名内部类


  • 内部类:类里边还有一个类,里边那个类叫内部类,外边那个类叫外部类。

  • 成员内部类:定义在成员位置的内部类(类中,方法外)
  • 局部内部类:定义在局部位置的内部类(只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。)

  • 匿名内部类

    • 概述:就是没有名字局部内部类

    • 格式:

      new 类名或者接口名(){
          //重写类或者接口中 所有的 抽象方法;
      }
      
    • 本质:

      • 专业版:就是一个继承了类或者实现了接口的 匿名的子类对象
      • 大白话:匿名内部类不是类,而是子类对象
package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 匿名内部类 成员内部类&局部内部类
 */
//外部类
public class AnonymousInnerClass {
    //成员变量
    String name;

    //成员内部类
    class Inner1 {}

    //成员方法
    public void show() {
        //局部变量,基本类型
        int num = 10;

        //局部变量,引用类型
        Object obj = new Object();

        //局部变量,引用类型,局部内部类
        class Inner2 {}
    }
}

4.1 三种实现方式


案例代码八匿名内部类实现抽象类 【a.抽象类Animal】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 匿名内部类实现抽象类 抽象类Animal
 *
 */
public abstract class Demo8AbstractAnimal {
    //有抽象方法的类一定是抽象类,抽象类不一定有抽象方法
    public abstract void eat();
}

案例代码八匿名内部类实现抽象类 【b.Animal实现类Cat】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 匿名内部类实现抽象类 Animal实现类Cat
 */
public class Demo8Cat extends Demo8AbstractAnimal {
    @Override
    public void eat() {
        System.out.println("我是Cat类的eat方法,猫吃鱼");
    }
}

案例代码八匿名内部类实现抽象类 【c.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 匿名内部类实现抽象类 测试类
 *
 * 需求:调用 Demo8Animal#eat();
 */
public class Demo8AnonymousInnerClass {
    public static void main(String[] args) {
        /**
         * 方式一:【普通类】实现
         *   A:定义一个Cat类,继承Animal
         *   B:重写eat()方法
         *   C:多态方式创建Cat类的对象,调用eat()方法
         */
        Demo8AbstractAnimal an1 = new Demo8Cat();
        an1.eat();

        /**
         * 方式二:【匿名对象】实现
         */
        new Demo8Cat().eat();
        System.out.println("*********************");
        /**
         * 方式三:【匿名内部类】实现
         */
        new Demo8AbstractAnimal() {
            public void eat() {
                System.out.println("我是匿名内部类的方式实现的,猫吃鱼");
            }
        }.eat();
    }
}

4.2 开发中的应用

  • 匿名内部类在实际开发中的应用
    • 1)当对象方法(成员方法)仅调用一次的时候
    • 2)可以作为方法的实参进行传递
    • 3)采用多态方式实现多次调用
    • 个人建议
      • 当接口中或抽象类中的抽象方法仅有一个的时候,就可以考虑使用匿名内部类(如果有多个方法,匿名调用一个方法也要同时实现其他方法)

                new Demo9InterfaceJumping(){
                    @Override
                    public void jump() {
                        //我会跳,1)当对象方法(成员方法)仅调用一次的时候
                        System.out.println("我会跳,1)当对象方法(成员方法)仅调用一次的时候");
                    }
                    
                    @Override
                    public void eat() {}
                    
                    ...
                }.jump();
        

案例代码九匿名内部类在实际开发中的应用 【a.接口类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 匿名内部类在实际开发中的应用 接口
 */
public interface Demo9InterfaceJumping {
    public abstract void jump();
}

案例代码九匿名内部类在实际开发中的应用 【b.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 匿名内部类在实际开发中的应用 测试类
 *
 * 1)当对象方法(成员方法)仅调用一次的时候(如果调用多次,就采用子类对象实现方法,再调用子类对象#方法)
 * 2)可以作为方法的实参进行传递
 * 3)采用多态方式实现多次调用
 */
public class Demo9AnonymousInnerClass {
    public static void main(String[] args) {
        //对,Demo9InterfaceJumping#jump 调用一次
        //1)当对象方法(成员方法)仅调用一次的时候(如果调用多次,就采用子类对象实现方法,再调用子类对象#方法)
        new Demo9InterfaceJumping(){
            @Override
            public void jump() {
                //我会跳,1)当对象方法(成员方法)仅调用一次的时候
                System.out.println("我会跳,1)当对象方法(成员方法)仅调用一次的时候");
            }
        }.jump();

        //2)可以作为方法的实参进行传递
        show(new Demo9InterfaceJumping() {
            @Override
            public void jump() {
                //我会跳,2)可以作为方法的实参进行传递
                System.out.println("我会跳,2)可以作为方法的实参进行传递");
            }
        });

        //3)采用多态方式实现多次调用
        Demo9InterfaceJumping jm =new Demo9InterfaceJumping(){  //多态 (接口指向子类对象)
            @Override
            public void jump() {
                System.out.println("我会跳,3)采用多态方式实现多次调用");
            }
        };
        /*
         * 我会跳,3)采用多态方式实现多次调用
         * 我会跳,3)采用多态方式实现多次调用
         * 我会跳,3)采用多态方式实现多次调用
         */
        jm.jump();
        jm.jump();
        jm.jump();
    }

    //2)可以作为方法的实参进行传递
    public static void show(Demo9InterfaceJumping jm) {
        jm.jump();
    }
}

5 同步


  • 同步和效率的问题
    • 线程安全(线程同步),效率低
    • 线程不安全(线程不同步),效率高

  • 概述/作用
    • 多线程(环境)并发操作同一数据,有可能引发安全问题,就需要用到同步解决

5.1 同步代码块


  • 格式

    synchronized(锁对象){
        //要加锁的代码
    }
    
    /* 锁对象:
     * 	 1)同步代码块的锁对象可以是任意类型的对象
     * 	 2)必须使用【同一把锁】,否则可能出现锁不住的情况
     * 
     * 同一把锁: 
     * 	 √-1) static修饰的静态对象(static Object obj = new Object();)  但不建议使用
     *	 √-2) 当前类对象的字节码文件(类名.Class) 
     *	 ×-3) this  (当前类对象) 	每个线程new一个新的对象,所以不是同一把锁 
     */
    

5.2 同步方法


  • 静态方法
    • 锁对象:该类的字节码文件对象(类名.Class)

  • 非静态方法
    • 锁对象this

5.2.1 静态方法锁对象

  • 静态方法
    • 锁对象:该类的字节码文件对象(类名.Class)

案例代码十静态方法锁对象:该类的字节码文件对象 【a.方法类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 静态方法锁对象:该类的字节码文件对象 方法类
 */
public class Demo10 {
    /*
     * 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?
     *  静态方法:
     *      锁对象:该类的字节码文件对象
     *  非静态方法:
     *      锁对象:this
     *
     *  思路:
     *      1.创建两个线程
     *      2.分别调用Demo类的两个方法
     *                 一个线程调用Demo10#method1(),method1()用同步代码块
     *                 一个线程调用Demo10#method2(),method2()用同步方法
     *      3.为了让效果更明显,用while(true)循环
     */
    public static void method1(){
        //synchronized (锁对象) {
        //synchronized (String.class) { //method1锁对象是String.Class时 【没锁住】
        synchronized (Demo10.class) { //method1锁对象是Demo10.Class(该类的字节码)时 【锁住了】
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.print("\r\n");
        }
    }

    //静态方法锁对象:该类的字节码文件对象(Demo10.class)
    public synchronized static void method2(){
        System.out.print("i");
        System.out.print("t");
        System.out.print("c");
        System.out.print("a");
        System.out.print("s");
        System.out.print("t");
        System.out.print("\r\n");
    }
}

案例代码十静态方法锁对象:该类的字节码文件对象 【b.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 静态方法锁对象:该类的字节码文件对象 测试类
 */
public class Demo10SynchronizedTarget {
    public static void main(String[] args) {
        /*
         * 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?
         *  静态方法:
         *      锁对象:该类的字节码文件对象
         *  非静态方法:
         *      锁对象:this
         *
         *  思路:
         *      1.创建两个线程
         *      2.分别调用Demo类的两个方法
         *                 一个线程调用Demo10#method1(),method1()用同步代码块
         *                 一个线程调用Demo10#method2(),method2()用同步方法
         *      3.为了让效果更明显,用while(true)循环
         */

        //1.创建一个线程
        new Thread() {
            public void run() {
                //2.一个线程调用Demo10#method1(),method1()用同步代码块
                //3.为了让效果更明显,用while(true)循环
                while (true) Demo10.method1();
            }
        }.start();

        //1.创建一个线程
        new Thread() {
            public void run() {
                //2.一个线程调用Demo10#method2(),method2()用同步方法
                //3.为了让效果更明显,用while(true)循环
                while (true) Demo10.method2();
            }
        }.start();

        /** method1锁对象是String.Class时 【没锁住】
         *
         * 黑itcast
         * 马程序员
         * 黑马程序员
         * 黑马程序员itcast
         */

        /** method1锁对象是Demo10.Class(该类的字节码)时 【锁住了】
         *
         * itcast
         * itcast
         * 黑马程序员
         * 黑马程序员
         */
    }
}

5.2.2 非静态方法锁对象

  • 非静态方法
    • 锁对象this

案例代码十一非静态方法锁对象:this 【a.方法类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 非静态方法锁对象:this 方法类
 */
public class Demo11 {
    /*
     * 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?
     *  静态方法:
     *      锁对象:该类的字节码文件对象
     *  非静态方法:
     *      锁对象:this
     *
     *  思路:
     *      1.创建两个线程
     *      2.分别调用Demo类的两个方法
     *                 一个线程调用Demo11#method1(),method1()用同步代码块
     *                 一个线程调用Demo11#method2(),method2()用同步方法
     *      3.为了让效果更明显,用while(true)循环
     */
    public void method1(){
        //synchronized (Demo11.class) { //method1锁对象是Demo11.Class(该类的字节码)时 【没锁住】
        synchronized (this) { //method1锁对象是this时 【锁住了】
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.print("\r\n");
        }
    }

    //非静态方法锁对象:this
    public synchronized void method2(){
        System.out.print("i");
        System.out.print("t");
        System.out.print("c");
        System.out.print("a");
        System.out.print("s");
        System.out.print("t");
        System.out.print("\r\n");
    }
}

案例代码十一非静态方法锁对象:this 【b.测试类】

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 静态方法锁对象:该类的字节码文件对象 测试类
 */
public class Demo11SynchronizedTarget {
    public static void main(String[] args) {
        /*
         * 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?
         *  静态方法:
         *      锁对象:该类的字节码文件对象
         *  非静态方法:
         *      锁对象:this
         *
         *  思路:
         *      1.创建两个线程
         *      2.分别调用Demo类的两个方法
         *                 一个线程调用Demo11#method1(),method1()用同步代码块
         *                 一个线程调用Demo11#method2(),method2()用同步方法
         *      3.为了让效果更明显,用while(true)循环
         */

        //jdk1.8这里默认了关键字final Demo11 demo = new Demo11();
        Demo11 demo = new Demo11();
        //demo = new Demo11();  //因为demo是final修饰的,所以不能重新赋值

        new Thread() {public void run() { while (true) demo.method1(); }}.start();
        new Thread() {public void run() { while (true) demo.method2(); }}.start();

        /** method1锁对象是Demo11.Class(该类的字节码)时 【没锁住】
         *
         * 黑马itcast
         * itcast程序员
         * 黑马程序员
         */

        /** method1锁对象是this时 【锁住了】
         *
         * itcast
         * itcast
         * 黑马程序员
         * 黑马程序员
         */
    }
}

6 多线程的难点


6.1 死锁


  • 死锁

    • 1)死锁需要两个线程,两把锁
    • 2)
      • 一个线程先拿锁A,再拿锁B
      • 另一个线程先拿锁B,再拿锁A
    • 3)为了让效果更明显,用while(true)改进

    在这里插入图片描述


案例代码十二死锁的实现

package com.groupies.base.day14;

/**
 * @author GroupiesM
 * @date 2021/05/17
 * @introduction 死锁的实现
 *
 * 1)死锁需要两个线程,两把锁
 * 2)
 *   一个线程先拿锁A,再拿锁B
 *   另一个线程先拿锁B,再拿锁A
 * 3)为了让效果更明显,用while(true)改进
 *
 */
public class Demo12DeadLock {
    public static final String LOCKA = "锁A";
    public static final String LOCKB = "锁B";

    public static void main(String[] args) {
        /*
        new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }){}.start();
        */

        new Thread() {
            public void run() {
                //3)为了让效果更明显,用while(true)改进
                while (true) {
                    //2)一个线程先拿锁A,再拿锁B
                    synchronized (LOCKA) {
                        System.out.println("线程一获取到" + LOCKA + ",等待" + LOCKB);
                        synchronized (LOCKB) {
                            System.out.println("线程一获取到" + LOCKB + ",成功进入小房间...");
                        }
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                //3)为了让效果更明显,用while(true)改进
                while (true) {
                    //2)另一个线程先拿锁B,再拿锁A
                    System.out.println("线程二获取到" + LOCKB + ",等待" + LOCKA);
                    synchronized (LOCKB) {
                        synchronized (LOCKA) {
                            System.out.println("线程二获取到" + LOCKA + ",成功进入小房间...");
                        }
                    }
                }
            }
        }.start();

        /** 死锁的实现:线程一与线程二互相等待对方手里的那把锁
         *
         * 线程一获取到锁B,成功进入小房间...
         * 线程一获取到锁A,等待锁B
         * 线程一获取到锁B,成功进入小房间...
         * 线程二获取到锁B,等待锁A
         * 线程一获取到锁A,等待锁B
         */
    }
}

6.2 多线程的生命周期


1.新建:创建线程对象
2.就绪:线程对象已经启动了,但是还没有获取到CPU的执行权
3.运行(有可能会发生阻塞或者等待状态) :获取到了CPU的执行权
    阻塞:没有CPU的执行权,回到就绪
4.死亡:代码运行完毕,线程消亡
    
//唤醒在此对象监视器上等待的单个线程(随机唤醒)
Object#notify()
//唤醒在此对象监视器上等待的所有线程(随机唤醒)
Object#notifyAll()
//在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
Object#wait()

线程生命周期(一)

在这里插入图片描述

1.新建:创建线程对象
2.就绪:线程对象已经启动了,但是还没有获取到CPU的执行权
3.运行(有可能会发生阻塞或者等待状态) :获取到了CPU的执行权
    阻塞:一般指IO流阻塞,没有CPU的执行权,回到就绪
    等待:一般是可控的,等待另一线程先执行完毕或等待
4.死亡:代码运行完毕,线程消亡

//唤醒在此对象监视器上等待的单个线程(随机唤醒)
Object#notify()
//唤醒在此对象监视器上等待的所有线程(随机唤醒)
Object#notifyAll()
//在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
Object#wait()
//等待(插队) ->  
//主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。    
Thread#join()
//等待(休眠),单位毫秒
Thread#sleep()

线程生命周期(二)

在这里插入图片描述


7.面试题


  • 1.多线程并行和并发的区别是什么
并行:两个(多个)线程同时执行.   (提前: 需要多核CPU)
并发:两个(多个)线程同时请求执行, 但是同一瞬间, CPU只能执行一个
     于是就安排它们交替执行, 因为时间间隔非常短, 我们看起来好像是同时执行的, 其实不是

  • 2.Java程序是多线程的吗?
是,因为至少开启了main(主线程),GC(垃圾回收线程)

  • 3.多线程的两种实现方式之间的区别是什么?
//Java中的继承特点:类只能单继承,接口可以多继承 
继承Thread类:
    好处:代码相对比较简单	//因为是继承Thread类,所以可以直接使用Thread类中的非私有成员(成员变量,成员方法)
    弊端:扩展性相对比较差	//因为是继承,而Java中类之间的继承只能单继承,不能多继承,但是可以多层继承
    
实现Runnable接口:
    好处:扩展性相对比较强  
    弊端:代码相对比较繁琐 

//修饰符
因为多个Thread对象共用一个Runnable对象,所以可以不用static修饰票数tickets。
//锁对象
因为多个Thread对象共用一个Runnable对象,所以锁对象可以是当前类this。(Thread类锁对象一般是当前类的字节码文件)

  • 4.多线程的执行特点是什么?
随机性、延迟性

延迟性的原因:因为CPU在做着高效的切换
延迟性的体现:关闭QQ后,当前已打开的QQ对话框会延迟几秒才会退出

  • 5.多线程的默认命名规则是什么
命名规则:Thread-${索引编号} //Thread-0  Thread-1
索引编号:从0开始

  • 6.匿名内部类(本质是一个对象)
内部类:
    概述:类里边还有一个类,里边那个类叫内部类,外边那个类叫外部类。

    分类:
    	成员内部类:定义在成员位置的内部类(类中,方法外)
    	局部内部类:定义在局部位置的内部类(只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。)

匿名内部类:
    概述:就是没有名字的局部内部类
    格式:
    	new 类名或者接口名(){
    		//重写类或者接口中 所有的 抽象方法;
		}
	本质:
		专业版:就是一个继承了类或者实现了接口的 匿名的子类对象
		大白话:匿名内部类不是类,而是子类对象
        
匿名内部类在实际开发中的应用:
	1)当对象方法(成员方法)<font color=red>仅调用一次</font>的时候
	2)可以作为方法的实参进行传递
	3)采用多态方式实现多次调用

	个人建议
		当接口中或抽象类中的抽象方法仅有一个的时候,就可以考虑使用匿名内部类(如果有多个方法,匿名调用一个方法也要同时实现其他方法)
        new Demo9InterfaceJumping(){
             @Override
             public void jump() {System.out.println("我会跳");}  
             @Override
             public void eat() {}
             ...
         }.jump();

  • 7.实现Runnable接口的原理
实现原理:多态
背景: 
    多线程的第一种实现方式是:继承Thread类,因为我们自定义的类(MyThread)Thread类的子类
    所以MyThread类的对象调用start()方法的时候,自动调用MyThread#run()
    
    但是MyRunnable类是实现了Runnable接口,而Runnable#run()Thread#start()没有关系

问:为什么Thread#start(),会自动调用Runnable接口的子类(MyRunnable)中的run()方法呢?

简化版的源码:
    //测试类中的代码:
    public static void main(String[] args){
    	MyRunnable mr = new MyRunnable();
    	Thread th = new Thread(mr);
    	th.start();//问:为什么会自动调用MyRunnable#run();?
	}
    
	//简化版的源码:
	public class Thread{
        private Runnable target;		//new MyRunnable();
            
        public Thread(Runnable target){
            this.target = target;		//new MyRunnable();
        }

        public void run(){
            if(target != null){
                target.run();			//new MyRunnable().run();
            }
        }
    }


	//Thread源码:
	public class Thread implements Runnable {
        
    	/* What will be run. */
    	private Runnable target;
        
        /**
         * Allocates a new {@code Thread} object. This constructor has the same
         * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
         * {@code (null, target, gname)}, where {@code gname} is a newly generated
         * name. Automatically generated names are of the form
         * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
         *
         * @param  target
         *         the object whose {@code run} method is invoked when this thread
         *         is started. If {@code null}, this classes {@code run} method does
         *         nothing.
         */
        public Thread(Runnable target) {
        	init(null, target, "Thread-" + nextThreadNum(), 0);
    	}
        
        /**
         * Initializes a Thread with the current AccessControlContext.
         * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
         */
        private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
            init(g, target, name, stackSize, null, true);
        }
        
        /**
         * Initializes a Thread.
         *
         * @param g the Thread group
         * @param target the object whose run() method gets called
         * @param name the name of the new Thread
         * @param stackSize the desired stack size for the new thread, or
         *        zero to indicate that this parameter is to be ignored.
         * @param acc the AccessControlContext to inherit, or
         *            AccessController.getContext() if null
         * @param inheritThreadLocals if {@code true}, inherit initial values for
         *            inheritable thread-locals from the constructing thread
         */
		private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
            if (name == null) {
                throw new NullPointerException("name cannot be null");
            }

            this.name = name;

            Thread parent = currentThread();
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
                /* Determine if it's an applet or not */

                /* If there is a security manager, ask the security manager
                   what to do. */
                if (security != null) {
                    g = security.getThreadGroup();
                }

                /* If the security doesn't have a strong opinion of the matter
                   use the parent thread group. */
                if (g == null) {
                    g = parent.getThreadGroup();
                }
            }

            /* checkAccess regardless of whether or not threadgroup is
               explicitly passed in. */
            g.checkAccess();

            /*
             * Do we have the required permissions?
             */
            if (security != null) {
                if (isCCLOverridden(getClass())) {
                    security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
                }
            }

            g.addUnstarted();

            this.group = g;
            this.daemon = parent.isDaemon();
            this.priority = parent.getPriority();
            if (security == null || isCCLOverridden(parent.getClass()))
                this.contextClassLoader = parent.getContextClassLoader();
            else
                this.contextClassLoader = parent.contextClassLoader;
            this.inheritedAccessControlContext =
                    acc != null ? acc : AccessController.getContext();
            /*****************************************************/
            /*****************************************************/
            //关键代码
            this.target = target;
            /*****************************************************/
            /*****************************************************/
            setPriority(priority);
            if (inheritThreadLocals && parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
            /* Stash the specified stack size in case the VM cares */
            this.stackSize = stackSize;

            /* Set thread ID */
            tid = nextThreadID();
        }
        
        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    }

  • 8.public static final有没有顺序要求
没有,可以随意调换顺序

  • 9.jdk1.8新特性,当匿名内部类访问其所在方法的局部变量时,该变量必须加final修饰,为什么?
//5.2.2 非静态方法锁对象
为了延长该变量的生命周期
但是jdk1.8起,final可以不写,因为程序会默认加上

public static void main(String[] args) {
    /** 局部方法随着main方法调用结束,被GC回收
     *  此时成员方法method1()尚未被GC回收,就会导致demo.method1找不到demo引用对象
     *  所以需要用final修饰所在方法的局部变量,将其放入静态常量池,延长生命周期
     */
	Demo demo = new Demo();
	new Thread() {public void run() { while (true) demo.method1(); }}.start();    
}    

  • 10.手写一个死锁代码
6.1

  • 11.多线程的生命周期是什么?
6.2

  • 12.数据结构 - 对象创建的过程

    在这里插入图片描述


21/05/18

M

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值