JAVA-线程安全

1.线程安全的定义

         限定为多个线程之间存在共享数据访问。因为如果多个线程之间不存在共享数据的话,那么从线程安全的角度看,程序串行和多线程执行时完全没有区别的。

定义1:如果一个对象可以安全地被多个线程同时使用,那它就是线程安全的。

定义2:当多个线程访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调度方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的



2.线程安全的五个等级(由强到弱)

不可变:可以是基本类型的final;可以是final对象,但对象的行为不会对其状态产生任何影响,比如String的subString就是new一个String对象各种Number类型如BigInteger和BigDecimal等大数据类型都是不可变的,但是同为Number子类型的AtomicInteger和AtomicLong则并非不可变我觉得原因是它里面状态对象时unsafe对象,所做的操作都是CAS操作,可以保证原子性。

绝对线程安全:他是完全满足Brian Goetz给出的线程安全的定义,一个类要达到这种程度,需要付出很大的,甚至不切实际的代价。

相对线程安全:这就是我们通常意义上的线程安全。需要保证对象单独的操作时线程安全的。比如Vector,HashTable,synchronizedCollection包装集合等。

线程兼容:对象本身不是线程安全的,但可以通过同步手段实现。一般我们说的不是线程安全的,绝大多数是指这个。比如ArrayList,HashMap等。

线程对立:不管调用端是否采用了同步的措施,都无法在并发中使用的代码。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。


3.实现线程安全的方法

(1)互斥同步

       在多线程访问的时候,保证同一时间只有一条线程使用。而互斥是实现同步的一种手段,临界区(Critical Section),互斥量(Mutex),信号量(Semaphore)都是主要的互斥实现方式。java里最基本的互斥同步手段是synchronized,编译之后会形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象,还有个锁的计数器,来记录拥有锁的次数,跟AQS里面的state一样。
       其实在“Java与线程”里已经提到,java的线程是映射到操作系统的原生线程之上的,不管阻塞还是唤醒都需要操作系统的帮忙完成,都需要从用户态转换到核心态,这是很耗费时间的,是java语言中的一个重量级(Heavyweight)操作,虽然虚拟机本身会做一点优化的操作,比如通知操作系统阻塞之前会加一段自旋等待的过程,避免频繁切换到核心态。
        还可以使用java.util.concurrent包中的重入锁(ReentrantLock)来实现同步,ReentrantLock比synchronized增加了一些高级功能:等待可中断、可实现公平锁以及锁可以绑定多个条件。

(2)非阻塞同步(Non-Blocking Synchronization)

        互斥同步最主要的问题就是阻塞和唤醒所带来的性能问题,所以这通常叫阻塞同步(悲观的并发策略)。随着硬件指令集的发展,我们有另外的选择:基于冲突检测的乐观并发策略,通俗讲就是先操作,如果没有其他线程争用共享的数据,操作就成功,如果有,则进行其他的补偿(最常见就是不断的重试),这种乐观的并发策略许多实现都不需要把线程挂起,这种同步操作被称为非阻塞同步。

(3)无同步方案

        有一些代码天生就是线程安全的,不需要同步。其中有如下两类:

        可重入代码(Reentrant Code):纯代码,具有不依赖存储在堆上的数据和公用的系统资源,用到的状态量都由参数中传入,不调用非可重入的方法等特征,它的返回结果是可以预测的。
        线程本地存储(Thread Local Storage):把共享数据的可见范围限制在同一个线程之内,这样就无须同步也能保证线程之间不出现数据争用问题。可以通过java.lang.ThreadLocal类来实现线程本地存储的功能。

4.多线程共享变量问题探讨

(1)多个线程同时维护共享变量(加减操作),例如:买票问题:

        synchronized方法,synchronized代码块

        重入锁(ReentrantLock)

        推荐使用后者,lock,unlock,更加灵活,volatile不推荐使用,它只能保证可见性,不能保证原子性

(2)每个线程内部独立维护共享变量,隔离多个线程之间的共享冲突

       Map<Thread,Integer> threadData = new HashMap<Thread, Integer>();
       threadData.put(Thread.currentThread(), data); 

       通过HashMap来标志是同一线程时,取同一变量

        ThreadLocal来实现,ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突


5.什么情况下会发生死锁

      线程1锁定对象A,等待对象B,线程2锁定对象B,等待对象A,2个线程都在等待自己所需的资源,但是这些


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值