java之线程那些事儿(二)

      编写线程安全的代码,核心就在于对程序状态访问操作进行管理,特别是对共享(shared)和可变的(mutable)状态的访问;
      一般来说,对象的状态是指对象域中的实时数据(如实例或者静态域中的数据),数据发生了改变,我们就说对象的状态发生了改变,就如今天的你不同于昨日的你;
  注意,对象的状态可能还包括其以来对象的域;比如,我么你所熟知的容器(HashMap,ArrayList),容器的状态不仅仅存储在其本身,还包括存储在其中的对象中包含
  的任何可能影响其外部可见性的数据;
      共享--同时会有多个线程对其进行访问,可变--变量的值在其生命周期内会发生变化,单独来看,没有问题,可二者结合起来,就给线程安全造成了严重的破坏性,
  所以讨论线程安全性,重点在于如何防止在数据上发生不受控制的并发访问,也就是如何保证所有人都能正确的访问和操作数据;
      另外,一个对象是否需要线程安全性,取决于它是否会被多个线程访问,需要注意的是我们所用的一些框架在调用应用程序代码的时候,将并发性引入到了程序中,
  因此对线程安全的需求将在程序中蔓延开来;
      一些程序刚开始应用时,访问量小,程序可以正常的执行,但是一旦访问增加时,就是我们所说的高并发访问,程序就可能出现意想不到的错误,而这个时间可能是
  几个月,甚至是几年,运气好的话可能永远不会出现(你的程序可能没人用了吧)~
      当出现并发访问可变的状态变量时,如果没有特殊处理,从前面的内容我们可以通过3种方式来修复:
      1.取消该状态的共享;
      2.将该变量设置为不可变;
      3.访问时同步;
      显然,为了适应业务需求,1和2我们无法实现,所以我们只能采用方法3;我们必须采用同步机制来协同这些线程对变量的访问与操作,这里就需要提到synchronized
  关键字,它提供了一种独特的加锁方式,但是“同步”还这是术语还包括volatile(可见),explicit lock(显式锁)以及atomic(原子性)类型的变量(至于这三种的
  区别,我们稍后再细细道来);
      如果在设计类的时候,并没有考虑并发的情况(我相信大部分新手都是如此),那么采用上述方法可能需要对程序进行重大修改,因为java并没有强制要求将状态都
  封装在类中,所以要找出该变量在哪些位置出现了并发访问,尤其是一些大型工程,更是复杂,修复起来是知易行难,所以从一开始就设计一个线程安全的类会好的多,这
  就要求我们充分发挥java的封装性(封装越好,越安全);当然有时候一些抽象和封装会降低程序的性能,但是与让代码正确的运行起来这个目的相比,性能就只能先靠边
  了;所以并发编程的守则就是:先让代码正确的运行,然后再提高性能,而且只有当性能测试结果和需求要求你必须提高性能以及某些优化缺失能提高性能时,才进行主
  动优化;
      那么什么是线程安全性,简单来说,它是一个在代码上应用的术语,只与状态相关,对象封装这些状态的整个代码,可以是单个对象,也可以是对象集合,甚至是整个
  程序;因此我们可以以java的基本单元类为标准来阐述安全性:
      当多线程访问某个类时,这个类的行为与其规范(通过不变形条件来约束状态--invariant,通过后验条件来描述操作结果--postcondition)始终一致,那么就称
  这个类是线程安全的,这个访问操作就具有线程安全性;而且只有讨论类的状态时吗,安全性才有意义,无状态的对象一定是线程安全的;
      具体的来说,就是当当问一个可变状态时,如果对其进行的操作不是原子性操作,则会由于如指令重排序等原因,造成指令的执行顺序出现多样化,结果每个线程执行
  的状态同一时刻处于不同操作,结果将导致严重的数据完整性问题,这种现象被称为race condition--竞态条件--计算的正确性完全取决于多个线程的交替执行;而最常
  见的类型就是--先检查后执行,本质就是基于一种可能失效的结果来做出响应,结果当然可能不正确;另外还有一种就是数据竞争--data race,在访问非final的共享域
  时没有同步,就会出现数据竞争(读取由之前的另一个线程写入的变量);为了确保线程安全性,之前的哪些操作必须是原子性的,所以我们组合操作称之为符合操作;然
  后通过加锁来确保其线程安全性(在操作完成之前,阻止其他线程进行操作);
      java提供了一种内置锁(同步代码块--synchronized block),synchronized方法以Class对象为内置锁,同时只有一个线程可以获取该锁,当然同一个线程可以
  多次获取该锁,我们称之为“重入”,JVM会记录下锁得持有者和计数值,只有当计数值为0的时候才会释放;所以一定不要在执行时间长的计算或者无法快速完成的操作中加
  锁(比如I/O),这样会长时间阻塞程序;所以用锁需谨慎!
      

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章