临界区相关操作

原子类

JAVA SE5引入了诸如AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类,他们提供下面形式的原子性条件更新操作:
boolean compareAndSet(expectedValue updateValue)
这些类被调整为可以使用在某些现代处理器上的可获得的,并且在机器级别上的原子性,因此在使用它们时,通常不需要担心。对于常规编程来说,他们很少会派上用场,但是在涉及性能调优时,他们就大有用武之地。例如,我们可以使用AtomicInteger来重写AtomicityTest.java

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest implements Runnable{

    //原子性整型
    private AtomicInteger i = new AtomicInteger(0);

    public int getValue() {
        return i.get();
    }

    private void evenIncrement() {
        i.addAndGet(2);
    }

    @Override
    public void run() {
        while (true) {
            evenIncrement();
        }
    }

    public static void main(String[] args) {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Aborting");
                System.exit(0);
            }
        },5000);
        ExecutorService exec = Executors.newCachedThreadPool();

        AtomicIntegerTest ait = new AtomicIntegerTest();

        exec.execute(ait);

        while (true) {
            int val = ait.getValue();

            if (val % 2 != 0) {
                System.out.println(val);
                System.exit(0);
            }
        }
    }
}

这里我们通过使用AtomicInteger而消除了synchronized关键字。因为这个程序不会失败,所以添加了一个Timer,以便在5秒钟之后自动地终止。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EvenChecker implements Runnable {
    private IntGenerator generator;

    private final int id;

    public EvenChecker(IntGenerator generator, int ident) {
        this.generator = generator;
        this.id = ident;
    }

    @Override
    public void run() {
     while (!generator.isCanceled()){
         int val = generator.next();
         if (val %2 != 0) {
             System.out.println(val + " not even!");
             generator.cancel();
         }
     }
    }

    public static void test(IntGenerator gp, int count) {
        System.out.println("Press Control-C to exit");
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < count; i++) {
            executorService.execute(new EvenChecker(gp,i));
        }
        executorService.shutdown();
    }

    public static void test(IntGenerator gp) {
        test(gp,10);
    }
}
public abstract class IntGenerator {

    private volatile boolean canceled = false;

    public abstract int next();

    public void cancel() {canceled = true;}
    public boolean isCanceled() {return canceled;}
}
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicEvenGenerator extends IntGenerator{

    private AtomicInteger currentEvenValue = new AtomicInteger(0);



    @Override
    public int next() {
        return currentEvenValue.addAndGet(2);
    }

    public static void main(String[] args) {
        EvenChecker.test(new AtomicEvenGenerator());
    }
}

所有其他形式的同步再次通过使用AtomicInterger得到了根除。
应该强调的是,Atomic类被设计用来构建java.util.concurrent中的类,因此只有在特殊情况下才在自己的代码中使用它们,即便使用了也需要确保不存在其他可能出现的问题。通常依赖于锁要更安全一些(要么是synchronized关键字,要么是显示的Lock对象)。

临界区

有时,你只希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法。通过这种方式分离出现的代码段被称为临界区,它也使用synchronized关键字建立。这里,synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制:
syncronized(syncObject){}
这也被称为同步控制块;在进行此段代码前,必须得到syncObject对象的锁,如果其他线程已经得到这个锁,那么就得等到锁被释放以后,才能进入临界区。
通过使用同步控制块,而不是对整个方法进行同步控制,可以使多个任务进行访问对象的时间性能得到显著提高,下面的例子比较了这两种同步控制方法。此外,它也演示了如何把一个非保护类型的类,在其他类的保护和控制之下,应用于多线程的环境:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**

  • Pair不是线程安全的,因为他的约束条件需要两个变量要维护成相同的值。
  • 自增加操作不是线程安全的,并且因为没有任何方法被标记为synchronized
  • 所以不能保证一个Pair对象在多线程程序中不会被破坏。
    */
class Pair {
    private int x,y;

    public Pair(int x,int y) {
        this.x = x;
        this.y = y;
    }

    public Pair(){
        this(0,0);
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void incrementX() {
        x++;
    }

    public void incrementY() {
        y++;
    }

    public String toString() {
        return "x: "+x+" y: "+y;
    }

    public class PairValueNotEqualException extends RuntimeException {

        public PairValueNotEqualException() {
            super("Pair value not equal: " + Pair.this);
        }
    }

    public void checkState() {
        if (x != y) {
            throw new PairValueNotEqualException();
        }
    }
}

/**
 * 某人交给你一个非线程安全的Pair类,而你需要再一个线程环境中使用它。
 * 创建这个类可以实现这一点,PariManager类持有一个Pair对象并且控制对他的一切访问,
 * 唯一的public方法就是getPair,它是synchronized的,对象抽象方法increment,对
 * increment的同步控制将在实现的时候进行处理。
 *
 * PairManager类的结构,它的一些功能在基类中实现,并且其一个或多个抽象方法在派生类中定义。
 * 这种结构在设计模式中称为模版方法。
 * 设计模式使你得以把变化封装在代码里;
 * 在此发生变化的部分是模版方法increment
 */
//在线程安全的类中保护Pair
abstract class PairManager {
    AtomicInteger checkCounter =  new AtomicInteger(0);

    protected Pair p = new Pair();

    同步list的实现原理就是使用同一个Object锁,对list里的每个方法包装在同步代码块中 线程安全list
    private List<Pair> storage = Collections.synchronizedList(new ArrayList<Pair>());

    public synchronized Pair getPair() {
        //复制一份,打开原来的保险箱
        return new Pair(p.getX(),p.getY());
    }

    /**
     * 将Pair对象添加了synchronized ArrayList 所以这个操作是线程安全的。
     * 因此,该方法不必进行防护。可以放置PairManager2的synchronized语句块的外部
     * @param p
     */
    protected void store(Pair p) {
        storage.add(p);
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {

        }
    }

    public abstract void increment();
}

class PairManager1 extends PairManager {
    //整个方法是被同步控制
    public synchronized  void increment() {
        p.incrementX();
        p.incrementY();
        store(getPair());
    }
}


class PairManager2 extends PairManager {

    @Override
    public void increment() {
        Pair temp;
        //同步控制块进行同步。synchronized关键字不属于方法特征签名的组成部分,所以可以在覆盖方法的时候加上去
        synchronized (this) {
            p.incrementY();
            p.incrementX();
            temp = getPair();
        }

        store(temp);
    }
}

/**
 * 创建用来测试两种不同的PairManager,其方法是在某个任务中调用increment
 * 而PairChecker则在另一个任务中执行,为了跟踪可以运行测试的频度,PairChecker在每次成功
 * 时都地址checkCounter。
 */
class PairManipulator implements Runnable{

    private PairManager pm;

    public PairManipulator(PairManager pm) {
        this.pm = pm;
    }
    @Override
    public void run() {
        while (true) {
            pm.increment();
        }
    }

    @Override
    public String toString() {
        return "Pair: " +pm.getPair() +" checkCounter = " +pm.checkCounter.get();
    }
}

class PairChecker implements Runnable {

    private PairManager pm;

    public PairChecker(PairManager pm) {
        this.pm = pm;
    }

    @Override
    public void run() {
        while (true) {
            pm.checkCounter.incrementAndGet();

            pm.getPair().checkState();
        }
    }
}

/**
 * PairManager1.increment不允许有PairManager2.increment那样多,后者采用同步控制块快进行同步,所以
 * 对象不加锁的时间更长。这也是宁愿使用同步控制块而不是对整个方法进行同步控制的典型原因:使得其他线程能更多地
 * 访问。
 */
public class CriticalSection {
 static void testApproaches(PairManager pman1, PairManager pman2) {
     ExecutorService executorService = Executors.newCachedThreadPool();

     PairManipulator pairManipulator = new PairManipulator(pman1);
     PairManipulator pairManipulator1 = new PairManipulator(pman2);

     PairChecker pairChecker = new PairChecker(pman1);
     PairChecker pairChecker1 = new PairChecker(pman2);

     executorService.execute(pairManipulator);
     executorService.execute(pairManipulator1);

     executorService.execute(pairChecker);
     executorService.execute(pairChecker1);

     try {
         TimeUnit.MILLISECONDS.sleep(5000);
     } catch (InterruptedException e) {
         System.out.println("Sleep interrupted");
     }

     System.out.println("pairMapipulator: "+ pairManipulator +"\npairManipulator1: " +pairManipulator1);
     System.exit(0);
 }

    public static void main(String[] args) {
        PairManager pairManager = new PairManager1();
        PairManager pairManager2 = new PairManager2();
        testApproaches(pairManager,pairManager2);

    }
}

你还可以使用显示的Lock对象来创建临界区:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ExplicPairManger1 extends PairManager{

    private Lock lock = new ReentrantLock();


    @Override
    public synchronized void increment() {
        lock.lock();

        try {
            p.incrementX();
            p.incrementY();
            store(getPair());
        } finally {
            lock.unlock();
        }
    }
}

class  ExplicPairManger2 extends PairManager{

    private Lock lock = new ReentrantLock();

    @Override
    public void increment() {
        Pair temp;

        lock.lock();

        try {
            p.incrementY();
            p.incrementX();
            temp = getPair();
        } finally {
            lock.unlock();
        }

        store(temp);
    }
}

public class ExplicCriticalSection {
    public static void main(String[] args) {
        ExplicPairManger1 explicPairManger1 = new ExplicPairManger1();
        ExplicPairManger2 explicPairManger2 = new ExplicPairManger2();

        CriticalSection.testApproaches(explicPairManger1,explicPairManger2);
    }
}

这里复用了CriticalSection的绝大部分,并创建了新的使用显示的Lock对象的PairManager类型。ExplicitPairManger2展示了如何使用Lock对象来创建临界区,而对store的调用则在这个临界区的外部。

你可能感兴趣的:(java)