1.
线程的状态
NEW:
安排了工作
,
还未开始行动
RUNNABLE:
可工作的
.
又可以分成正在工作中和即将开始工作
.
BLOCKED:
这几个都表示排队等着其他事情
WAITING:
这几个都表示排队等着其他事情
TIMED_WAITING:
这几个都表示排队等着其他事情
TERMINATED:
工作完成了
.
2
线程状态和状态转移的意义
还是我们之前的例子:
刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是
NEW
状态;
当李四、王五开始去窗口排队,等待服务,就进入到
RUNNABLE
状态。
该状态并不表示已经被银行工
作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度
;
当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入
BLOCKED
、
WATING
、
TIMED_WAITING
状态,至于这些状态的细分,我们以后再详解;
如果李四、王五已经忙完,为
TERMINATED
状态。
所以,之前我们学过的
isAlive()
方法,可以认为是处于不是
NEW
和
TERMINATED
的状态都是活着
的。
3
观察线程的状态和转移
观察
1:
关注
NEW
、
RUNNABLE
、
TERMINATED
状态的转换
使用
isAlive
方法判定线程的存活状态
.
关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
在这里运行代码我们是看不出来什么的,通过使用 jconsole 可以看到 thread1 的状态是 TIMED_WAITING ,thread2的状态是BLOCKED.
修改上面的代码, 把 t1 中的 sleep 换成 wait
使用 jconsole 可以看到 thread1 的状态是 WAITING
结论
:
BLOCKED
表示等待获取锁
, WAITING
和
TIMED_WAITING
表示等待其他线程发来通知
.
TIMED_WAITING
线程在等待唤醒,但设置了时限
; WAITING
线程在无限等待唤醒
3: yield()
大公无私,让出
CPU
可以看到
:
1.
不使用
yield
的时候
,
张三李四大概五五开
2.
使用
yield
时
,
张三的数量远远少于李四
结论
:
yield
不改变线程的状态
,
但是会重新去排队
4.
多线程带来的的风险
-
线程安全
(
重点
)
1 线程安全的概念
想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线
程安全的。
2 解决线程不安全问题
可以利用synchronized的特性:
1)
互斥
synchronized
会起到互斥效果
,
某个线程执行到某个对象的
synchronized
中时
,
其他线程如果也执行到 同一个对象 synchronized
就会
阻塞等待
.
进入
synchronized
修饰的代码块
,
相当于
加锁
退出
synchronized
修饰的代码块,相当于 解锁
理解
"
阻塞等待
".
针对每一把锁
,
操作系统内部都维护了一个等待队列
.
当这个锁被某个线程占有的时候
,
其他线程尝
试进行加锁
,
就加不上了
,
就会阻塞等待
,
一直等到之前的线程解锁之后
,
由操作系统唤醒一个新的
线程
,
再来获取到这个锁
.
注意
:
上一个线程解锁之后
,
下一个线程并不是立即就能获取到锁
.
而是要靠操作系统来
"
唤醒
".
这
也就是操作系统线程调度的一部分工作
.
假设有
A B C
三个线程
,
线程
A
先获取到锁
,
然后
B
尝试获取锁
,
然后
C
再尝试获取锁
,
此时
B
和
C
都在阻塞队列中排队等待
.
但是当
A
释放锁之后
,
虽然
B
比
C
先来的
,
但是
B
不一定就能
获取到锁
,
而是和
C
重新竞争
,
并不遵守先来后到的规则
.
synchronized的底层是使用操作系统的mutex lock实现的.
2)
刷新内存
synchronized
的工作过程
:
1.
获得互斥锁
2.
从主内存拷贝变量的最新副本到工作的内存
3.
执行代码
4.
将更改后的共享变量的值刷新到主内存
5.
释放互斥锁
所以
synchronized
也能保证内存可见性
.
具体代码参见
volatile 的源码。
3)
可重入
synchronized
同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;
理解
"
把自己锁死
"
一个线程没有释放锁
,
然后又尝试再次加锁
.
理解
"
把自己锁死
"
一个线程没有释放锁
,
然后又尝试再次加锁
这样的锁称为
不可重入锁。
代码示例
在下面的代码中
,
increase
和
increase2
两个方法都加了
synchronized,
此处的
synchronized
都是针对
this
当前
对象加锁的
.
在调用
increase2
的时候
,
先加了一次锁
,
执行到
increase
的时候
,
又加了一次锁
. (
上个锁还没释
放
,
相当于连续加两次锁
)
这个代码是完全没问题的
.
因为
synchronized
是可重入锁
.
在可重入锁的内部
,
包含了
"
线程持有者
"
和
"
计数器
"
两个信息
.
如果某个线程加锁的时候
,
发现锁已经被人占用
,
但是恰好占用的正是自己
,
那么仍然可以继续获取
到锁
,
并让计数器自增
.
解锁的时候计数器递减为
0
的时候
,
才真正释放锁
. (
才能被别的线程获取到)