目录
线程状态——阻塞状态(blocked、waiting、timed_waiting)
线程安全
1.现象:
开发者角度:有多个线程&&只有一个线程在修改数据
系统角度:原子性、内存可见性、代码重排序 ——线程调度
1.线程不安全示例:
代码每次运行的结果都不相同,并且与预期结果不符合
COUNT越大,线程执行需要跨时间片的概率越大,导致中间出错的概率越大
原子性被破坏是线程不安全的常见的原因
public class phenomenon {
//定义一个共享属性 ——静态属性的方式来体现
static int r =0;
//定义加减的次数
//COUNT越大,出错的概率越大
static final int COUNT = 1000000;
//定义两个线程,分别对r进行加减法操作
static class Add extends Thread{
@Override
public void run() {
for(int i =0 ;i< COUNT; i++){
r++;
}
}
}
static class Sub extends Thread{
@Override
public void run() {
for(int i =0;i<COUNT; i++){
r--;
}
}
}
public static void main(String[] args) throws InterruptedException {
Add add = new Add();
add.start();
Sub sub = new Sub();
sub.start();
add.join();
sub.join();
//理论上,被加减了count次,结果应该是0
System.out.println(r);
}
}
2.线程不安全的原因
1.开发者角度
(1)多个线程之间操作同一块数据了(数据共享)——不仅仅是内存数据
(2)至少有一个线程在修改这块共享数据
即使在多线程的代码中,那些情况不需要考虑线程安全问题?
a.线程之间互相没有任何数据共享的情况下,天生线程是安全的
b.线程之间即使有共享数据,但是都做读操作,没有写操作,也是天生线程安全的
2.系统角度解释
(1)java代码中的一条语句,很肯对应多条指令
(2)线程调度可能发生在任意时刻,但是不会切割指令(一条指令只有执行完/完全没有执
2.保护线程安全的措施——锁lock
理论上是一段数据(一段被多个线程之间互相共享的数据)
static boolean lock = false;
所以一共两种状态{锁上(locked)、打开(unlocked)}
false:unlocked true:locked
当多个线程都有加锁操作、并且申请的是同一把锁时,会造成 加锁 代码s(临界区) 解锁
临界区代码会互斥着进行
1.synchronized锁
sync(引用 ->指向的是被当成锁的对象)这里请求锁{...这是临界区(被锁保护起来的代码)..}这里释放锁
sync修饰方法:
a.sync修饰普通方法
视为对“当前对象”加锁 sync(this){...}
sync void methord(){} 等价于 void methord(){ sync(this){} }
b.修饰静态方法
视为对静态方法所在的类加锁 sync(类.class){...}
static sync void methord(){} 等价于static void methord(){ sync(类.class){} }
请求的结果:
a.请求成功(该锁,没有线程持有)
继续花括号内代码的执行
b.请求失败(该锁,已经被线程持有)
请求锁的线程会被释放,直到锁被释放,重新去请求锁
//JVM针对每个对象都实现了“锁”的功能
一旦正确加锁:多个县城都尝试加锁&&请求的是同一个把锁,会出现互斥现象
互斥:某一时刻,只有一个线程(请求到锁的线程)在临界区运行指令,其他线程(请求锁失败的线程)在等待
满足互斥的条件:线程都有加锁操作&&同一把锁 锁的是同一个对象
2.判断是否互斥:
1.理论基础
前提条件:
t1线程 s1 = new SomeClass();
t2线程 s2 = new SomeClass();
s3 =s1;
结果:
需要注意的是:
同步静态方法就视为对SomeClass.class加锁
SomeClass.class与SomeClass.class.o2不是同一对象
s1与s1.o1也不是同一对象
2.代码实践
门门 、丢丢互斥,
门门、聪聪不互斥, 毛毛、丢丢不互斥
门门、毛毛在同一线程,丢丢、聪聪在统一线程,不存在互斥问题
3.synchronied的解引用操作
synchronized(ref){...},当ref == null 的时候,结果如何?
隐含着一个解引用(dereference)的操作(通过引用操作引用指向的对象)
如果null,一定会出现空指针异常
很多时候,异常看起来一堆,只是调用栈比较长
这两段代码的区别是啥?
加锁“粒度”不同,左边的加锁粒度更细,右边的加锁粒度更粗
粒度不是越粗越好,也不是越细越好,最好是一个需要工程测量的取值
加锁粒度越细,并发性越高
新学了一个copy class
4.synchronied加锁的作用:
1.原子性(90%):通过将需要保证原子性的操作互斥起来
2.可见性(5%)
3.重排序(5%)
5.synchronized 锁 VS juc下的锁
sync 代码的书写就保证一定有锁的释放
只有一种类型的锁
一直请求锁
juc 是可能忘记写lock.unlock(),导致锁一直不释放
书写更灵活,可以在一个方法加锁,另一个方法中解锁
锁的类型更灵活
公平锁/非公平锁 读写锁/独占锁 可重入锁/不可重入锁
锁的加锁策略更加灵活:1.一直请求锁 2.带中断 3.尝试请求4.带超时的请求
线程状态——阻塞状态(blocked、waiting、timed_waiting)
blocked:专指请求synchrnoized锁失败的状态
waiting VS timed_waiting
不带时间 带时间
lock.lock()、lock.lockInterruptly()、t.join() Thread.sleep(...)、t.join(50000)、 lock.tryLock(5s)