Home Tags Posts tagged with "notify()"

notify()

引言

今年七月的实习太划水了,每天下班后也没有充电,全去打守望先锋了,仔细想想这才是最关键的一个月,应该奋斗而不是享乐,挑战周五看完这本Guide哥推荐的 Java并发实现原理!!!

这本书初看,并不喜欢,但当看完第一章后,我改变了想法。任何一本书都不可能知识体系全面,我们也正因此去不断学习。

阅读学习,更应该看重其知识广度,因为对于现阶段的我来说,还没有追求深度的能力,所以,加油吧!!!

 

多线程基础(个人笔记)

1.1 线程的优雅关闭

1.1.1 stop()和destory()函数

何谓线程?即 一段运行中的代码

若 QQ 是一个进程,我们与好友的聊天框作为 QQ 进程下的一个线程,有一天,我们可能和女朋友聊着聊着就吵起来了,这个时候心里一肚子火气,我们不想通过正常的点击 关闭 按钮关闭对话框,而想强制杀死线程,那么问题来了

我们能否将运行到一般的线程强制杀死?

答:不能。在Java中,有stop()destory()函数是用于强制杀死线程的,但官方明确不建议使用,因为,如果强制杀死线程,则线程中所使用的资源,例如文件描述符、网络连接等不能正常关闭。

因此,一个线程一旦运行起来,就不要去强行打断它,合理的关闭办法是让其运行完(也就是函数执行完毕),干净地释放掉所有资源,然后退出。如果是一个不断循环运行的线程,就需要用到线程间的通信机制,让主线程通知其退出。


1.1.2 守护线程

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务等待处理某些发生的事件

默认开启的线程都是非守护线程,可通过 t1.setDaemon(true) 将 非守护线程转为 守护线程Java 中有一个规定:当所有的非守护线程退出后,整个 JVM 进程就会退出。意思就是 守护线程 不算作“数”,守护线程不影响整个 JVM 进程的退出,例如垃圾回收线程。


1.1.3 设置关闭的标志位

通过设置标志位跳出循环,避免阻塞(死循环)。

通过标志位来实现,有个问题,即如果程序没有机会再执行while(!stopped) 代码即无法再执行到标志位判断,则一直无法退出循环。

此时就需要使用 InterruptedException()与interrupt()函数。


1.2 InterruptedException()和interrupt ()函数

1.2.1 什么情况下会抛出 Interrupted 异常

首先注意:Interrupt 不是说一个线程运行到一半,把它中断了,然后抛出 InterruptExcetion 异常

如果没有声明会抛出 InterruptedExcetion ,则 t.interrupt()  不会抛出异常,

只有那些声明了会抛出 InterruptedException 的函数才会抛出异常也就是下面这些常用的函数:

先声明抛出 InterruptedException,Interrupt() 函数才有效!!!


1.2.2 轻量级阻塞和重量级阻塞

不仅锁分为轻量级锁、重量级锁,阻塞也分为 轻量级阻塞重量级阻塞

轻量级阻塞:能被中断的阻塞称为 轻量级阻塞,对应的线程状态为 WAITING 或者 TIMED_WAITING;

重量级阻塞:像 Synchronized 这种不能被中断的阻塞称之为 重量级阻塞,对应的状态是BLOCKED。

 

线程的状态迁移过程

无阻塞函数:READY <–>RUNNING

阻塞函数:WAITING <–>TIMED_WAITING

如果使用了 Synchronized 关键字或块 :进入 BLOCKED 

除了常用的阻塞/唤醒函数,还有一对不太常见的阻塞/唤醒函数,LockSupport.park()/unpark()。这对函数非常关键,Concurrent包中Lock的实现即依赖这一对操作原语。故而t.interrupted()的精确含义是“唤醒轻量级阻塞”,而不是字面意思“中断一个线程”。


1.2.3 t.isInterrupted()与Thread.interrupted()的区别

这两个函数都是线程用来判断自己是否收到过中断信号前者是非静态函数,后者是静态函数。二者的区别在于前者只是读取中断状态不修改状态后者不仅读取中断状态,还会重置中断标志位


1.3 synchronized关键字

1.3.1 锁的对象是什么(即锁住的到底是什么)

误解:它通常加在所有的静态成员函数和非静态成员函数的前面,表面看好像是“函数之间的互斥”

实质:给某个对象加了把锁

等价于对于非静态成员函数其实是加在对象 a 上面的;对于静态成员函数加在 A.class面的;当然,class 本身也是对象。

补充:一个静态成员函数和一个非静态成员函数,都加了 synchronized 关键字,分别被两个线程调用,它们是否互斥?很显然,因为是两把不同的锁,所以不会互斥


