Java并发(1)线程基础
重新学习 Java 并发,本文包括线程的创建,线程的状态切换方法,守护线程。
As We All Know
进程是系统分配资源和调度的基本单位
线程是在 CPU 上运行的基本单位
在 Java 中,启动 main 函数即启动一个 JVM 进程,main 函数的线程是这个进程的一个线程,也称主线程。线程有自己的程序计数器(执行位置)和栈(局部变量);进程有堆与方法区,前者存放 new 的对象实例,后者存放 JVM 加载的类、常量、静态变量,他们都是线程共享的。
线程创建
Java有三种方式创建线程:
继承 Thread 类, 重写 run() 方法
实现 Runnable 接口
使用 FutureTask
1.继承 Thread 类,重写 run() 方法:
1 |
|
main 函数中:
1 |
|
这种方法直接调用 this 就可以获得当前进程,不必使用 Thread.currentThread() 方法。缺点是 Java 不支持多继承,继承 Thread 类就不能继承其他类了。
2.实现 Runnable 接口:
1 |
|
新建线程共用一个代码逻辑,并且线程可以继承其他类。
3. FutureTask 方式
1 |
|
创建了一个 FutureTask 的对象,构造函数为 CallerTask ,使用 FutureTask 对象作为任务创建线程,这个对象可以调用 get() 方法,执行的是重写的 call()。
三种方法的后两者都是先创建任务 task,再启动。
线程状态切换
wait() 函数:
wait(), wait(long timeout), wait(long timeout,int nanos)
timeout: 超时则返回,参数 =0 时为 wait()
nanos: >0时timeout++
wait() 是 Object 方法,使用前需要获得对象的监视器锁。
某线程调用共享对象的 wait() 方法时,该线程会挂起,释放对象的监视器锁。解除 wait() 状态需要其他线程调用共享对象的 notify() 或者 notifyAll() 方法,或者调用 wait() 线程的 interrupt() 方法抛出 IllegalMonitorStateException 异常。
当调用 wait() 方法时,只有该对象的监视器锁被线程释放。
虚假唤醒: 莫名其妙被唤醒,在 wait() 外使用 while 循环防范,不满足则继续挂起。PS :这内部类看着真舒服
1 |
|
notify(),notifyAll():
一个线程调用共享对象的 notify() 方法,会唤醒一个在该变量上调用 wait() 挂起的线程,若有多个则随机唤醒一个(notifyAll() 全部唤醒)。调用 notify() 方法会释放对象监视器锁,唤醒的线程需要竞争获得监视器锁。
wait 与 notify 都是 Object 的方法.
join(), 无参且返回值为 void,线程的方法,等待该线程执行完毕。
sleep(), 线程的方法,阻塞自己一段时间,调用睡眠时不释放监视器锁
yield(), 线程的方法,让出 CPU 使用权,线程不被阻塞挂起,故下一次可能还是调度它执行
interrupt(), 中断,将线程的中断标志设置为 true 并返回,实际上还可以往下运行。但如果被 wait(),sleep(),join() 阻塞,则会在调用这些方法的地方抛出 InterruptedException 异常
boolean isInterrupted() : 检测线程是否中断,返回布尔值
boolean interrupted():检测当前线程是否中断,返回布尔值,如果中断则清除中断标志。static 可以通过 Thread 类直接调用,在主线程中调用 threadOne.interrupted 实际上返回的是主线程的中断标志。
可以在线程内调 Thread.currentThread().interrupted() 实现子线程的 interrupted 方法。
中断异常处理实例:
线程A执行过程中,sleep 阻塞20s,但可能中间某一时刻就可以继续运行了,这时候用中断处理,将它中断,强制 sleep()方法发生中断异常而返回。关键代码
1 |
|
上下文切换:在线程调度、上下文切换时,要保存现场,再次执行时恢复现场
线程死锁:操作系统都学过的四个条件:
1. 互斥条件,资源排他性
2. 请求和保持,不主动释放
3. 不剥夺条件,不能剥夺
4. 环路等待,死锁环
预防死锁的主要方法是使用资源申请的有序性原则,假如线程 A 和线程 B 都需要资源 1, 2, 3, …, n 时,对资源进行排序,线程 A 和线程 B 都只有在获取了资源 n-1 时才能去获取资源 n. 这样做是从2,4条件入手,避免死锁。
守护线程:
Java 中的线程分为两类,分别为 daemon 线程(守护线程)和 user 线程(用户线程),在 JVM 启动时会调用 main 函数,main 函数所在的线程就是一个用户线程,在 JVM内部同时还启动了许多守护线程,比如垃圾回收线程。
在 Java 中,父线程结束,子线程不一定结束。而只要有一个用户线程还没结束,正常情况下 JVM 就不会退出。如果我们想让一个线程不影响JVM结束工作,可以把它设置为守护线程。thread.setDaemo(true) ;