Java多线程(基础)
知识点
进程和线程
进程是操作系统资源分配的基本单位(每个进程都有独立的代码和数据空间)
线程是cpu调度的最小单位
并行与并发:
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS(吞吐量)或者QPS(每秒查询率)来反应这个系统的处理能力。
可见性、原子性、有序性
**可见性:**指线程之间,一个线程的状态改变对另一个线程是可见的。volatile修饰的变量就具有可见性。
volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。但是,volatile修饰的变量不具有原子性。在Java中volatile、synchronized、final具有可见性。
**原子性: **JVM中执行的最小单位,具有不可分割性。非原子操作会存在线程安全问题。Java的concurrent包下提供了一些原子类,AtomicInteger、AtomicLong、AtomicReference。在Java中synchronzed、lock、unlock中保证原子性。
**有序性:**Java中synchronized和volatile保证线程之间操作的有序性。volatile本身具有禁止指令进行重排,synchronized在同一个时刻只允许一个线程对其进行操作,也就决定了具有同一个对象锁的两个同步块只能串行执行。
为什么notify()、wait()等函数定义在Object类中而不是Thread类中
notify()、wait()依赖于“同步锁”,而“同步锁”是该对象所持有。可以把wait()方法写进任何同步控制方法里,而不用考虑这个类是继承Thread还是实现Runnable接口。
线程安全与同步
**线程安全:**经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果。
**同步:**Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
yield()、sleep()、wait()
yield()是让线程由“运行状态”进入到“就绪状态”,不会释放对象的同步锁
sleep()的作用是让当前线程由“运行状态”进入到“休眠(阻塞)”状态,不会释放对象的同步锁。
wait()的作用是让当前的线程由“运行状态”进入到“等待(阻塞)状态”的同时,会释放同步锁。
join()和interrupt()
如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive()返回为假)。也可以在调用jion()时带上一个超时参数(单位可以是毫秒,或者纳秒),这样如果目标线程在这段时间到期时还没有结束的话,join()方法总能返回。对jion()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法。
interrupt()和isInterrupted()
interrupt()和isInterrupted()都能够用于检测对象的“中断标记”。区别是:interrupt()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记
线程的状态
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
线程包括5种状态:
1、新建(New):线程对象被创建时,它只会短暂地处于这种状态。此时它已经分配了必须的系统资源,并执行了初始化。例如,Thread thread = new Thread()。
2、就绪(Runnable):称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启 动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3、运行(Running):线程获取CPU权限进行执行。注意:线程只能从就绪状态进入运行状态。
4、阻塞(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分为三种:
(1)等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。
(2)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
(3)其他阻塞:通过调用线程的sleep()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或是超时。或是I/O处理完毕时,线程重新转入就绪状态。
5.死亡(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java中线程的优先级的范围是1~10,默认的优先级为5。“高优先级线程”会优先于“低优先级线程”执行。
创建线程
通过继承Thread来创建线程
通过实现 Runnable 接口来创建线程
通过 Callable 和 Future 创建线程
中断线程
Thread中的stop()和suspend()方法,由于固有的不安全性,已经不建议使用。下面是通用的中断方式
@Override
public void run() {
try {
// 1. isInterrupted()保证,只要中断标记为true就终止线程。
while (!isInterrupted()) {
// 执行任务...
}
} catch (InterruptedException ie) {
// 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
}
}