java基础学习_多线程01_多线程_day23总结

java基础学习_多线程01_多线程_day23总结

=============================================================================
=============================================================================
涉及到的知识点有:
1:多线程(理解)
    (1)多线程的概述
    (2)Java程序的运行原理及JVM的启动是多线程的吗?
    (3)多线程的实现方案(掌握)
    (4)线程的调度模型和如何获取和设置线程优先级
    (5)线程的控制(即线程常见的方法)
    (6)线程的生命周期(参照:03_线程的生命周期图解.png)
    (7)电影院卖票程序的实现
    (8)电影院卖票程序出现问题
    (9)多线程安全问题产生的原因(这些原因也是我们以后判断一个程序是否有线程安全问题的依据)
    (10)同步解决线程安全问题
    (11)回顾以前的线程安全的类
=============================================================================
=============================================================================
1:多线程(理解)
    (1)多线程的概述        
        进程:正在运行的应用程序。进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
        线程:是进程(程序)的执行单元,执行路径。
        单线程:一个应用程序只有一条执行路径。
        多线程:一个应用程序有多条执行路径。
        
        一个进程 = 一个正在运行的程序 = 1个线程+1个线程+1个线程+... = 多个线程 = 多个任务
        
        多进程的意义?
            提高CPU的使用率。
        多线程的意义?
            提高应用程序的使用率。

        注意两个词汇的区别:并行和并发。
            并行:前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
            并发:后者是物理上同时发生,指在某一个时间点同时运行多个程序。
        
            在java就业班中会有如何解决高并发?

--------------------------------------
    (2)Java程序的运行原理及JVM的启动是多线程的吗?
        A:Java程序的运行原理
            Java通过java命令会启动java虚拟机。启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
            该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的main方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的。
            
        B:JVM的启动是多线程的吗?
            垃圾回收线程也要先启动,否则很容易会出现内存溢出。
            JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
--------------------------------------
    (3)多线程的实现方案(掌握)
        A:自定义类继承Thread类
            1:自定义类MyThread继承Thread类
            2:MyThread类里面重写run()方法
            3:在测测试类MyThreadTest中创建MyThread类的对象
            4:启动线程
        B:自定义类实现Runnable接口
            1:自定义类MyRunnable实现Runnable接口
            2:MyRunnable类里面重写run()方法
            3:在测测试类MyRunnableTest中创建MyRunnable类的对象
            4;在测测试类MyRunnableTest中再创建Thread类的对象,并把3步骤的对象作为构造参数进行传递
            5:启动线程
--------------------------------------        
        注意事项:
            1:Thread类的方法:
                public final String getName() 获取线程对象的名称(一般放在需要被线程执行的代run()方法里面)
                public final void setName(String name) 设置线程对象的名称
                    对象名.setName("林青霞");
                    
            2:在不是Thread类的子类中,如何获取线程对象的名称呢?
                public static Thread currentThread() 返回当前正在执行的线程对象(静态方法)
                    Thread.currentThread().getName()
            
            3:该自定义的类为什么要重写run()方法?
                自定义类中不是所有的代码都需要被线程执行。
                而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()方法,用来包含那些需要被线程执行的代码。    
                    注意:这里的  被线程执行 = 开一个新线程执行    
            
            4:由于自定义类实现了接口,所以就不能在自定义类中直接使用Thread类的getName()方法了,但是可以间接的使用。
                Thread.currentThread().getName()
--------------------------------------                
        小问题:
            1:为什么要重写run()方法?
                答:run()方法里面封装的是被线程执行的代码。    
            2:启动线程对象用的是哪个方法?
                答:start()方法
            3:run()方法和start()方法的区别?
                答:run()方法直接调用仅仅是普通方法。
                    start()方法是先启动线程,再由jvm去调用run()方法。
            4:有了方式1,为什么还来一个方式2呢?
                答:若自定义类MyThread类已经有一个父类了,那么它就不可以再去继承Thread类了。(java不支持多继承)
                    若自定义类MyRunnable类已经实现了一个接口了,那么它还可以再去实现Runnable接口。(java支持多实现)
                    即可以避免由于Java单继承带来的局限性。
            
                    在测试类MyThreadTest中,要想开多个线程,就要先new多个自定义类MyThread的对象,每一个自定义类MyThread的对象的成员变量都相同,这样需要在栈中开辟很多内存;
                    在测试类MyRunnableTest中,要想开多个线程,只需要new一个自定义类MyRunnable的对象,再new多个Thread类的对象即可,这样就大大节约了内存。
                    即适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码和数据有效分离(即耦合性降低),较好的体现了Java面向对象的设计思想。
