Java线程学习(一)

线程生命周期

stateDiagram-v2
    direction LR

    s1 : 就绪状态
    s2 : 运行状态
    s3 : 阻塞状态
    s4 : 锁池(lockpool)

    classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow

    [*] --> s1::: badBadEvent : 调用start方法
    s1 --> s2::: badBadEvent : JVM的调度
    s2 --> s1 : JVM的调度(yield方法)
    s2 --> s3::: badBadEvent : 遇到阻塞事件(sleep/join方法)
    s2 --> s4 : synchronized
    s4 --> s1 : 
    s3 --> s1 : 阻塞解除
    s2 --> [*] : run方法结束

初始是新建状态:刚新建出来的线程对象

就绪状态:就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权利(CPU时间片就是CPU执行权),当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着线程进入运行状态

运行状态:run方法的开始执行标志着这个线程进入运行状态,当之前占着的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU时间片,当再次抢到CPU时间片之后,会重新进入run方法接着上一次的代码继续往下执行

阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片。阻塞状态解除时,之前的CPU时间片没了,需要再次回到就绪状态,抢夺CPU时间片

锁池(lockpool)锁池不是一种状态,便于理解的话可以当成一种阻塞状态。线程进入锁池找共享对象的对象锁的时候,会释放之前占有的CPU时间片,有可能找到了,也有可能没找到,没找到则在锁池中等待,如果找到了会进入就绪状态继续抢夺CPU时间片

结束是死亡状态:run方法结束


synchronized三种写法

  1. 第一种:同步代码块

    1
    2
    3
    synchronized (线程共享对象) {
    // 线程同步代码块
    }
  2. 第二种:在实例方法上使用synchronized
    表示共享的对象是调用该实例方法(不包括静态方法)的对象
    并且同步代码块是整个方法体

  3. 第三种:在静态方法上使用synchronized
    表示找类锁
    因为静态方法是不属于当前实例的,而是属于类的,因此锁的是类
    类锁永远只有一把
    就算创建了100个对象,那类锁也只有一把


开发中如何解决线程安全?

  • 尽量使用局部变量代替实例变量和静态变量
  • 如果必须使用实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了
  • 如果不能使用局部变量,也不能创建多个对象,此时考虑使用synchronized