线程安全

目录

线程安全(Thread Safe)

线程不安全的原因

1.开发者角度

2.系统角度

系统角度分析出现线程不全的原因——内存的可见性问题(memory visible)

系统角度分析出现线程不全的原因——代码重排导致的问题

锁(lock)

正确使用synchronized的作用

1.保证了临界区的原子性

2.在有限程度上可以保证内存可见性

3.可以给代码重排增加一定的约束

线程状态——阻塞状态


线程安全(Thread Safe)

不安全:单看代码“没问题”的情况下,但结果是错误的

安全:代码的运行结果应该是100%符合预期

线程不安全的原因

1.开发者角度

a.多个线程之间操作同一块数据(共享数据)——不仅仅是内存数据

b.至少有一个线程在修改这块共享数据

在多线程的代码中,哪些情况下不需要考虑线程安全问题?

①几个线程之间互相没有任何数据共享的情况下,天生是线程安全的;

②几个线程之间即使有共享数据,但都是做读操作,没有写操作时,也是天生线程安全的。

2.系统角度

前提知识:

①Java代码中的一条语句,很可能对应多条指令

②线程调度是可能发生在任意时刻的,但是不会切割指令

本质是没有保证原子性(actomic),所以才出错。

原子性被破坏是线程不安全的最常见的原因

为什么执行次数越多,出错率越大?

次数越多,线程执行需要卡时间片的概率越大(碰到线程调度的概率越大),导致中间出错的概率越大。

系统角度分析出现线程不全的原因——内存的可见性问题(memory visible)

CPU中为了提升数据获取速度,以=一般在CPU中设置缓存(Cache)

指令的执行速度>>内存的读写速度

 JVM规定了JVM内存模型

主存储/主内存:真实内存。

工作存储/工作内存:CPU中缓存的模拟

线程的所有数据操作(读写)必须:

1.从主内存加载到工作内中

2.在工作内存中进行处理,允许在工作内存中处理很久

3.完成最终的处理之后,再 把数据同步回主内存

内存可见性:一个线程对数据的操作,很可能其他线程是无法感知的,甚至,某些情况下,会被优化成完全看不到的结果

系统角度分析出现线程不全的原因——代码重排导致的问题

所谓的重排序,就是指:执行的指令不和书写指令并不一致。

JVM规定了一些重排序的基本原则:happend-before规则

解释:JVM要求,无论怎么优化,对于单线程的视角,结果不应该有改变。但并没有规定多线程环境的情况下(并不是不想规定,而是不能规定),导致在多线程环境下可能出问题


ArrayList ,LinkedList, PriorityQueue,TreeMap,TreeSet,HashMap,HashSet,StringBilder都不是线程安全。

线程安全的: Vector,Stack,Dictionary ,StringBuffer.

为什么不是线程安全(以ArrayList为例)——多个线程同时对一个ArrayList对象有修改操作时,结果会出错。

锁(lock)

synchronized锁:同步锁/monitor锁

语法:

1.修饰方法(普通,静态方法)->同步方法

synchronized int  add(……){}

修饰普通方法视为对“当前对象”加锁

修饰静态方法视为所在的类加锁

2.同步代码块

synchronized(引用){}


类名.class

是一个引用,指向关于这个类对象,不是这个类实例化出来的对象,而是这个类数据表现出的对象,每个被加载进来的类,都可以通过 类名.class访问到。

每个被加载的类有且仅有一个Class对象。


锁理论上,就是一段数据(一段被多个线程之间共享的数据)。   实现了代码的互斥

尝试加锁的内部操作:

1.整个尝试加锁的操作已经被JVM保证了原子性


当多个线程:1.都有加锁操作时2.并且申请的是同一把锁时

会造成,加锁,代码s(临界区代码),解锁


和join()相比锁更灵活

为什么会互斥?

互斥的必要条件:线程都有加锁操作&&同一把锁,锁的是同一个对象

public class SomeClass {
    synchronized void m1() { }
    synchronized static void m2() { }
    void m3() { }
    void m4() {
        synchronized (this) { }
    }
    void m5() {
        synchronized (SomeClass.class) { }
    }
    Object o1 = new Object();
    void m6() {
        synchronized (o1) { }
    }
    static Object o2 = new Object();
    void m7() {
        synchronized (o2) { }
    }
}

 SomeClass s1=new SomeClass();

 SomeClass s2=new SomeClass();

 SomeClass s3=s1;

synchronized(ref){}当ref==null的时候,一定会有NullPointerException

正确使用synchronized的作用

1.保证了临界区的原子性

 加锁的“粒度”不同,会影响性能,但最好值是一个需要工程测量的取值,

2.在有限程度上可以保证内存可见性

加锁:加锁成功之前清空当前线程的工作内存

解锁:解锁前保证把工作内存中的数据全部同步回主内存。

3.可以给代码重排增加一定的约束

举个例子


 用法:

Lock lock=new ReentrantLock();

lock.lock();

try{

//临界代码

}finally{

lock.unlock();

}

 该方法的结束以两种形式出现:

1,正常结束,并返回

2.异常结束,抛出异常。

可能导致该方法结束的情况:

1.超时时间内(这里的5s),加锁成功了正常返回,返回true。

2.超时时间到了(5s),加锁失败,正常返回,返回false。

3.超市时间内(5s),加锁还没成功但是线程被终止了,异常返回,捕获到InterrptedExceprion.

这个方法结束的情况:

1.不限时间等待过程中,加锁成功了,等待过程可能是永远。

2.在请求锁过程中,由于线程被终止了导致方法结束,以抛出InterrptedExceprion形式体现

interrupt():让线程停止,没有释放锁。


 juc下的锁的优点

1.书写灵活

可以一个方法加锁,到另一个方法中解锁

2.锁的类型更灵活

3.锁的加锁策略更灵活

a.一直请求锁

b.带中断

c.尝试请求

d.带超时的尝试

缺点:

容易忘记写lock.unlock()导致锁一直不释放。但这也是synchronized锁的优点(一定会带有锁的释放)

线程状态——阻塞状态

blocked,waiting,timed_waiting

blocked:专指请求synchronized锁失败时的状态

直接上状态转移图

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值