四、类的线程安全

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);
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值