您的位置:首页 > 编程语言 > Java开发

java多线程 基础理解

2017-10-20 00:00 274 查看
原文:http://www.cnblogs.com/ckb58/p/7698813.html

一、概述

理解多线程先要理解线程,理解线程先要理解进程。

1. 进程

一个正在执行的程序。

每个进程的执行都有一个执行的顺序,顺序是一个执行路径,也叫一个控制单元。

2. 线程

进程中独立的控制单元称为线程。

线程控制进程的执行。

进程中只要有一个线程在执行,进程就不会结束。

一个进程中至少存在一个线程。

3. 多线程

Java 虚拟机启动时,会有一个 java.exe 的执行程序,也就是一个进程。

这个进程中至少存在一个线程负责 java 程序的执行,这个线程的运行代码存在 main 方法中,这个线程称之为主线程。

JVM 启动时除了执行一个主线程,还会启动负责垃圾回收机制的线程。

在一个进程中有多个线程执行的方式,称为多线程。

4. 多线程的意义

多线程能让程序产生同时运行的效果,可以提高程序执行的效率。

例如:

java.exe 进程执行主程序时,如果程序的代码非常多,在堆内存中会产生很多对象,而对象调用完后就会变成垃圾。如果垃圾过多的话,可能会导致堆内存出现内存不足的现象,影响程序的运行。这种情况下,如果只有一个线程在运行处理的话,程序执行的效率非常低;如果有多个线程在帮助处理的话,程序执行的效率将大大的提高。

例如:垃圾回收机制的线程在帮助进行垃圾回收的话,那堆内存空间的释放将快很多。

5. CPU 运行的原理

PC 上有很多程序“同时”进行,看起来好像是 CPU “同时”处理所有程序似的,其实在同一时刻,单核的 CPU 只能运行一个程序,看起来“同时”运行的效果,实际上只是 CPU 在多个线程之间做快速切换的动作而已。

CPU 执行哪个程序,或者说是哪个程序抢到了 CPU 的执行权,哪个程序就执行,CPU 不会只执行一个线程,执行完一个后,会执行另一个,或者说是另一个线程抢走了 CPU 的执行权。至于如何执行是由 CPU 所决定。

CPU 执行哪个程序,是毫无规律的,这是多线程的特性:随机性。

二、创建方式

创建线程的方式:继承和实现。

1. 继承

Java 中已经提供了对线程描述的类 —— Thread。继承 Thread 类,重写(覆盖)run 方法,创建线程。

步骤:

定义类继承 Thread;

重写(覆盖)Thread 中的 run 方法;

自定义代码存储在 run 方法中,让线程执行。

创建自定义类的实例对象;

相当于创建线程。

实例对象调用线程的 start 方法。

启动线程,调用 run 方法。

注:如果对象直接调用 run 方法,那么就只有一个线程在执行,自定义的线程并没有启动。

重写(覆盖)run 方法的原因:Thread 类用于描述线程,类中定义了一个功能,用于存储线程要执行的代码,存储这个功能的就是 run 方法。总而言之,Thread 中的 run 方法用于存储线程要执行的代码。

例如:


结果如下



注:线程是随机、交替执行的,每次运行的结果都不同

2. 实现

使用继承 Thread 创建线程的方式有弊端,就是如果类继承了其它的父类,就无法使用 Thread 来创建线程,于是便有了通过实现 Runnable 接口来创建线程。实现 Runnable 接口,重写(覆盖)run 方法,创建线程。

步骤:

定义类实现 Runnable;

重写(覆盖)Runnable 中的 run 方法;

自定义代码存储在 run 方法中,让线程执行。

通过 Thread 类创建线程对象。

将 Runnable 的子类对象作为实际参数传递给 Thread 的构造函数;

将 Runnable 的子类对象传递给 Thread 的构造函数的原因:

自定义的 run 方法所属的对象是 Runnable 的子类对象,要让线程去指定对象的 run 方法,就必须明确 run 方法所属的对象。

调用 Thread 中的 start 方法启动线程。

start 方法会自动调用 Runnable 子类的 run 方法。

好处:避免了单继承的局限性。(定义线程时,建议优先使用)

例如:





结果如下:



注:线程是随机、交替执行的,每次运行的结果都不同。

三、区别及状态

1. 创建方式的区别

继承:线程代码存储在 Thread 子类的 run 方法中。

实现:线程代码存储在 Runnable 子类的 run 方法中。

2. 状态

被创建:等待启动,调用 start 启动。

运行状态:具有执行资格和执行权。

临时状态(阻塞):具有执行资格,但没有执行权。

冻结状态:遇到 sleep(time) 方法和 wait() 方法时,失去执行资格和执行权;sleep 方法的时间结束或调用 notify() 方法时,获得执行资格,变为临时状态(阻塞)。

