Java线程状态

状态描述
初始状态(New)线程对象被创建后就进入了新建状态
就绪状态(Runnable)线程对象调用start方法进入可运行线程池中,此时就处于就绪状态中。
运行状态(Running)当前线程获得CPU时间片,开始执行。注意:线程只能由就绪状态进入运行状态。
终止状态(Dead)线程完成所有工作,正常退出,或者因异常退出。
阻塞状态(Bloacked)线程因为某种原因放弃CPU使用权,暂时停止运行。

阻塞状态可细分为下面三种状态:

阻塞描述
等待阻塞运行的线程执行wait()方法,进入等待阻塞,该阻塞态必须由其他线程调用notify()或者notifyAll方法才能被唤醒,进入锁池(同步锁定线程池)。
同步阻塞运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,则该线程被放入同步锁定线程池中,即处于同步阻塞状态。当线程池中的线程获得锁后,会转到就绪状态。
其他阻塞(定时阻塞、join等待阻塞、I/O阻塞)通过调用线程的sleep()方法或join()或发出I/O请求时,线程进入阻塞状态,当sleep()方法超时,join()等待线程终止或者I/O处理完毕后,线程会重新回到就绪状态。

Java线程状态.png

Java线程竞争与合作

Java内存模型

Java中多个线程可以同时访问一个对象,由于每个线程都有这个变量的拷贝(虽然对象以及成员变量分配的内存在主内存中,但是为了加速程序进行,每个执行的线程均会有一份拷贝,放在本地内存中,这是现代多核处理器的特性),所以程序在执行中,一个线程看到的变量不一定是最新的。
Java内存模型.png

锁机制/同步机制

volatile与CAS

volatile的一般特性:
关键字volatile可以修饰字段,告知程序任何对该变量的访问都需要从主内存中获取,而对它的任何改变都必须同步刷新到主内存,。最终实现了线程对volatile变量访问的可见性
volatile的读-写内存语义:
当写一个volatile变量时,JMM会把该线程本地内存中的所有共享变量值刷新到主内存中。
当读一个volatile变量时,JMM会把该线程本地内存中的所有共享变量值置为无效,线程接下来从主内存中读取共享变量。

volatile的读、写内存语义是JSR-133后增强的,通过严格限制编译器和处理器对volatile变量和普通变量的重排序,确保volatile的写、读和锁(synchronized)的释放、获取具有相同的内存语义。

Java中的concurrent包就是利用到了CAS的原子性和volatile的可见性读、写内存语义(禁止volatile读写前后重排序),将它们组合在一起,CAS更新volatile变量不仅具有原子性,而且会根据上下文,自动选择volatile的读、写内存语义。在获取一个锁时,CAS更新volatile变量会使用volatile内存读语义,即把该线程本地内存中的所有共享变量值置为无效,线程接下来从主内存中读取共享变量;在释放锁时,CAS更新volatile变量会使用volatile内存写语义,即把该线程本地内存中的所有共享变量值刷新到主内存中。

注意:volatile并不能保证修饰的字段一定是线程安全的,它的作用是使得被修饰的变量的具体某一次读和写是对所有线程是可见的,需要满足一定条件下,才能保证线程安全:

  • 对当前变量的写操作不依赖于当前值。反例如:i= i++;这其实是两个原子操作,一个读一个写,共计两次操作,因此该操作不是线程安全的
  • 该变量与其他变量没有相互依赖,状态是独立的。反例如: i= a + 1; 这也是两个原子操作,一个读一个写,共计两次操作,因此该操作不是线程安全的。

概括起来就是,如果对该变量的操作是原子性的,同时使用volatile的使其具有线程可见性,最终使得线程安全。

synchronized

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一时刻,只能有一个线程处于同步方法、同步块中,它保证了线程对变量访问的可见性和排他性,同时保证了线程安全。
关键字synchronized的原理:任意一个对象都有一个自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到这个对象的监视器才能进入同步块或者同步方法,而没有获取到监视器的线程就会阻塞在同步块或同步方法的入口处。
锁(synchronized)的读-写内存语义锁的释放具有volatile变量的内存写语义,具有锁的线程释放锁时,会将监视器中该线程本地的共享变量刷新到主内存中;锁的获取具有对volatile变量的内存读语义,当线程获得锁的时候,会将监视器中本地内存共享变量置为无效,重新从主内存读取共享变量。

volatile和synchronized区别

volatile和synchronized原理
观察上图可得:synchronized的底层原理和volatile差不多,只不过它同步的是监视器中的所有变量。然后通过lock和unlock来对这个监视器所有变量进行操作。所以synchronized本质就是一个JVM实现的、隐式的、可自动释放的锁,其本身实现的底层原理有用到和volatile相同的部分。

volatile和synchronized的主要区别:volatile仅仅是保证对单个变量的具体某一次读/写是对所有线程可见的,并不能保证线程安全;synchronized则不仅可以使多个变量的多个操作的集合为原子性的,同时还可以使该集合操作结果对所有线程可见。由此保证线程安全。

Java编程规范定义了一些原子操作,例如char、int等基本类型的赋值和引用操作都是原子性的,另外,对象等引用类型的赋值和引用操作也是原子性的。

synchronized和Java中的显式锁

使用synchronized关键字将隐式地获取锁,它将锁的获取和释放都简化了,这种方法虽然简化了同步的管理,但是其拓展性没有显式地获取锁和释放锁好。由此,Java中还有一种显式锁通过实现并发包的Lock接口来完成锁功能,其本质上利用到了volatile和CAS原子操作而不是使用synchronized。

等待/通知机制

通过wait()、notify()、notifyAll()等方法实现不同线程的相互交互、协作完成任务。
经典范式:

// 等待方/消费者的伪代码
synchronized(对象){
    while(条件不满足){
        对象.wait();
    }
}

// 通知方/生产者的伪代码
synchronized(对象){
    改变条件;
    对象.notifyAll();
}
最后修改:2024 年 11 月 23 日
如果觉得我的文章对你有用,请随意赞赏