实现并发安全要注意哪些

并发安全

一、类的线程安全定义  

Doug Lee:如果多线程下使用这个类,不过多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的。

类的线程安全表现为:

  1. 操作的原子性
  2. 内存的可见性

不做正确的同步,在多个线程之间共享状态的时候,就会出现线程不安全。

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

1.栈封闭

所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态。

2.无状态

没有任何成员变量的类,就叫无状态的类

public int service(int a,int b) {
		return a*b;
	}

3.让类不可变

让状态不可变,两种方式:(JDK中,String,所有基本类型包装类)

1、加final关键字,对于一个类,所有的成员变量应该是私有的,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的

2、不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值

第一种

public class ImmutableFinalRef {
	
	private final int a;
	private final int b;
	private final User user;//这里,就不能保证线程安全啦
	
	public ImmutableFinalRef(int a, int b) {
		super();
		this.a = a;
		this.b = b;
		this.user = new User(2);
	}

	public int getA() {
		return a;
	}

	public int getB() {
		return b;
	}
	
	public User getUser() {
		return user;
	}

	public static class User{
		private final int age;//这里也要加final,才是安全的

		public User(int age) {
			super();
			this.age = age;
		}

		public int getAge() {
			return age;
		}
		
	}
	
	public static void main(String[] args) {
		ImmutableFinalRef ref = new ImmutableFinalRef(12,23);
		User u = ref.getUser();
		//u.setAge(35);
	}
}

第二种

public class ImmutetableToo {
	private List<Integer> list =  new ArrayList<>(3);
	
	public ImmutetableToo() {
		list.add(1);
		list.add(2);
		list.add(3);
	}
	
	public boolean isContains(int i) {
		return list.contains(i);
	}

}

4.volatile

保证类的可见性,最适合一个线程写,多个线程读的情景

5.加锁和CAS

请看第三章CAS

6.安全的发布

类中持有的成员变量,特别是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。

public class UnsafePublish {
	//要么用线程的容器替换
	//要么发布出去的时候,提供副本,深度拷贝
	private List<Integer> list =  new ArrayList<>(3);
	
	public UnsafePublish() {
		list.add(1);
		list.add(2);
		list.add(3);
	}
	
	//讲list不安全的发布出去了
	public List<Integer> getList() {
		return list;
	}

	//也是安全的,加了锁--------------------------------
	public synchronized int getList(int index) {
		return list.get(index);
	}
	
	public synchronized void set(int index,int val) {
		list.set(index,val);
	}	
	
}

7.TheadLocal

8.Servlet

不是线程安全的类:周期:请求的时候创建,返回时销毁

1、在需求上,很少有共享的需求。

2、接收到了请求,返回应答的时候,都是由一个线程来负责的。

9.死锁(关于死锁和活锁我单独写了一篇 死锁和活锁

是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

死锁的根本成因:获取锁的顺序不一致导致。

10.动态的

动态顺序死锁,在实现时按照某种顺序加锁了,但是因为外部调用的问题,导致无法保证加锁顺序而产生的。

解决:

  1. 通过内在排序,保证加锁的顺序性
  2. 通过尝试拿锁,也可以。

11.活锁

尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生拿锁,释放锁的过程。

解决办法:每个线程休眠随机数,错开拿锁的时间。

12.线程饥饿

低优先级的线程,总是拿不到执行时间。

三、性能和思考

使用并发的目标是为了提高性能,引入多线程后,引入额外的开销。衡量应用的程序性能:服务时间,延迟时间,吞吐量,可伸缩性,服务时间,延迟时间(多快),吞吐量(处理能力的指标,完成工作的多少),多快和多少,完全独立,甚至是相互矛盾。、

对服务器应用来说:多少(可伸缩性,吞吐量)这个方面比多快更受重视。

1、先保证程序正确性,确实达不到要求的时候,再提高速度。(黄金原则)

2、一定要以测试为基础。

一个应用程序,串行的部分是永远都有的。

Amdahl定律

四、影响性能的因素

上下文切换

5000~10000个时钟周期,几微秒

内存同步

加锁时增加额外的指令

阻塞

挂起,包括两次额外的上下文切换

五、减少锁的竞争

缩小锁的范围

对锁的持有,快进快出,尽量缩短持有锁的时间(只锁需要锁的代码)

减少锁的粒度

使用锁的时候,锁所保护的对象是多个,多个对象其实是独立变化的时候,不如用多个锁来保护不同的对象。但是要注意死锁

锁分段

ConcurrentHashMap(第五章)

替换独占锁 

1、使用读写锁

2、自旋CAS

3、使用系统的并发容器

六、线程安全的单例模式

懒汉式-双重检查

public class SingleDcl {
	private int id;
	private UserAccount userAccount;// 域可能没有赋值到对象中
    private volatile static SingleDcl singleDcl;// volatile可以解决线程安全
    private SingleDcl(){
    }

    public static SingleDcl getInstance(){
    	if(singleDcl==null) {
    		synchronized (SingleDcl.class) {//类锁
				if(singleDcl==null) {// 防止第一个判断后,其他线程实例化
					singleDcl = new SingleDcl();
				}
			}
    	}
        return singleDcl;
    }
}

懒汉式-类初始化模式

类初始化模式,也叫延迟占位模式

public class SingleInit {
    private SingleInit(){}

    //定义一个私有类,来持有当前类的实例
    private static class InstanceHolder{
        public static SingleInit instance = new SingleInit();
    }

    public static SingleInit getInstance(){
        return InstanceHolder.instance;
    }

}

饿汉式

在jvm中,对类的加载和类的初始化,由虚拟机保证线程安全

public class SingleEHan {
    public static SingleEHan singleEHan = new SingleEHan();
    private SingleEHan(){}
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值