消亡状态:调用 stop() 方法或 run 方法结束。

注:线程从创建状态到了运行状态后,再次调用 start() 方法时,已经没有任何意义,Java 运行时会提示线程状态异常。



四、安全问题

1. 原因

当多条语句操作同一个线程的共享数据时,一个线程对多条语句只执行了一部分,没执行完成时,另外的线程参与执行,会导致共享数据的错误异常,也就是线程的安全问题。

总而言之:

线程的随机性。

多个线程访问出现延迟。

注:线程的安全问题在理想状态下,一般不容易出现,但是一旦出现线程的安全问题,将会对程序软件造成非常大的影响。

2. 同步

对于线程的安全问题,在对多条操作共享数据的语句时,只让一个线程执行完,再让下个线程去执行,每条线程在执行的过程中,其它线程都不可以参与执行。

Java 中提供了专业的解决办法 —— synchronized(同步)。

解决的方式:同步代码块和同步函数。(均是使用关键字 synchronized 实现)

同步代码块

格式:
synchronized(对象){
需要被同步的代码;
}


同步之所以可以解决线程的安全问题,根本原因在于对象上,对象如果加了同步锁,持有锁的线程可以在同步中执行,没持有锁的线程即使获取 CPU 的执行权,也无法进入去执行,因为没有获取到同步锁。

例如:



同步函数

格式:
在函数上加 synchronized 修饰符即可。


函数需要被对象所调用,函数就拥有一个所属对象的引用,就是 this,也就是说同步函数所使用的锁是 this。

例如:



前提:

必须有两个或以上的线程;

必须是多个线程使用同一个锁。

利与弊:

利:解决了多线程的安全问题。

弊:多个线程均需要判断锁,消耗资源,影响效率。

如何寻找多线程的安全问题?

明确共享数据;

明确哪些代码是多线程运行的代码;

明确多线程运行的代码中哪些语句是操作共享数据的。

3. 静态函数的同步

同步函数被静态所修饰后,使用的同步锁不再是 this,因为静态函数中不可以定义 this,静态进入内存时,内存中没有本类对象,但一定存在类所对应的字节码文件对象。

例如:类名.class(对象的类型是 Class)

静态函数所使用的同步锁就是所在类的字节码文件对象。

类名.class

例如:



4. 死锁

同步中嵌套同步时,有可能出现死锁现象。

例如:





说明:程序卡死,无法继续执行。

五、通信

多个线程操作同一个资源,但是操作的动作不相同,就是线程间通信。

同步操作同一个资源

例如:





问题点:

wait()、notify()、notifyAll() 用来操作线程的,为什么是定义在 Object 类中呢?

这些方法存在于同步中;

使用这些方法时必须要有标识所属的同步锁;

例如:同一个锁上 wait 的线程,只能被同一个锁上的 notify 唤醒。

锁可以是任意的对象,任意对象调用的方法一定要定义在 Object 类中。

wait() 和 sleep() 有什么区别呢?

wait():释放 CPU 的执行权,释放同步锁。

sleep():释放 CPU 的执行权,不释放同步锁。

为什么要定义 notifyAll()?

需要唤醒对方线程时,如果只用 notify(),容易出现只唤醒本方线程的情况,会导致程序中所有线程都处于等待状态。

JDK 5 及以上版本中提供了多线程同步锁的升级解决方案

将 synchronized(同步)替换成 Lock,将 Object 类中的 wait()、notify()、notifyAll() 替换成 Condition 对象。

Condition 对象可通过 Lock(锁)进行获取,并且支持多个相关的 Condition 对象。

例如:





六、线程停止

JDK 5 之前,停止线程用 stop() 方法,JDK 5 及以上版本中,stop() 方法已过时。

线程停止的办法就是让 run() 方法结束。

启动多线程运行一般都是使用循环结构的代码,只需控制循环的条件,就可以让 run() 方法结束,也就是线程停止。

例如:



说明:只要在主函数或者其它线程中,对标记 flag 赋值 false,就可以让 run() 方法结束,线程停止。

特殊情况:当线程处于冻结状态时,无法读取到 run() 方法中的代码,线程就无法停止。

需要对线程的冻结状态进行清除,强制让线程恢复运行,Thread 类中提供了 interrupt();

例如:





七、什么情况需要多线程?

某些代码需要同时执行时,可用单独的线程封装,多线程运行执行。

例如



八、拓展

join();

临时加入线程去执行:

当 A 线程执行到了 B 线程的 join(); 方法时,A 线程等待,B 线程执行完后,A 线程才继续执行(此时的 B 线程与其它线程交替执行)。

setPriority();

设置优先级:

MIN_PRIORITY:最低优先级 1

MAX_PRIORITY:最高优先级 10

NORM_PRIORITY:默认优先级

yield();

可以暂停当前线程,让其它线程执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: