java并发临界资源管理

所谓的并发,一般是指基于多处理器硬件环境的,允许系统在同一时刻执行多件不同的任务逻辑,在单处理器硬件环境下,一般是按照时间片轮转的调度方式,实现宏观意义上的并发,而事实上,在同一个时间点上,仍然只有一件任务在运行,我习惯把这种并发看成“伪并发”,以下所讲的并发临界资源管理,是基于多CPU硬件环境的。即在同一时刻,正在运行的不同CPU可能会访问一些共用的资源,而临界资源管理需要做的就是保证,这些资源的读写操作不能陷入死锁以及,各线程获得的资源都是最新的。 

在提到并发之前,我得提到一个基础问题,那就是,程序在运行时,为了加快系统的运算速度,临时数据一般是放在CPU缓存(Cache)中的,而不同的线程占有一段不同的Cache,所以这里就涉及到一个问题,即对共享资源而言,如何保证所有线程读写的同步。这就是并发所要解决的临界资源管理问题的来由。java并发解决这个问题都是基于以下原理:保证每个线程对共享资源的写操作都是独占式的,且将发生改变的共享资源写回到主存(memory)中,基于缓存一致性规则,系统所有Cache中的相同共享资源值将全部过期失效,其他线程的读操作被迫从主存中去取值了。(硬件实现,就不细说了) 

 一. Java多线程的实现 

Java中从语言上实现多线程是通过实例化Thread类,调用实例start()方法,执行其run()方法所定义的任务来实现的。 得到我们自己的Thread实例,一般有两种方式,一是让自己的任务类继承Thread父类,并覆写其run方法,最后实例化这个任务类,调用start()方法,启动线程。

 另一种方法是任务类继承Runnable接口,并实现其run()方法,通过Thread myThread = new Thread(myRunnable),传入该Runnable实例作为构造器参数,从而获得Thread实例,调用start()方法,启动线程。这种方式较为常用。

 以上提到的两种方式,都需要显示获得Thread类的实例,并针对每单个Thread进行操作与管理。但在所需构建的线程较多时,这种方式便显得较为繁琐,因为对系统资源的占用与释放问题都交给了我们的设计者,所以Java中也提供了类似线程池的管理方式来管理这些线程。这些管理工具在java.util.concurrent包中。通过ExecutorService实例,进一步封装了Thread管理细节,通过调用其execute()方法,执行线程,并通过shutdown()方式,释放掉其中所有的线程所占用的资源。其通用代码如下:(推荐) 

 ExecutorService es = Executors.newCachedThreadPool();//获取线程池 

 for(int i=0;i<5;i++){ 

       es.execute(new Accessor(i)); //执行线程 

 }

 TimeUnit.SECONDS.sleep(5); 

 es.shutdown(); //释放所有资源 

其获取线程池的方式有三种,分别是 

Executors.newCachedThreadPool():创建的线程池大小等于实际线程数 Executors.newFixedThreadPool(int num):创建固定大小的线程池 Executors.newSingleThreadExecutor():固定线程池大小为1 

 二. 临界资源管理 

这部分涉及到几个常用的关键词,一个是synchronized,一个是volatile,一个是Atomatic*一系列原子类,以及ThreadLocal(线程本地存储)。 

 1. volatile 

这个关键字用来修饰变量,其功能和影响只需要记住一句话,它保证的是,被其所修饰的变量值,在每次发生改变之后,必定即时将改动后的值刷新写入主存中。 

其局限性也在于此,他只能保证变量的同步,而不是功能性的同步。例如对被volatile修饰的变量i,执行操作i++;这个操作并不线程安全,因为这个操作不是原子性的,它可以拆分成两步,一步是读i值,第二步是做加法;volatile只能保证第一步读到的值一定是当时最新的,但在第二步之前,该线程可能被暂时挂起,进而去执行其他的线程,如果其他线程在此时修改了i的值,那么第二步算出来的值就不是那么合理了。所以功能性的同步,就是synchronized这个关键字的事情了。 

 2. synchronized 

这是Java并发保证同步最常用到的一个关键字。利用该关键字来保证同步是通过加锁机制实现的,也可以说是一种隐式加锁方式,之所以这么说,是因为你可以使用java.util.concurrent.locks类库中提供的Lock类显示地对代码块进行加锁以实现线程同步。

 Java中的每一个对象都可以作为锁,这里的锁是针对加锁和解锁两种操作来说的,即同一时刻至多只有一个线程能够访问作为锁的对象。根据synchronized的用途:synchronized可以用于修饰普通方法、静态方法以及代码块,锁的表现形式有以下三种: 

对于普通同步方法,锁是当前实例对象;对于静态同步方法,锁是当前类的Class对象;对于同步方法块,锁是synchronized括号里参数指明的对象。 

关于synchronized需要说明的有两点: 

(1)对于某个特定对象来说,其所有synchronized方法共享同一个锁,即当一个类中同时包含多个Synchronized修饰的成员方法时,该类的一个实例对象,同一时刻只能访问其中一个成员方法,对同步代码块,这一规则同样成立。 

(2)synchronized关键字不属于方法特征签名的组成部分,所以可以在覆盖方法的时候加上去。 

 3. 原子类 

需要首先说明的是,原子操作是指不能被线程调度机制中断的操作。原子性可以用于除了long和double之外的所有基本类型之上的简单操作,即对于读取和写入除long和double之外的基本类型变量的值的操作,是原子操作。但是由于long和double为64bit数据,而JVM对64位的读取和写入是当做两个分离的32位操作来执行,这就可能产生一个在读取和写入操作中间发生上下文切换的隐患,导致不正确结果的可能性,这也被称为字撕裂。所以Java SE5引入了诸如AtomicInteger、AtomicLong以及AtomicReference等特殊的原子性变量,并提供了其对应的读写方法,从而保证其读写操作的原子性。具体参考手册,其类库为java.util.concurrent.atomic。 

 4. ThreadLocal 

引用自《Java编程编程思想》——“防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。因此,如果你有5个线程都要使用变量X所表示的对象,那么线程本地存储会生成5个用于X的不同的存储块。”,而创建和管理线程本地存储可以用java.lang.ThreadLocal类来实现。 

 其提供的示例代码如下: 

 /** 

 * Created by Song on 2016/10/15. 

 */

 public class ThreadLocalVariableHolder {

      private static ThreadLocal<Integer> values = new ThreadLocal<Integer>(){ 

        private Random rand = new Random(47); 

       @Override 

       protected synchronized Integer initialValue() { 

              return rand.nextInt(10000); 

        } 

 }; 

 public static void increment(){ 

 values.set(values.get()+1); 

 } 

 public static int get(){return values.get();} 

 public static void main(String [] args) throws InterruptedException{ 

        ExecutorService es = Executors.newSingleThreadExecutor(); 

        for(int i=0;i<5;i++){ 

              es.execute(new Accessor(i)); 

        } 

        TimeUnit.SECONDS.sleep(5); 

        es.shutdown(); 

    } 

}

 class Accessor implements Runnable{ 

      private final int id; 

      public Accessor(int id){this.id=id;} 

      public void run() { 

          while (!Thread.currentThread().isInterrupted()){                     ThreadLocalVariableHolder.increment(); 

System.out.println(this); Thread.yield(); 

    } 

 } 

 public String toString(){ 

       return "#"+id+": "+ThreadLocalVariableHolder.get();

   }

 } 

转载于:https://juejin.im/post/5cedfda1f265da1ba84a729e

猜你喜欢

转载自blog.csdn.net/weixin_34399060/article/details/91438973