Java基础——多线程之等待唤醒机制和状态

1.线程间的等待唤醒机制

Object 类中
 void wait ()  在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
       
 void wait (long timeout) 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。
 
 void notify () 唤醒在此对象监视器上等待的单个线程,随机唤醒。
        
 void notifyAll ()  唤醒在此对象监视器上等待的所有线程。 
    
notify()需要在同步方法或同步块中调用,即在调用前,线程必须获得该对象的对象级别锁
在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait方法。

wait()和sleep()的区别
Wait和Sleep的区别:
1.它们最大本质的区别是,Sleep()不释放同步锁,Wait()释放同步锁。
2.还有用法的上的不同是:Sleep(milliseconds)可以用时间指定来使他自动醒过来,
如果时间不到你只能调用Interreput()来强行打断;Wait()可以用Notify()直接唤起。
4.这两个方法来自不同的类分别是Thread和Object
5.最主要是Sleep方法没有释放锁,而Wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

2.内存可见性问题 volatile

volatile 解决内存可见性问题
 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的。
	 Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,
	 线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。
	 线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。
	 不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
	 
Java中的可见性:
	 对于可见性,Java提供了volatile关键字来保证可见性。
	 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
	 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,
	 当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
	 另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,
	 并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
	 
volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。
 	           相较于 synchronized 是一种较为轻量级的同步策略。
volatile 变量,用来确保将变量的更新操作通知到其他线程。
可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:
对于多线程,不是一种互斥关系 
不能保证变量状态的“原子性操作”              

3.CAS 算法

 CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器
操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。
CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:
需要读写的内存值 V 内存
进行比较的值 A 预估值
拟写入的新值 B 
当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。
 jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。

java.util.concurrent.atomic 包下提供了一些原子操作的常用类:
AtomicBoolean 、 AtomicInteger 、 AtomicLong 、 AtomicReference
AtomicIntegerArray 、 AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray 

4.线程的状态

新建:线程被创建出来
就绪:具有CPU的执行资格,但是不具有CPU的执行权
运行:具有CPU的执行资格,也具有CPU的执行权
阻塞:不具有CPU的执行资格,也不具有CPU的执行权
死亡:不具有CPU的执行资格,也不具有CPU的执行权
线程的状态图
Java中线程的状态分为6种:

  1. 初始(New):

新创建了一个线程对象,但还没有调用start()方法。

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

就绪状态的线程在获得CPU时间片后变为运行中状态(running)。这也是线程进入运行状态的唯一的一种方式。

  1. 阻塞(Blocked):

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

  1. 等待(Waiting) 跟 超时等待(Timed_Waiting):
  1. 处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒(通知或中断),否则会处于无限期等待的状态。
  2. 处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
  1. 终止(Terminated):

当线程正常运行结束或者被异常中断后就会被终止。线程一旦终止了,就不能复生。

注意:

  1. 调用 obj.wait 的线程需要先获取 objmonitorwait会释放 objmonitor 并进入等待态。所以 wait()/notify() 都要与 synchronized 联用。
  2. 其实线程从阻塞/等待状态 到 可运行状态都涉及到同步队列等待队列的

阻塞与等待的区别
阻塞

当一个线程试图获取对象锁(非JUC库中的锁,即synchronized),而该锁被其他线程持有,则该线程进入阻塞状态。它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己,不响应中断。

等待

当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语义更丰富,可响应中断。例如调用:Object.wait()、**Thread.join()**以及等待 LockCondition

虽然 synchronizedJUC 里的 Lock 都实现锁的功能,但线程进入的状态是不一样的。synchronized 会让线程进入阻塞态,而 JUC 里的 Lock是用park()/unpark() 来实现阻塞/唤醒 的,会让线程进入等待状态。虽然等锁时进入的状态不一样,但被唤醒后又都进入Runnable状态,从行为效果来看又是一样的。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Geek Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值