类的线程安全
1.怎么才能做到类的线程安全?
1.1 栈封闭
所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态。
public class Safe{
public int service(){
int a = 10;
int b = ....;
}
}
1.2 无状态
没有任何成员变量的类,就叫无状态的类
1.3 让类不可变
让状态不可变,两种方式:
- 1、加final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。
public class Immutable {
private final int a = 0;
private final int b = 0;
}
- 2、根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值
public class Immutable2 {
private List<Integer> list = new ArrayList<>();
public Immutable2() {
list.add(1);
list.add(2);
list.add(3);
}
public boolean isContains(int i) {
return list.contains(i);
}
}
1.4 volatile
保证类的可见性,最适合一个线程写,多个线程读的情景
1.5 加锁和CAS
1.6 安全的发布
类中持有的成员变量,特别是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。
public class UnsafePublish {
private List<Integer> list = new ArrayList<>();
//不安全的发布
public List<Integer> getList(){
return list;
}
}
1.7 ThreadLocal
2 死锁
2.1 静态死锁
synchronized(A)
synchronized(B)
synchronized(B)
synchronized(A)
2.2 动态死锁
银行转账,需要同时锁定from、to两个账户:
/**
* 不安全的转账动作
*
* @author Rab
* @date 2020-01-03 09:46
*
*/
public class TransferAccount implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount)
throws InterruptedException {
synchronized (from){//先锁转出
System.out.println(Thread.currentThread().getName()
+" get"+from.getName());
Thread.sleep(100);
synchronized (to){//再锁转入
System.out.println(Thread.currentThread().getName()
+" get"+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
看似没有问题
TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi", "zhangsan", lisi, 2000, transfer);
TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan", lisi, zhangsan, 4000, transfer);
这样就造成了死锁
解决方法一:
public class SafeTransferAccount2 implements ITransfer {
private Object tieLock = new Object();
@Override
public void transfer(UserAccount from, UserAccount to, int amount) throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
// 锁住Hash小的那一个
if (fromHash < toHash) {
synchronized (from) {
System.out.println(Thread.currentThread().getName() + " get" + from.getName());
Thread.sleep(100);
synchronized (to) {
System.out.println(Thread.currentThread().getName() + " get" + to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
} else if (toHash < fromHash) {
synchronized (to) {
System.out.println(Thread.currentThread().getName() + " get" + from.getName());
Thread.sleep(100);
synchronized (from) {
System.out.println(Thread.currentThread().getName() + " get" + to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
} else { // Hash冲突
synchronized (tieLock) {
synchronized (from) {
System.out.println(Thread.currentThread().getName() + " get" + from.getName());
Thread.sleep(100);
synchronized (to) {
System.out.println(Thread.currentThread().getName() + " get" + to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
}
}
解决办法二:
public class SafeTransferAccount implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amount) throws InterruptedException {
while(true) {
if(from.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " get " + from.getName());
Thread.sleep(100);
if(to.getLock().tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " get " + to.getName());
from.flyMoney(amount);
to.addMoney(amount);
break;
} finally {
to.getLock().unlock();
}
}
} finally {
from.getLock().unlock();
}
}
}
}
}
注意:上述代码有可能发生活锁现象
尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生拿锁,释放锁的过程。
解决办法:每个线程休眠随机数,错开拿锁时间。
分析:A:zhangsan拿到,拿不到lisi,释放zhangsan
B:lisi拿到,拿不到zhangsan,释放lisi
如此反复
2.3 线程饥饿
低优先级的线程总是拿不到执行时间
3.锁性能思考
3.1 缩小锁的范围
public synchronized boolean isMatch(String name, String regexp){
String key = "user." + name;
String job = matchMap.get(key);
if(job == null){
return false;
}
// 耗时操作
return Pattern.matches(regexp, job);
}
优化后:
public boolean isMatch(String name, String regexp){
String key = "user." + name;
synchronized(this){
String job = matchMap.get(key);
}
if(job == null){
return false;
}
// 耗时操作
return Pattern.matches(regexp, job);
}
3.2 减少锁的粒度
使用锁的时候,锁所保护的对象是多个,多个对象其实是独立变化的,不如用多个锁来一一保护这些对象,但是要注意避免死锁
public class FinessLock{
public final Set<String> users = new HashSet<String>();
public final Set<String> queries = new HashSet<String>();
public synchronized void removeUser(String u){
user.remove(u);
}
public synchronized void removeQuery(String q){
queries.remove(q);
}
}
优化后:
public class FinessLock{
public final Set<String> users = new HashSet<String>();
public final Set<String> queries = new HashSet<String>();
public void removeUser(String u){
synchronized(users){
user.remove(u);
}
}
public synchronized void removeQuery(String q){
synchronized(queries){
queries.remove(q);
}
}
}