多线程——线程安全相关问题

类的线程安全定义

如果多线程下使用这个类,不管多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的。
类的线程安全表现为:
操作的原子性
内存的可见性
不做正确的同步,在多个线程之间共享状态的时候,就会出现线程不安全。

怎么才能做到类的线程安全?

栈封闭
所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态。
无状态
没有任何成员变量的类,就叫无状态的类
让类不可变

让状态不可变 两种方式:
1,加final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。
2、根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值
volatile
保证类的可见性,最适合一个线程写,多个线程读的情景,
加锁和CAS
安全的发布
类中持有的成员变量,特别是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。

关于Servlet
不是线程安全的类,为什么我们平时没感觉到,:2、在需求上,很少有共享的需求,第二,接收到了请求,返回应答的时候,都是由一个线程来负责的。

死锁问题

资源一定是多于1个,同时小于等于竞争的线程数,资源只有一个,只会产生激烈的竞争。
死锁的根本成因:获取锁的顺序不一致导致。
在这里插入图片描述
通过以上命令可以监听死锁,

动态顺序死锁,在实现时按照某种顺序加锁了,但是因为外部调用的问题,导致无法保证加锁顺序而产生的。
解决:
1、通过内在排序,保证加锁的顺序性
通过尝试拿锁,也可以。

 public void tranferMoney2(Account fromAccount, Account toAccount, long amount) 

如上面的Account对象调用时顺序错了,可通过 System.identityHashCode()获取两个账户的原始哈希值(取原始哈希值是为了防止HashCode被篡改)进行比较排序,当原始哈希值相同时,可以再加一个额外的锁进行争夺,争夺到再进行加锁(此时顺序已无关紧要,因为每次只有一个线程可进入)
在这里插入图片描述
当然以上紧针对没有的更好的排序方式时提出的方案,实际上大小比较,字母顺序比较都可以

活锁

调用tryLock方法时,尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生拿锁,释放锁的过程。
举个栗子:线程A获取到锁1,调用tryLock方式获取锁2,与此同时,线程B获取到锁2,同样tryLock锁1,此时线程A拿不到锁1,发生谦让让出锁1,但B线程同样发生谦让,让出锁2。不断重复这个过程
解决办法:每个线程休眠随机数,错开拿锁的时间。

if(from.getLock().tryLock()) {...}
SleepTools.ms(r.nextInt(10));

线程饥饿

饥饿:如果一个线程因为 CPU 时间全部被其他线程抢走而得不到 CPU 运行时间,这种状态被称之为“饥饿”;

高优先级线程吞噬所有的低优先级线程的 CPU 时间。
线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
线程在等待一个本身(在其上调用 wait())也处于永久等待完成的对象,因为其他线程总是被持续地获得唤醒

性能优化

1、上下文切换,线程数不要超过cpu线程数一定范围,否则性能降低
2、内存同步(volatile关键字)
3、减少锁的竞争
4、缩小锁的范围(只锁核心代码,其他尽量摘出syc关键字外面,快进快出,缩短持有锁时间)
5、减少锁的粒度(如果需要锁多个对象,且对象是独立变化无关联时,宁愿加多个锁 )
6、锁分段
7、替换独占锁

猜你喜欢

转载自blog.csdn.net/qq_41700030/article/details/103815107
今日推荐