什么是线程安全性,如何保证线程安全*

前言

并发编程最大的难点并不在于如何使用,而在于如何保证我们程序的线程安全.,如果我们能保证并发安全的话,那么我们可以大胆的在程序里面使用多线程.

在《Java并发编程实战》中,定义如下:
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在调用代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

线程封闭

实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发。避免并发最简单的方法就是线程封闭。什么是线程封闭呢?

就是把对象封装到一个线程里,只有这一个线程能看到此对象(别的线程无法看到这个对象)。那么这个对象就算不是线程安全的也不会出现任何安全问题。实现线程封闭有哪些方法呢?

ad-hoc线程封闭(避免使用)

Ad-hoc 线程封闭描述了线程封闭的方式,由开发人员或从事该项目的开发人员确保仅在单个线程内使用此对象(线程安全做的好不好完全看开发人员的编码水平)。 这种方式方法可用性不高,在大多数情况下应该避免。
Ad-hoc 线程封闭下的一个特例适用于 volatile 变量。 只要确保 volatile 变量仅从单个线程写入,就可以安全地对共享 volatile 变量执读 - 改 - 写操作。在这种情况下,您将修改限制在单个线程以防止竞争条件,并且 volatile 变量的可见性保证确保其他线程看到最新值。

栈封闭(局部变量)

栈封闭是我们编程当中遇到的最多的线程封闭。什么是栈封闭呢?简单的说就是局部变量(方法中的变量)。多个线程访问一个方法,此方法中的局部变量都会被拷贝一份到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。

ThreadLocal(线程隔离)

无状态的类

类的状态就是指类的成员变量

没有任何成员变量的类,就叫无状态的类,这种类一定是线程安全的。如果这个类的方法参数中使用了对象,也是线程安全的吗?比如:
在这里插入图片描述

当然也是,为何?因为多线程下的使用,固然user这个对象的实例会不正常,但是对于StatelessClass这个类的对象实例来说,它并不持有UserVo的对象实例,它自己并不会有问题,如果有线程安全问题的是UserVo这个类,而非StatelessClass本身。

让类不可变

让状态不可变,两种方式:

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

2、根本就不提供任何可供修改成员变量的地方(不提供get方法),同时成员变量也不作为方法的返回值。这样的话别人就改不动内部的成员变量

但是要注意,一旦类的成员变量中有对象,上述的final关键字保证不可变并不能保证类的安全性,为何?

因为在多线程下,虽然对象的引用不可变,但是对象在堆上的实例是有可能被多个线程同时修改的,没有正确处理的情况下,对象实例在堆中的数据是不可预知的。这就牵涉到了如何安全的发布对象这个问题。

在这里插入图片描述
如果有getUser() 方法把对象暴露出去就不线程安全了.如果在getUser()方法上加个synchronized就是线程安全的了.

如果class类上加final 只是代表这个类不能继承.

volatile

并不能保证类的线程安全性,只能保证类的可见性,在某种特殊的情况下,它也能保证写的原子性 ,volatile最适合一个线程写,多个线程读的情景。

加锁和CAS

我们最常使用的保证线程安全的手段,使用synchronized关键字,使用显式锁,使用各种原子变量,修改数据时使用CAS机制等等。

安全的发布

类中持有的成员变量,如果是基本类型,发布出去,并没有关系,因为发布出去的其实是这个变量的一个副本,
但是如果类中持有的成员变量是对象的引用,如果这个成员对象不是线程安全的,通过get等方法发布出去,会造成这个成员对象本身持有的数据在多线程下不正确的修改,从而造成整个类线程不安全的问题。 可以看见,
在这里插入图片描述
这个list发布出去后,是可以被外部线程之间修改(把对象的引用复制了一份给外面的list),那么在多个线程同时修改的情况下不安全问题是肯定存在的,怎么修正这个问题呢?

我们在发布这对象出去的时候,就应该用线程安全的方式包装这个对象。 我们将list用Collections.synchronizedList进行包装以后,无论多少线程使用这个list,就都是线程安全的了。
在这里插入图片描述
对于我们自己使用或者声明的类,JDK自然没有提供这种包装类的办法,但是我们可以仿造这种模式或者委托给线程安全的类,当然,对这种通过get等方法发布出去的对象,最根本的解决办法还是应该在实现上就考虑到线程安全问题,

TheadLocal

ThreadLocal是实现线程封闭的最好方法。ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,而Map的值就是我们要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。
是线程级别隔离的局表变量,即使是static变量,对于不同的线程也是不共享的,每个线程都互相独立的,互相看不见,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

当前请求结束,ThreadLocal里面的东西将消失,所以说ThreadLocal的声明周期是线程结束内容就消失.因为ThreadLocal的key就是当前线程

主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
这个详细内容我以后会专门有个文章去讲解.

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值