线程专题

线程专题笔记

##线程的状态转换

这是一个线程状态图

线程有

  • ready to run
  • Running
  • Dead
  • sleeping
  • waiting
  • Blocked

    JAVA 核心卷版本

  • New(新创建)
  • Runnable(可运行)
  • Blocked(被阻塞)
  • Waiting(等待)
  • Timed waiting(计时等待)
  • Terminated(被终止)

这 6 个状态

另一种说法的5个状态

  • 初始状态

    1
    实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态
  • 可运行状态

    1
    2
    3
    4
    5
    1.可运行状态只是说你资格运行,调度程序没有挑选到你,你就永远是可运行状态。
    2.调用线程的start()方法,此线程进入可运行状态。
    3.当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入可运行状态。
    4.当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入可运行状态。
    5.锁池里的线程拿到对象锁后,进入可运行状态。
  • 运行状态

    1
    线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
  • 死亡状态

    1
    2
    3
    1. 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它死去。
    这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。
    2. 在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
  • 阻塞状态

    1
    2
    3
    1.当前线程T调用Thread.sleep()方法,当前线程进入阻塞状态。
    2.运行在当前线程里的其它线程t2调用join()方法,当前线程进入阻塞状态。
    3.等待用户输入的时候,当前线程进入阻塞状态。


首先 从start 开始

  1. 一个正常的线程周期为
    start --> ready to run --> run --> Dead

  2. 如果不幸遇到 sleep 那么会在sleep时间结束后 进入run状态
    start --> read to run --> run --> sleep --> run-->Dead

  3. 如果不幸遇到wait 那么需要在被notify后 再进入 run状态
    start --> ready to run -->run --> wait -->(Object.notify() or object.notifyall()) --> run --> dead

  4. 如果不幸遇到 blocked for io (类似读取数据库,做文件读取工作) OR enter a synchronize code (其他线程进入同步代码块了)
    那么 会在 锁被自己获取到 或者 io中数据读取到了 的时候 再进入running状态
    start --> ready to run -->run --> blocked -->(received date or get the lock) --> run --> dead

锁专题

synchronized 和 lock的区别

  • synchronized是一个Java的关键字,Lock是一个接口;
  • synchronized代码块执行完或线程抛出异常时结束线程,Lock必须显示调用释放锁的方法:unlock();
  • synchronized修饰的锁其他线程在等待获取锁的阶段,会一直阻塞等待直到得到锁为止(不可中断锁);Lock有多种方式可以获取锁,不一定一直阻塞等待(可中断锁)。
  • synchronized无法判断锁的状态,Lock可以判断;
  • synchronized是非公平锁,而Lock可以设置为公平锁;

Join操作

join 让父线程等待子线程结束之后才能继续运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  public class ThreadJoinStudy {

public void a(Thread joinThread){
System.out.println("function a run begin");
try {
joinThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("function a run end");
}

public void b(){
System.out.println("function b run begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("function b run end");
}

public static void main(String[] args) {
ThreadJoinStudy threadJoinStudy = new ThreadJoinStudy();

Thread thread = new Thread(()->{
threadJoinStudy.b();
});

new Thread(()->{
threadJoinStudy.a(thread);
}).start();

thread.start();
}
}

输出结果为

function a run begin
function b run begin
function b run end
function a run end

StampedLock

在老的JDK中 一有 ReentrantReadWriteLock
read read 不互斥
read write 互斥
write write 互斥

这种是悲观锁

在ReentrantReadWriteLock 使用中 由于大多为读操作 会导致写线程处于饥饿状态(即一直抢占不到CPU)
为了解决这种问题
在 StampedLock中
read操作 并不会排斥 write 操作
会这样 如果 read 和 write 操作冲突 ,那么会 read 和 write 一起进行,等write操作执行完成后,再次执行一把read操作

任何对象都可以作为锁,锁信息会记录在对象的 对象头中,
一般来说 所有的对象 都会有一个 两个字节长度的对象头

Synchronied
每个对象头中都有一个 mark word

在JDK 1.6之前 synchronized 就是一个重量级锁
在 JDK1.6 后 我们引入了几种锁概念

  1. 偏向锁
  2. 轻量级锁
  3. 重量级锁

详解偏向锁

当我们一个线程进入后
Mark word 会记录

  • 线程ID
  • 锁的标志位

  • hash: 保存对象的哈希码

  • age: 保存对象的分代年龄
  • biased_lock: 偏向锁标识位
  • lock: 锁状态标识位
  • JavaThread:* 保存持有偏向锁的线程ID
  • epoch: 保存偏向时间戳
    偏向锁会执行一个策略 等到有线程出现来竞争锁的时候 才释放偏向锁
    很多时候 不是由多个线程来竞争锁 而是由一个单个线程来竞争锁
    但是 锁的释放和获取是会浪费资源的

偏向锁

轻量级锁

  1. 可以同时让多个线程进入我们的同步代码块中

线程池

maximumPoolSize 线程池的最大大小,线程池允许创建的最大的线程数量。如果队列满了,并且已创建的线程数量小于maximumPoolSize的线程数量。 那么会再创建新的线程执行任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
AtomicInteger count = new AtomicInteger();
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,20,10,TimeUnit.DAYS,new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.CallerRunsPolicy());

for (int i = 0; i < 100; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
count.getAndIncrement();
}
});
}
executor.shutdown();
System.out.println(count.get());
}

创建线程的多种方式

  • 继承Thread类
  • 实现runnable接口
  • 匿名内部类的方式
  • 带返回值的线程
  • 定时器
  • 线程池的实现
  • lambda表达式
  • spring 的 async

    死锁的四个条件

    1.互斥
    2.占有且等待
    3.不可抢占
    4.循环等待