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!
=============================================================================
Copyright ©2018-2019
【转载文章务必保留出处和署名,谢谢!】
【转载文章务必保留出处和署名,谢谢!】