多线程

多线程

1.实现对线程

1.1 进程

进程 : 是正在运行的程序
* 是系统进行资源分配和调用的独立单位
* 每一个进程都有它自己的内存空间和系统资源

1.2 线程

线程 :是进程中单个顺序控制流,是一条执行路径
* 单线程 : 一个进程如果只有一条执行路径,则成为单线程。
* 多线程 : 一个进程如果有多条执行路径,则成为多线程。

1.3 多线程的实现方式

方式1:继承Thread类
* 定义一个类MyThread继承Thread类
* 在MyThread类中重写run()方法
* 创建MyThread类的对象
* 启动线程
为什么要重写run()方法?
因为run() 是用来封装被线程执行的代码
run()和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用(我觉得就是等同于顺序执行)。
start():启动线程;然后由JVM调用此线程的run()方法。

1.4 设置和获取线程名称

Thread获取当前线程:
Thread.currentThread();

Thread类中设置和获取线程名称的方法
* void setName(String name): 设置线程名称
* String getName():返回此线程的名称
* 通过构造方法也可以设置线程名称

如何获取main()方法所在的线程名称?
{
String name = Thread.currentThread().getName();
}

1.5线程调度

线程有两种调度模型
* 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
* 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些

Java 使用的是抢占式调度模型
假设计算机只有一个CPU,那么CPU在某个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令.
所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的。

Thread类中设置和获取线程优先级的方法
特别注意:
1.线程优先级高仅仅是表示线程获取CPU时间片的几率高,而不是线程优先级高的全部执行再到优先级低的
2.线程默认优先级为5 ,优先级的范围在1-10

* public final int getPriority(): 返回此线程的优先级
* public final void setPriority(int newPriority): 更改此线程的优先级

Thread.MAX_PRIORITY 10 (最大优先级)
Thread.MIN_PRIORITY 1 (最小优先级)
Thread.NORM_PRIORITY 5 (默认优先级)

1.6 线程控制
* static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数,会丢失CPU执行权,时间到期需要重新抢占CPU执行权。
* void join() 等待这个线程死亡
* void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程,Java虚拟机将退出
JAVA线程分为“即实线程”(用户线程)与“守护线程”,守护线程是优先级低,存活与否不影响JVM的退出的线程,
实现守护线程的方法是在线程start()之前setDaemon(true),否则会抛出一个IllegalThreadStateException异常。
不能操作文件、数据库等资源,避免主线程关闭而未能关闭守护线程的资源,并且它会在任何时候甚至在一个操作的中间发生中断。
守护线程优点及使用场景
在主线程关闭后无需手动关闭守护线程,因为会自动关闭,避免了麻烦,Java垃圾回收线程就是一个典型的守护线程,
简单粗暴的可以理解为所有为线程服务而不涉及资源的线程都能设置为守护线程。

1.7 线程的生命周期
1:新建状态 (特别注意 线程对象的start方法只能调用一次,否则报错:IllegalThreadStateException)
使用new创建一个线程对象,仅仅在堆中分配内存空间,在调用start方法之前. 新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已.
2:可运行状态(runnable)
分成两种状态,ready和running。分别表示就绪状态和运行状态。
就绪状态:线程对象调用start方法之后,等待JVM的调度(此时该线程并没有运行).
运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行.
3:阻塞状态(blocked)
正在运行的线程因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态.此时JVM不会给线程分配CPU,直到线程重新进入就绪状态,
才有机会转到运行状态.阻塞状态只能先进入就绪状态,不能直接进入运行状态.
阻塞状态的两种情况:
1):当A线程处于运行过程时,试图获取同步锁时,却被B线程获取.此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态.
2):当线程处于运行过程时,发出了IO请求时,此时进入阻塞状态.
4:等待状态(waiting)(等待状态只能被其他线程唤醒)
此时使用的无参数的wait方法,
1):当线程处于运行过程时,调用了wait()方法,此时JVM把当前线程存在对象等待池中.
5:计时等待状态(timed waiting)(使用了带参数的wait方法或者sleep方法)
1):当线程处于运行过程时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中.
2):当前线程执行了sleep(long time)方法.
6:终止状态(terminated)
常称为死亡状态,表示线程终止.
1):正常执行完run方法而退出(正常死亡).
2):遇到异常而退出(出现异常之后,程序就会中断)(意外死亡).
1.8 多线程的实现方式

多线程的实现方案有两种
* 继承Thread类
* 实现Runnable接口

相比继承Thread类,实现Runnable接口的好处
1.避免了Java单继承的局限性(线程类只是实现了Runnable接口,还可以继承其他类)
2.在这种方式下,可以多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,
从而可以将CPU、代码数据分开,形成清晰的模型,较好的体现面向对象的思想。

方式二:实现Runnable
* 定义一个类MyRunnable实现Runnable接口
* 在MyRunnable类中实现run()方法
* 创建MyRunnable类的对象
* 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
* 启动线程 thread.start();

2.0 线程同步
当多个线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。因此,任何一个线程都有可能在任何指令处被操作系统暂停,然后在某个时间段后继续执行。
这个时候,有个单线程模型下不存在的问题就来了:如果多个线程同时读写共享变量,会出现数据不一致的问题。

2.1 同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
* 格式 synchronized(任意对象){ 多条语句操作共享数据的代码 }
* synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁。

同步的好处与弊端
* 好处 : 解决了多线程的数据安全问题
* 弊端 : 当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中降低程序的运行效率。

注意:
多线程同时读写共享变量时,会造成逻辑错误,因此需要通过synchronized同步;
同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码;
注意加锁对象必须是同一个实例;
对JVM定义的单个原子操作不需要同步。

2.2 同步方法
同步方法:用synchronized修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this;
* 格式 : private synchronized void sellTicket(){}

同步静态方法:就是把synchronized 关键字加到静态方法上, 静态同步方法 加锁的对象是 类名.class 例如:SellTicket.class
* 格式 : private static synchronized void sellTicket(){}



未完待续















posted @ 2021-01-03 22:34  year12  阅读(81)  评论(0编辑  收藏  举报