文章目录
线程与进程的区别
- 进程是操作系统
分配资源的最小单元,线程是操作系统调度的最小单元,也可以说是进程里的一个执行单元 - 一个程序至少有一个进程,一个进程至少有一个线程
线程的生命周期
-
新建【NEW】:当程序使用new关键字创建了一个Thread对象后,该线程就处于新建状态,此时的 JVM 为其分配内存,并且初始化其成员变量的值;例Thread t1 = new Thread() -
就绪状态/可运行状态【RUNNABLE】:当线程对象调用start()方法后,该线程就处于可运行状态,此时的 JVM 会为其创建方法调用栈和程序计数器,等获取到 CPU 后执行 -
运行状态【RUNNING】: Thread 类里面没有这个状态在就绪状态的线程获取到了 CPU 的使用权时候开始执行run()方法,该线程就处于运行状态 -
阻塞状态【BLOCKED】:线程因为某种原因放弃了 CPU 的使用权,也让出了 cpu 时间片,暂时的停止运行直到线程再次获取到 CPU 的使用权,才有机会再次获得 CPU 的时间片转到运行状态。在同步阻塞的情况下,在获取到对象的同步锁时,如果该同步锁被其他的线程所占用,JVM 会把该线程放入到锁池中,当线程获取到锁的时,线程重新转为可运行状态 -
等待状态【WAITING】:运行状态的线程执行wait()方法后, JVM 会把该线程放入等待队列 -
限时等待【TIMED_WAITING】:运行状态的线程执行sleep()/join()方法后进入到限时等待状态,当sleep() 超时 / join() 等待线程终止后,线程重新转为可运行状态 终止【TERMINATED】:
线程的创建方式
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口,需要实现的是 call() 方法
- 使用线程池
如何停止正在运行的线程
-
正常地运行结束
-
使用退出标志退出线程,使用
boolean类型的标志,并通过这个标志的为false/true来控制while循环是否退出,同时需要使用volatile关键字进行修饰,这个关键的目的是使标志同步,并且修改标志位的值时让其他线程可见 -
interrupt()结束线程-
如果一个线程由于等待某些事件的发生而阻塞【例:Thread.sleep()/Thread.join()/IO 等待时】,当调用
Thread.interrupt()方法时,会抛出InterruptException异常,通过代码捕获该异常,然后break跳出循环。 -
如果一个线程未处于阻塞状态:使用
isInterrupted()方法判断线程的中断标志来退出循环,当使用interrupt()方法的时候中断标志会设置为true,类似自定义标志退出循环
-
-
stop()方法停止线程【不推荐】: Thread 类里面也提供了stop()方法来停止线程,但是该方法是很危险的,就像是突然关闭计算机,可能会产生不可预料的后果。在调用Thread#stop()方法之后,创建子线程的线程就会抛出ThreadDeathError的错误,并且会释放子线程的所有锁,一般情况下对加锁的代码都是为了保证数据的一致性,突然释放所有的锁可能被保护的数据出现不一致性。
volatile 关键字的作用
变量可见性:保证了该变量对所有的线程是可见的。【例:线程 one 修改了变量的值,那么其他线程都可以获取到最新值】
禁止指令重排序
-
Java 语言提供的一种稍弱的同步机制,
volatile关键字 用于确保将变量的更新操作通知给其他的线程;volatile具备两种特性,它不会被缓存在寄存器【CPU 缓存】,所以在读取 volatile 关键字修饰的变量时总是会从主内存中返回最新值。 -
对非
volatile关键字修饰的变量进行读写操作的时候,每个线程先从主内存拷贝变量的值到 CPU cache 中,假如有多个 CPU,每个线程可能在不同的 CPU 上处理,这意味着每个线程可以拷贝到不同的 CPU 缓存。 -
使用
volatile关键字修饰的变量,JVM跳过了 CPU 缓存,保证了每次读写都是对主内存进行操作。 -
对
volatile关键字修饰的变量的单次 read/write 操作是可以保证原子性的,比如long/double/boolean类型的变量,因为long/double/boolean都是 64 位,对于这两种数据类型的读是分为两个部分,第一次读取的是 32 位,然后再读取剩下的 32 位,在这个过程中不是原子操作。但JVM中的volatile关键字修饰的long/double/boolean后, 其读写就是原子操作。但是对于 自增/自减 并不能保证原子性,因为它们本质上是读和写两次操作。
wait() 方法与 sleep() 方法的区别
-
sleep()方法是线程类Thread的静态方法,调用此方法设置休眠时间,会让出CPU的使用权给其他线程,但还是持有对象锁,当休眠时间结束时,该线程会回到就绪状态【RUNNABLE】 -
wait()方法是 Object 类中的方法,调用Object#wait()方法后会导致当前线程放弃对象锁(线程暂停执行),也会让出CPU的使用权给其他线程,进入到等待池,只有针对此对象调用Object#notify()方法后该线程才准备获取对象锁进入运行状态【RUNNING】
为什么需要把 wait()/notify() 放在 synchronized 代码块里?
- JAVA 的 API 强制要求放置在同步块中,如果不这样做会抛出 IllegalMonitorStateException 异常,还有一个原因是为了避免
Object#wait()、Object#notify()之间产生竞态条件
start() 与 run() 方法的区别
-
start()方法是真正启动一个线程,实现了多线程运行,调用了start()方法之后,此线程就进入到了就绪状态【RUNNABLE】 -
run()方法里称之为线程体,包含了要执行这个线程的内容,线程就进入到了运行状态【RUNNING】,开始运行run()方法中的代码