jc_线程基础
进程与线程
进程: 资源分配的最小单元(内存地址、 文件I/O等),一个进程就是一个正在执行的程序的实例
线程: cpu执行调度的最小单元,轻量级进程,java的线程是基于操作系统原生线程模型来实现的,采用 1:1 的线程模型
线程调度
1)协同式调度:线程的执行时间由自身控制,执行完之后主动通知系统切换到另外一个线程
2)抢占式调度:由操作系统来分配每个线程的执行时间,线程的切换不由线程自身来决定
线程优先级
不是一项稳定的调节手段,操作系统大多会忽略我们的优先级设置
而且操作系统的线程优先级和java中的1-10优先级不一致,例如:win系统中只有7个
java线程
1)用户线程/守护线程
用户线程: 默认新创建的是用户线程,当jvm中不存在用户线程的时,jvm进程将会退出
守护线程: 创建线程时且启动前设置Thread.setDaemon(true),例如GC线程
2)线程的状态
操作系统线程状态: 运行中、阻塞、就绪、终止
java线程状态: java.lang.Thread.State,共6种状态
(1)初始状态-NEW: 线程被创建,但没有调用start()
(2)运行状态-RUNNABLE: 将操作系统中的就绪和运行统称为运行态,调用了start方法后的状态
(3)阻塞状态-BLOCKED: 线程阻塞于锁(synchronized)
(4)等待状态-WAITING: 线程进入等待状态,需要等待其他线程做出(通知或中断)
(5)超时等待状态-TIME_WAITING: 该状态不同于WAITING,它可以在指定时间自行返回
(6)终止状态-TERMINATED: 表示当前线程已经执行完毕
3)线程的启动/停止
(1)构造
(1)继承Thread类,重写run方法(线程的主方法),创建实例
(2)实现Runnable接口,重写run方法,然后再利用Thread的构造方法创建Thread实例,new Thread(() -> {System.out.println(“子线程”);});
(2)启动
调用线程对象的thread.start()方法
(3)停止
(1)安全停止:使用实例字段标志判断,推荐使用
1 | new Thread(new Runnable() { |
(2)停止(已过时,不要使用)thread.stop()
(3)中断(不建议使用):提前中断结束线程,如果遇到抛出InterruptedException的方法,则发生异常
设置中断线程标志:调用线程对象的thread.interrupt()方法,不会立即中断线程,只是在线程的中断线程标志打上标记
判断线程是否中断:
调用线程对象的thread.isInterrupted()方法,不会复位中断标记
调用线程类Thread.interrupted()静态方法,会复位中断标记为false
(4)暂停/恢复(已过时,不要使用)
thread.suspend(),thread.resume(),由于暂停后依旧占用着资源,容易死锁,所以标记为过期方法,使用wait(),notify()替代
4)线程间通信
(1)volatile、synchronized
通过同步关键字利用 共享变量 进行通信
(2)等待/通知机制(synchronized块中使用)
object为锁对象:object.wait(),object.notify(),object.notifyAll(),thread.join()
notify()和notifyAll()区别:
如果线程调用了对象的wait()方法,
那么线程便会处于该对象的等待池中,
等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),
被唤醒的的线程便会进入该对象的锁池中,
锁池中的线程会去竞争该对象锁。所以notify()使用不当时,会造成等待池中挂起了多个线程无法正常获取锁,
优先使用notifyAll()
thread.join()
当前线程等待thead线程执行完成后再返回该方法
https://blog.csdn.net/u010983881/article/details/80257703
原理:
join是Thread的synchronized方法,
锁对象是thread对象,
join方法里面调用了wait()方法,
在线程结束的时候会唤醒锁对象notify,
当前线程接受到了通知,继续执行调用thread.join()相当于当前主线程对thread对象加锁,
里面调用了wait()方法,
导致主线程阻塞,
直到thread线程结束时,
jvm会自动调用这个thread锁对象的notify唤醒当前主线程
1 | //伪代码实现生产者消费者 |
(3)其他方法
static sleep(); 睡眠不会释放锁资源,所以睡眠时,其它线程是无法得到锁的
static yield(); 当前线程做出让步,放弃当前cpu,让cpu重新选择线程,避免线程过度使用cpu
ThreadLocal(单线程中的全局变量)
java.lang.ThreadLocal
1)ThreadLocal、ThreadLocalMap、Thread 的关系
每个线程都有自己的 ThreadLocalMap ,key是 ThreadLocal 变量,value 是存储的值
1 | public class Thread implements Runnable { |
2)ThreadLocal源码
(1)基本属性
1 | public class ThreadLocal<T> { |
(2)set方法
设置值
1 | //set 操作每个线程都是串行的,不会有线程安全的问题 |
(3)get方法
获取值
1 | public T get() { |
(4)resize方法
- 扩容后数组大小是原来数组的 2 倍
- 扩容时是没有线程安全问题的
因为 ThreadLocalMap 是线程的一个属性,
一个线程同一时刻只能对 ThreadLocalMap 进行操作,
同一个线程执行业务逻辑必然是串行的,
那么操作 ThreadLocalMap 必然也是串行的
1 | //扩容 |
(5)remove方法
1 | public void remove() { |
3)ThreadLocalMap的key=ThreadLocal为什么使用弱引用包装?
弱引用对象:在没有强引用的时候,gc时会直接被回收
引用链关系:Thread -> ThreadLocalMap -> ThreadLocalMap.Entry[] -> referent(ThreadLocal)
某些场景下会导致内存泄漏,所以使用完一定要调用 remove 方法
假设一个场景:tomcat服务器 + 线程池处理请求
(1)ThreadLocal没有使用弱引用
使用一个ThreadLocal变量(作用域是单个请求),
当使用完没有使用remove,本次请求完成,
使用的线程没有销毁,被线程池回收,
那么上面的这个变量将永远不会被回收,
一直保留在这个线程的ThreadLocalMap里,
而且存储的value也一直保留,
造成内存泄露(2)ThreadLocal使用了弱引用
当本次请求完成时,
ThreadLocal变量除了弱引用外没有别的强引用,
gc时就会回收ThreadLocal变量,
这个线程的ThreadLocalMap就会出现key为null的Entry元素,
当别的请求进来再次使用这个线程,
再次ThreadLocal.set时,
里面会有逻辑清除key为null的Entry元素
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
虚假唤醒
线程有可能再没有 notify 的情况下被唤醒
所以在有条件判断去 wait 的场景使用 while 而不是 if
1 | synchronized(lock){ |

赞赏是不耍流氓的鼓励