多线程
进程和线程
进程:应用程序的执行实例,有独立的内存空间和系统资源
线程:CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程
多线程
- 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”
- 多个线程交替占用CPU资源,而非真正的并行执行
- 多线程的好处
- 充分利用CPU的资源
- 简化编程模型
- 良好的用户体验
- 并行执行
- 通常表示同一个时刻有多条指令代码在处理器上同时运行
- 往往需要多个处理器支持
- 并发运行
- 表示在一个处理器中,操作系统为了提高程序的运行效率,将CPU的执行时间分成多个时间片,分配给同一进程的不同线程
- 多个线程分享CPU时间,交替执行
java.lang.Thread类
-
支持多线程
-
常用方法
方法 描述 类型 Thread() 创建Thread对象 构造方法 Thread(Runnable target) 创建Thread对象,target为run()方法被调用的对象 构造方法 Thread(Runnable target,String name) 创建Thread对象,target为run()方法被调用的对象,name为新线程的名称 构造方法 void run() 执行任务操作的方法 实例方法 void start() 使该线程开始执行,JVM将调用该线程的run()方法 实例方法 void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) 静态方法 Thread currentThread() 返回当前线程对象的引用 静态方法
主线程
- Java程序启动时,一个线程立即随之启动,通常称之为程序的主线程
- main()方法即为主线程入口‘
- 产生其他子线程的线程
- 必须最后完成执行,因为它执行各种关闭动作
线程的创建
- Java中创建线程的两种方式
- 继承java.lang.Thread类
- 实现java.lang.Runnable接口
线程休眠
- 让线程暂时睡眠指定时长,线程进入阻塞状态
- 睡眠时间过后线程会再进入可进行状态
public static sleep(long millis)
- millis为休眠时长,以毫秒为单位
- 调用sleep()方法需处理InterruptedException异常
注:
- 已启动的线程对象不能重复调用start()方法,否则会抛出IllegalThreadStateException异常
- 如果调用sleep()方法控制线程休眠时间的线程,被其他线程中断,则会产生InterruptedException异常
继承Thread类创建多线程
- 多个线程交替执行,不是真正的“并行” (并发)
- 线程每次执行时长由分配的CPU时间片长度决定
实现Runnable接口创建多线程
- Runnable接口位于java.lang包
- 只提供一个抽象方法run()的声明
实现Callable接口创建多线程
- 重写call()方法
比较两种创建线程的方式
- 继承Thread类
- 编写简单,可直接操作线程
- 适用于单继承
- 实现Runnable
- 避免单继承局限性
- 便于共享资源
线程的状态
-
通常,线程的生命周期有五种状态
-
处于运行状态的线程会让出CPU控制权
- 线程运行完毕
- 有比当前线程优先级更高的线程抢占了CPU
- 线程休眠
- 线程因等待某个资源而处于阻塞状态
线程调度
-
指按照特定机制为多个线程分配CPU的使用权
-
每个线程执行时都具有一定的优先级
-
常用的线程操作方法、
方法 说明 int getPriority() 返回线程的优先级 void setPriority(int Priority()) 更改线程的优先级 boolean isAlive() 测试线程是否处于活动状态 void join() 进程中其他线程必须等待该线程终止后才能执行 void interrupt() 中断线程 void yield() 暂停当前正在执行的线程对象,并执行其他线程
线程优先级
- 线程优先级由1~10表示,1最低,默认优先级为5
- 优先级高的线程获得CPU资源的概率较大
- 使用Thread类的静态常量设置线程的优先级
- MAX_PRIORITY:值是10,表示优先级最高
- MIN_PRIORITY:值是1,表示优先级最低
- NORM_PRIORITY:值是5,表示普通优先级
注:
- 尽管为线程设定了不同的优先级,但实际上并不能精确控制这些线程的执行先后顺序
- 在不同的计算机或同一计算机不同时刻中运行本程序,都会得到不同的执行序列
线程的强制运行
-
使当前线程暂停运行,等待其他线程结束后再继续执行本线程
-
join()方法的重载方法
public final void join(); public final void join(long mills); public final void join(long mills,int nanos);
mills:以毫秒为单位的等待时长
nanos:要等待的附加纳秒时长
需要处理InterruptedException异常
线程的礼让
-
暂停当前线程,允许其他具有相同优先级的线程获得运行机会
-
该线程处于就绪状态,不转为阻塞状态
-
yield()方法定义
public static void yield()
注:只是提供一种可能,但是不能保证一定会实现礼让
- 执行Thread.yield()方法后,多个线程间交替执行较为频繁,可以提高程序的并发性。
比较sleep()方法和yield()方法
- 共同点
- Thread类的静态方法
- 会使当前处于运行状态的线程放弃CPU使用权,将运行机会让给其他线程
- 不同点
- sleep()方法会给其他线程运行机会,不考虑其他线程的优先级,因此较低优先级线程可能会获得运行机会
- yield()方法只会将运行机会让给相同优先级或者更高优先级的线程
- 调用sleep()方法需处理InterruptedException异常,而调用yield()方法无此需求
- sleep()方法会让线程变成阻塞状态,而yield()方法不不会
线程同步
- 当两个或多个线程需要访问同一资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用
- 相当于将线程中需要一次性完成不允许中断的操作加上一把锁,以解决冲突
- 使用synchronized关键字,为当前的线程声明一把锁
- 实现方式
- 同步代码块
- 同步方法
同步代码块
-
使用synchronized关键字修饰的代码块
synchronized(syncObject){ //需同步的代码块 } //syncObject 指需同步的对象,通常为this 对象
-
如果一个代码块带有synchronized(obj)标记,那么当前线程执行此代码块时,必须先获得obj变量所引用对象的锁
对于任何代码块都可以任意指定上锁的对象,灵活性更高
线程同步的特征
- 不同的线程在执行以同一个对象作为锁标记的同步代码块或同步方法时,因为要获得这个对象的锁而相互牵制
- 多个并发线程访问同一资源的同步代码块或同步方法时
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
- 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码块
- 如果多个线程访问的不是同一共享资源,无需同步
注:
- 使用同步代码块和同步方法完成线程同步,二者的实现结果没有区别
- 不同点
- 同步方法便于阅读理解
- 同步代码块更精确地限制访问区域,会更高效
线程安全的类型
-
如果程序所在的进程中,有多个线程同时同时进行,每次运行结果和单线程时运行结果是一样的,且其他变量的值也和预期相同,则当前程序是线程安全的
-
查看ArrayList类的add()方法定义
public boolean add(E e){ ensureCapacityInternal(size+1); //集合扩容,确保能新增数据 elementData[size++]=e; //在新位置存放数据 return true; }
-
ArrayList类的add()方法为非同步方法
-
当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题
ArrayList为非线程安全的类型
方法是否同步 | 效率比较 | 适合场景 | |
---|---|---|---|
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
注:为达到安全性和效率的平衡,可以根据实际场景选择合适的类型