1.3.2 锁的本质是什么

多个线程要访问同一个资源。线程是一段段运行的代码;资源就是一个变量,一个对象,或者一个文件等;而就是要实现 线程 资源的访问控制,保证同一时间只能有一个线程去访问某个资源。

实现对资源的访问控制不仅有锁,还有信号量:如果同一时间允许多个线程访问同一资源,那么锁就变成信号量从程序角度看,本身就是一个对象,它维护三个信息

1.自己(该锁)是否被某个线程占用(0没有,1被占用)

2.占用自己(该锁)的线程ID

3.其他阻塞的、等待拿自己(该锁)的线程列表

锁是一个对象,要访问的共享资源也是一个对象,合二为一,成为一个对象,使得在 Java 里面,synchronized 关键字可以加在任何对象的成员上面,这意味着,这个对象既是共享资源同时也具备 “锁”的功能


1.3.3 synchronized 实现原理

答案在Java的对象头里在对象头里,有一块数据叫Mark Word在64位机器上,Mark Word是8字节(64位)的,这64位中有2个重要字段:锁标志位占用该锁的thread ID


1.4 wait()与notify()

1.4.1 生产者、消费者模型满足该模型的条件:

(1)内存队列本身要加锁,才能实现线程安全。

(2)阻塞。当内存队列满了,生产者放不进去时,会被阻塞;当内存队列是空的时候,消费者无事可做,会被阻塞。

(3)双向通知。消费者被阻塞之后,生产者放入新数据,要notify()消费者;反之,生产者被阻塞之后,消费者消费了数据,要notify()生产者。

1.   如何阻塞、如何通知?

办法1:线程自己阻塞自己,也就是生产者、消费者线程各自调用wait()和notify()。

办法2:用一个阻塞队列,当取不到或者放不进去数据的时候,入队/出队函数本身就是阻塞的。0

2.如何双向通知?

办法1:wait()与notify()机制。

办法2:Condition机制。


1.4.2 为什么wait() 和 notify()必须和 synchronized 一起使用?

注:wait() 和 notify()是 Object 的成员函数,是基础中的基础,而非作为像 Thread 一类的成员函数

两个线程之间要通信,对于同一个对象来说,一个线程调用该对象的wait(),另一个线程调用该对象的notify(),该对象本身就需要同步!

所以,在调用wait()、notify()之前,要先通过 synchronized 关键字同步给对象,也就是给该对象加锁。前面已经讲了,synchronized 关键字可以加在任何对象的成员函数上面任何对象都可能成为锁。那么,wait()和notify()要同样如此普及,也只能放在 Object 里面


1.4.3 为什么 wait()的时候必须释放锁?

为了避免死锁!

线程 A 进入 synchronized(obj 1)中之后,也就是对 obj 1 上了锁此时,调用 wait()进入阻塞状态,则一直不能退出 synchronized 代码块;那么线程 B 永远也无法进入 synchronized(obj 1)同步块里永远没有机会调用 notify(),则造成死锁


1.4.4 wait()与notify()的问题 

生产者本来只想通知消费者,但它把其他的生产者也通知了;消费者本来只想通知生产者,但它被其他的消费者通知了。原因就是 wait() notify()所作用的对象和 synchronized 所作用的对象是同一个,只能有一个对象,无法区分队列空和列队满两个条件这正是Condition要解决的问题。


1.5  volatile关键字

1.5.1 64位写入的原子性(Half Write)

对于一个long型变量的赋值和取值操作而言,在多线程场景下,线程 A 调用set(100),线程B 调用 get(),在某些场景下,返回值可能不是100.

因为 JVM 的规范并没有要求 64位的 long 或者 double 的写入是原子的;在32位的机器上,一个64位变量的写入可能被拆分成两个32位的写操作来执行,如此一来,读取的线程可能读到“一半的值”,解决办法为:在 long 前面加上 volatile 关键字

即,long 或 double的写入操作不一定原子性,多线程下可能产生读到写入一半的值解决办法加上 volatile 关键字(可见性、禁止重排序)


1.5.2 内存可见性

多线程下,我们可能会遇到“最终一致性”和“强一致性”分别对应的情况,举个例子:

线程 A 负责标志位的修改,Flag 标志位类型为 Boolean 类型

线程 B 负责读取 Flag 标志位的值

线程 A 将 Flag 设置为 true 的时候,线程 B 读到的可能还是 false,可是过一段时间之后,线程 B 再读到的却是 true 了这就是满足“最终一致性”,而不满足“强一致性”

强一致性当线程 A 将 Flag 设置为 true 的时候,线程 B 读到的必须也立即是 true