--------------------------------------
    (4)线程的调度模型和如何获取和设置线程优先级
        假如我们的计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。
        那么Java是如何对线程进行调用的呢?线程有两种调度模型。
        
        A:线程的调度模型
            a:分时调度模型
                所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
            b:抢占式调度模型 (Java采用的是该调度方式)
                优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片的概率相对高一些。 
                
        B:如何获取和设置线程优先级
            线程默认的优先级是:5。
            线程优先级的范围是:1-10。
            
            如何获取线程对象的优先级?
                public final int getPriority() 返回线程对象的优先级
                    int i = 对象名.getPriority();
                    
            如何设置线程对象的优先级?
                public final void setPriority(int newPriority) 更改线程的优先级
                    对象名.setPriority(10);
                
            IllegalArgumentException:非法参数异常
                抛出的异常表明向方法传递了一个不合法或不正确的参数。 
--------------------------------------
    (5)线程的控制(即线程常见的方法)
        A:线程休眠
            public static void sleep(long millis)    单位是毫秒(该方法会抛出异常)
                Thread.sleep(1000);
                
        B:线程加入
            public final void join()     等待该线程终止(为了使某线程先执行完毕)(该方法会抛出异常)
                对象名.join(); // 该方法必须在启动线程后调用
                
        C:线程礼让
            public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 能够在一定的程度上,让多个线程的执行更和谐,但是不能靠它保证一个线程一次。
                Thread.yield();
                
        D:后台线程(守护线程/用户线程)
            public final void setDaemon(boolean on)     将该线程标记为守护线程或用户线程
                对象名.setDaemon(true); // 设置守护线程
                当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。
                
        E:中断(终止)线程(掌握)
            public final void stop()     让线程停止,过时了,但是还可以使用。(为什么会过时呢?因为该方法太暴力了,具有固有的不安全性,直接把线程停止,该线程之后的代码都不能执行了)
                对象名.stop();
            public void interrupt()     中断线程。 把线程的状态终止,并抛出一个InterruptedException异常。
                对象名.interrupt();
                
        注意事项:
            如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws。
--------------------------------------
    (6)线程的生命周期(参照:03_线程的生命周期图解.png)
        A:线程的创建
            创建线程对象,无资格无权。
        B:线程的就绪
            有资格无权
        C:线程的运行
            有资格有权
        D:线程的阻塞
            无资格无权
        E:线程的死亡
            无资格无权

--------------------------------------
    (7)电影院卖票程序的实现
        A:自定义类继承Thread类
        B:自定义类实现Runnable接口
--------------------------------------
    (8)电影院卖票程序出现问题
        A:为了更符合真实的场景,加入了休眠100毫秒。程序就出现了问题。
        B:但出现了卖票问题
            a:相同的票出现多次
                CPU的一次操作必须是原子性的(即这个操作不能再拆分了)。原子性 = 最简单基本的
            b:出现了负数票
                线程的随机性和延迟导致的。
--------------------------------------
    (9)多线程安全问题产生的原因(这些原因也是我们以后判断一个程序是否有线程安全问题的依据)
        A:是否是多线程环境
        B:是否有共享数据
        C:是否有多条语句操作共享数据
--------------------------------------
    (10)同步解决线程安全问题
        A:同步代码块
            synchronized(对象) {
                需要被同步的代码;
            }
            这里的锁对象可以是任意对象。
            
        B:同步方法
            把同步加在方法上。
            这里的锁对象是this。
            
        C:静态同步方法
            把同步加在方法上,再加上静态。
            这里的锁对象是当前类的字节码文件对象(反射再讲字节码文件对象)。
