栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

JavaSE进阶-多线程

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JavaSE进阶-多线程


    多线程相关概念                     【了解】
    多线程的三种实现方式                【重点】
    Thread类的常用方法                 【重点】
    多线程安全问题                     【重点】

多线程相关概念                     【了解】
    什么是多线程
        是一种可以让程序同时执行多个任务的技术,可以提供程序的执行效率。

    线程和进程的关系
        进程:正在执行的程序(软件),占用内存和CPU,进程中至少包含一条线程,如果一条都没有则进程也会随之结束
        线程:是进程中的一条执行路径(执行单元)

        单线程:单条执行路径
        多线程:多条执行路径

    并发和并行
        并行:有多个任务同时(同一时刻)执行,多核CPU支持
        并发:有多个任务交替(同一时间段)执行,严格说并非真正的同时执行

        Java中创建的多线程程序是并发执行的


多线程的三种实现方式                【重点】
    方式一:继承Thread类
            步骤:
                1、自定义一个类继承Thread类
                2、重写run方法,run方法就是线程任务所在的方法
                    public void run(){
                    }
                3、在测试类中,创建自定义类的对象
                4、启动线程

                重写的原则:一大两同两小
                    * 重写的时候run方法的声明完成不能改
                    * run方法不能抛异常,如果run方法中有异常,只能try...catch处理

            方式一代码解析:
            1、start方法的作用
                启动线程,并调用run方法
                将run方法中设置的线程任务放到新开启的线程中进行执行
            2、run方法的作用
                用于设置线程任务,线程启动会执行其中的代码
                由于不同的线程执行的任务不同,我们肯定需要对
                父类Thread中的run方法进行重写,对于第一种实现方式来说
                父类Thread中的run方法相当于没有做任务的事情
             3、启动线程调用的是start还是run方法
                调用start方法
                start:启动线程并调用run方法
                run:不会启动线程,只会普通方式在原来的线程中调用run方法

    方式二:实现Runnable接口
            步骤:
                1、自定义一个类实现Runnable接口
                2、重写run方法
                3、在测试类中创建自定义类的对象
                4、创建Thread类的对象,然后将自定义类的对象当做构造方法的参数传递进去
                5、通过Thread类的对象调用start方法启动线程

        方式二代码解析:
            1、为什么多了一个步骤,将实现类对象传递到Thread类对象中?
                自定义类的对象实现的是Runnable接口,没有start方法
                不能直接调用并启动线程,所以要借助Thread类的对象来启动线程,
                而我们的实现类就只做一件事情:设置线程任务

                方式二让线程对象和线程任务分离了

             2、 将线程任务设置到线程对象中,最终是如何执行到线程任务的
                Thread th = new Thread(mt) -> this.target = target;
                th.start() ->th.run() ->if (target != null) { target.run(); }

        创建和启动线程的简化写法
            1、匿名对象方法
                方式一:
                    new MyThread().start();
                方式二:
                    new Thread(new MyThread()).start();
            2、匿名内部类
                方式一:
                     new Thread(){
                        @Override
                        public void run() {
                            // 设置线程任务
                        }
                    }.start();
                方式二:
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            // 设置线程任务
                        }
                    }).start();
            3、Lambda表达式
                方式二:
                    new Thread(()->{
                        // 设置线程任务
                    }).start();

    方式三:实现Callable接口
            步骤:
                1、自定义一个类实现Callable接口
                2、重写call方法,作用类似于run方法也是用于设置线程任务的
                    call方法和run方法的两点不同:
                        1、call方法有返回值,而run方法没有返回值
                        2、call方法可以抛异常,而run方法不可以

                3、在测试类中创建自定义类的对象
                4、创建FutureTask的对象,将自定义类对象当做构造方法参数传递进去
                5、创建Thread类的对象,将FutureTask的对象当做构造方法参数传递进去
                6、通过Thread类的对象调用start方法启动线程


    实现线程的三种方式的区别
        1、方式一(继承方式)和另外两种(实现方式)的区别
            方式一:
                好处:继承了之后就是Thread类的子类,可以直接使用Thread类中的方法,编程相对简单
                弊端:扩展性差,Java是单继承的,如果一个类已经继承了其他类,就不能使用这种方式了,
                        另外如果继承Thread类,下次要抽取父类也做不到了
             另外两种:
                好处:扩展性好,Java类在继承的同时可以实现接口,所以如果一个类已经继承了其他类,依然可以
                    使用这种方式,同样的即使实现了接口,也可以抽取父类
                弊端:在这些实现类不能直接使用Thread类中的方法,编程相对复杂

        2、方式三(实现Callable)和另外两种(继承Thread类和实现Runnable接口)的区别
            方式三:
                任务方法是call
                可以返回线程任务执行之后的结果
                支持在处理线程任务的时候抛出异常(throws)

            另外两种:
                 任务方法是run
                 无法得到线程任务执行之后的结果
                 在处理线程任务的时候不能抛出异常

        推荐的实现方式
            方式二
            方式三
            方式一

