初步探讨线程问题
Posted maozhaoqin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初步探讨线程问题相关的知识,希望对你有一定的参考价值。
一、线程与进程
在了解线程前,首先我们先了解一下什么叫进程。
进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。简单来说,一个应用程序的运行就可以被看做是一个进程。
注意点:
线程是进程的组成部分,一个线程必须有一个父进程
一个程序运行后至少有一个进程,一个进程至少要包含一个线程
线程是操作系统进行调度的单位
线程不拥有系统资源,多个线程共享父进程的资源
单线程的程序,只有一个执行流
多线程的程序,有多个执行流
多线程可以让一个应用程序的多个顺序执行流同时执行(宏观)
多个线程是相互独立的、并发执行的
随机执行的
使用java命令运行Java程序一次,就是在操作系统中启动一个JVM进程,Java程序的线程都在JVM进程中
二、并行和并发
进程只是一个静态的概念,机器上的一个.class文件,机器上的一个.exe文件,这个叫做一个进程。程序的执行过程都是这样的:首先把程序的代码放到内存的代码区里面,代码放到代码区后并没有马上开始执行,但这时候说明了一个进程准备开始,进程已经产生了,但还没有开始执行,这就是进程,所以进程其实是一个静态的概念,它本身就不能动。平常所说的进程的执行指的是进程里面主线程开始执行了,也就是main()方法开始执行了。进程是一个静态的概念,在我们机器里面实际上运行的都是线程。
Windows操作系统是支持多线程的,它可以同时执行很多个线程,也支持多进程,因此Windows操作系统是支持多线程多进程的操作系统。Linux和Uinux也是支持多线程和多进程的操作系统。DOS就不是支持多线程和多进程了,它只支持单进程,在同一个时间点只能有一个进程在执行,这就叫单线程。
CPU难道真的很神通广大,能够同时执行那么多程序吗?不是的,CPU的执行是这样的:CPU的速度很快,一秒钟可以算好几亿次,因此CPU把自己的时间分成一个个小时间片,我这个时间片执行你一会,下一个时间片执行他一会,再下一个时间片又执行其他人一会,虽然有几十个线程,但一样可以在很短的时间内把他们通通都执行一遍,但对我们人来说,CPU的执行速度太快了,因此看起来就像是在同时执行一样,但实际上在一个时间点上,CPU只有一个线程在运行。
学习线程首先要理清楚三个概念:
- 进程:进程是一个静态的概念
- 线程:一个进程里面有一个主线程叫main()方法,是一个程序里面的,一个进程里面不同的执行路径。
- 在同一个时间点上,一个CPU只能支持一个线程在执行。因为CPU运行的速度很快,因此我们看起来的感觉就像是多线程一样。
什么才是真正的多线程?如果你的机器是双CPU,或者是双核,这确确实实是多线程。
注意点:
并行
在同一时刻,同时执行
多核CPU
并发
在同一时间段,快速轮换、交替执行
在某一时刻只有某一个或某几个在执行,在宏观上具有同时执行的效果
单核、多核
三、创建和启动线程
线程对象必须是Thread类或Thread子类的对象
运行Java程序的main方法,会启动一个主线程,主线程的线程执行体就是main方法的方法体
一个线程可以启动其他的线程
主线程的名字默认是main
其他线程的名字按创建顺序默认是Thread-0、Thread-1、。。。
3.1、Thread子类
1.定义Thread类的子类,重写从Thread继承的run方法,
在方法体中编写当前线程要完成的任务(要执行的一段代码),
run方法的方法体叫做线程执行体
2.创建Thread子类的对象,得到一个线程对象
3.调用线程对象的start()方法,启动当前线程
共享资源(对象)可以时静态的或通过构造方法接收
3.2、Runnable接口
1.定义Runnable接口的实现类,重写Runnable接口的run方法,该run方法的方法体就是线程执行体
Runnable 用于代表线程的执行体
2.调用Thread类的构造方法,创建Thread对象时,传入Runnable接口类型的对象
3.调用线程对象的start()方法,启动当前线程
共享资源可以作为Runnable接口实现类的成员存在
四、Thread常用方法
String getName() :返回当前线程的名字
void setName(String name):设置当前线程的名字
static Thread currentThread():返回当前正在运行的线程
static void sleep(long ms):让当前正在执行的线程暂停一段时间,并进入阻塞状态
static void yield():让当前正在执行的线程暂停,并进入就绪状态
void join():加入线程
让当前正在执行的线程等待,等待被加入的线程执行完
阻塞状态
五、线程的生命周期
1、新建状态:
使用new关键字创建了线程对象还未启动,没调start()2、就绪状态:
已经调用了线程对象的start()方法由操作系统线程调度器决定什么时候运行
只能用处在新建状态线程调用start(),否则IllegalThreadStateException
3、运行状态:
线程被调度 获得了CPU使用权,开始执行线程执行体4、阻塞状态:
sleep(long ms) ,经过了指定的时间阻塞式IO,已经返回
阻塞状态解除后,会重新进入就绪状态,重新等待线程调度器的再次调度,
以便接着从停止的地方,继续执行
获取正在被其他线程持有的同步监视器的锁,获得了锁
正在等待某个通知,其他线程发出了通知
5、死亡状态
线程执行体正常执行完
六、线程优先级
优先级高的线程获得执行的机会较多,优先级低的线程获得执行的机会较少主线程优先级默认是5,其他线程优先级默认与创建它的线程的优先级相同
Threadvoid setPriority(int priority)
int getPriority()
优先级的有效值1-10的整数
Thread类中定义的优先级常量:
MIN_PRIORITY 1
NORM_PRIORITY 5
MAX_PRIORITY 10
七、线程同步
同步锁 Lock7.1、线程安全问题
多个线程并发修改同一个共享资源,导致的不正确结果问题线程不安全
7.2、同步代码块
synchronized(obj){代码块
}
synchronized:
关键字
同步obj:
同步监视器
一个Java对象,每个Java对象中都有一把锁
通常使用要并发修改的共享资源作为同步监视器
一个线程要执行同步代码块,必须先获取同步监视器的锁,对同步监视器上锁
任意时刻,一个对象的锁只能被一个线程获得
一个线程执行完同步代码块,会自动释放占有对象锁
获取锁 --> 加锁 --> 执行 --> 释放锁
7.3、同步方法
修饰符 synchronized 返回值类型 方法名(形参列表){方法体}在同一时间段内,只能有一个线程在执行同步方法,直至执行完
同步方法的同步监视器是this
7.4、线程会释放同步监视器的锁的情况
同步代码块、同步方法正常执行完同步代码块、同步方法抛出了异常
同步代码块、同步方法中调用执行了同步监视器对象的wait方法
同步代码块、同步方法中调用执行了sleep、yield、join、阻塞式IO暂停当前线程的执行,并不会释放同步监视器的锁
7.5、死锁
两个线程相互等待对方释放同步监视器锁的现象
避免
八、线程的通信
必须由同步监视器对象来调用,否则java.lang.IllegalMonitorStateExceptionObject
void wait()
void wait(long ms)
void wait(long ms, int nanos)让当前正在执行的线程释放锁,并在当前监视器上等待,并进入阻塞状态
等待接收到对应监视器对象的通知或等待指定的时间后,进入锁池
void notify()
随机唤醒一个在当前同步监视器上等待的线程
void notifyAll()
唤醒所有在当前同步监视器上等待的线程
九、ThreadLocal<T>
线程本地变量用于隔离多个线程各自的资源,保证当前线程获取到的值一定是自己放进去的
获取线程自己放进去的值
T 要保存的值的类型
方法
void set(T value)
当正在运行的前线程放入一个值
一个线程只能放入一个值,再次放入,覆盖
T get()
获取当前线程放入的值
void remove()
删除当前线程放入的值
以上是关于初步探讨线程问题的主要内容,如果未能解决你的问题,请参考以下文章