--------------------------------------
        详解如下:
         * A:同步代码块的格式及其锁对象问题?
         *         格式:
         *         synchronized (对象名称) {
         *             需要同步的代码;
         *         }
         * 
         *         同步代码块的锁对象是谁呢?
         *             任意对象。
         * 
         * B:同步方法的格式及其锁对象问题?
         *         如果一个方法一进去就看到了代码被同步了,那么我就在想能不能把这个同步加在方法上呢? 答:能。
         *         把同步关键字加在方法上。
         *         格式:
         *         synchronized private void sellTicket() {...}
         *         private synchronized void sellTicket() {...}    // 习惯上这样写
         * 
         *         同步方法的锁对象是谁呢?(方法的内部有一个你看不到的对象是this啊,傻瓜哈)
         *             this
         * 
         * C:静态同步方法的格式及其锁对象问题?
         *         格式:
         *         private static synchronized void sellTicket() {...}
         *         
         *         静态同步方法的锁对象是谁呢?
         *             当前类的字节码文件对象。(反射会讲)
         * 
         *         类的初始化过程:Person p = new Person(); // 第一步做的事情是:把Person.class文件加载进内存。在Person.class文件中找到main方法并放到栈。
         *         因为静态是随着类的加载而加载。此时对象this根本就不存在。此时的对象是.class文件(字节码文件)。
         * 
         *         简言之:要想同步,需要先确定同步的对象。
         *                 要在静态同步方法加载之前就得先确定同步的对象,(否则你跟我咋同步)
         *                     谁比静态先存在呢? 答:只有.class文件(字节码文件)
         *    
         * 那么,我们到底使用谁?
         *        如果锁对象是this,就可以考虑使用同步方法。
         *        否则能使用同步代码块的尽量使用同步代码块。
--------------------------------------            
        同步的特点:
            前提:
                多个线程
                多个线程使用的是同一个锁对象
                
        同步的好处:
            同步的出现解决了多线程的安全问题。
        同步的弊端:
            当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
--------------------------------------
    (11)回顾以前的线程安全的类
        A:StringBuffer
        B:Vector
        C:Hashtable
        D:如何把一个线程不安全的集合类变成一个线程安全的集合类
            用Collections工具类的方法即可。
            
        示例代码如下:
        
        // 线程安全的类
        StringBuffer sb = new StringBuffer(); // 几乎所有的方法都加l了synchronized,所以线程安全,但效率低。
        Vector<String> v = new Vector<String>(); // 几乎所有的方法都加了synchronized,所以线程安全,但效率低。
        Hashtable<String, String> h = new Hashtable<String, String>(); // 几乎所有的方法都加了synchronized,所以线程安全,但效率低。

        // Vector是线程安全的时候才会去考虑使用的,但是呢,即使要安全,也不用Vector。
        // 为什么呢?那么到底用谁呢?
        // Collections工具类的让集合同步的方法,以List举例:
        // public static <T> List<T> synchronizedList(List<T> list)
        List<String> list1 = new ArrayList<String>(); // 线程不安全的List
        List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全的List
        // 通过Collections类的让集合同步的方法,就把线程不安全的List变成线程安全的List了,所以我们不用Vector!
=============================================================================

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

Java代码编译和执行的整个过程

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

891
来自专栏九彩拼盘的叨叨叨

glob 介绍

glob 最早是出现在类Unix系统的命令行中, 是用来匹配文件路径的。比如,lib/**/*.js 匹配 lib 目录下所有的 js 文件。

913
来自专栏微信公众号:Java团长

《深入理解Java虚拟机》笔记

也就是说,我们完全可以做一个工具,从一个文件中读入指令,然后将这些指令运行起来。上面代码中“编好的机器指令”当然指的是能在CPU上运行的,如果这里我还实现了一个...

601
来自专栏Java成长之路

深入理解多线程

多线程是java中比较重要的一部分内容,使用多线程有许多的优点: - 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。 - 程序需要实现一些需...

1783
来自专栏java一日一条

Java代码编译和执行的整个过程

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

1102
来自专栏向治洪

数据结构之堆和栈

内存分配策略     按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.  静态存储分配是指在编译时就能确定每个数据目标...

2299
来自专栏Java编程技术

Dubbo剖析-增强SPI的实现

在Duboo剖析-整体架构分析中介绍了dubbo中除了Service 和 Config 层为 API外,其他各层均为SPI,为SPI意味着下面各层都是组件化可以...

1381
来自专栏程序员互动联盟

【编程基础】你是否真的了解main()函数?

最近看到很多人、甚至市面上的一些书籍,都使用了void main() ,其实这是错误的。C/C++中从来没有定义过void main() 。C++之父 Bjar...

3316
来自专栏Python小屋

Python+pickle读写二进制文件小案例

对于二进制文件,不能使用记事本或其他文本编辑软件进行正常读写,也无法通过Python的文件对象直接读取和理解二进制文件的内容。必须正确理解二进制文件结构和序列化...

3276
来自专栏java一日一条

JAVA 动态代理

为了使代理类和被代理类对第三方有相同的函数,代理类和被代理类一般实现一个公共的interface,该interface定义如下

983

扫码关注云+社区

领取腾讯云代金券