Thread类的常用方法                 【重点】
    构造方法
        Thread(Runnable r)
        Thread(String name)
        Thread()
        Thread(Runnable r, String name)

    成员方法
        void start()
        void run()
        设置和获取线程名称
            获取
                String getName():获取线程名称

            设置
                线程如果没有设置名称,会有默认名称:
                    普通线程:Thread-x,x从0开始依次递增
                    主线程:主方法所在的线程,默认名称是main

                * void setName(String name) :设置线程名称
                * 构造方法
                     Thread(String name)
                     Thread(Runnable r, String name)

        static Thread currentThread() :获取当前正在执行的线程对象
        static void sleep(long millis) :线程休眠,参数传递的是毫秒值
        设置和获取优先级
            void setPriority(int newPriority)
            int getPriority()
        设置守护线程(用户线程/后台线程)
             void setDaemon(boolean on):on传递true,就能设置为守护线程          【了解】
             当所有的非守护线程执行完毕,守护线程也会随之结束。

多线程安全问题                     【重点】
    线程安全问题的演示
        模拟三个窗口卖100张票,使用多线程实现

        三个窗口:三个线程,线程任务一样的
        演示看到的现象:卖出了重复票和不存在的票

    线程安全问题的原因解析
        重复票:tickets--之前出现的线程CUP被抢夺的情况
        不存在的票:判断之后,卖票之前出现线程CUP被抢夺的情况

        产生问题的通用条件:
            有多线程,操作共享数据,而且操作的共享数据有多条语句。

            如果是单线程不会有线程安全问题
            如果是多线程但是没有操作共享数据,而是操作各自的数据也不会有线程安全问题

    线程安全问题的解决
        方案一:同步代码块
            格式:
                synchronized(锁对象){

                }
                synchronized:关键字,同步
                锁对象:首先是一个对象,而且可以是任意对象,但是多线程看到的该对象必须是同一个

                同步代码块的特点:
                    同一时刻只能有一个线程进入到同步代码块中执行。

                    线程进入到同步代码块之前必须先判断锁是否被其他线程获取,如果被获取了,只能在同步代码块之外等待
                    如果没有被获取,则当前线程获取锁,接下来才能进入到同步代码块中执行,执行完之后,才会归还锁,
                    其他线程才可以继续抢夺锁
        方案二:同步方法
            同步方法:使用synchronized关键字修饰的方法

            非静态同步方法
                修饰符 synchronized 返回值类型 方法名(参数列表){
                    方法体;
                }

            静态同步方法
                修饰符 static synchronized 返回值类型 方法名(参数列表){
                    方法体;
                }

                方法大括号中的方法体内容都是被同步(被锁)的代码
            面试题:
                非静态同步方法和静态同步方法的锁分别是什么?
                非静态同步方法:this
                静态同步方法:当前类的字节码对象  类名.class
        方案三:Lock锁
            三个步骤:
                1、创建锁
                2、锁定
                3、解锁


代码块
    Java中只有四种代码块(局部代码块、构造代码块、静态代码块和同步代码块)
    代码块:使用一对大括号包裹起来的代码区域

    局部代码块
        格式:
            {

            }
        位置:
            在方法中
        作用:控制变量的作用域,或者节省一点点内存
        特点:变量只能在局部代码块中使用,出了局部代码块则无法使用

    构造代码块
        格式:
            {

            }
        位置:
            类中方法外
        作用:用于抽取所有构造方法前面的重复代码
        特点:每执行一次构造方法都会执行一次构造代码块
            而且先于构造方法执行

    静态代码块
        格式:
            static{

            }
        位置:
            类中方法外
        作用:
            一切一次性的操作,并且要非常早的操作(准备操作)都可以放到静态代码块中执行
            比如:
                jdbc注册驱动
                加载配置文件
        特点:
            1、执行时机非常早,比当前类的主方法还早,随着类的加载而执行
            2、一个类只会加载一次,所以静态代码块只会执行一次

    面试题:分析以下代码的执行结果?


 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1034750.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号