Java——并发(二)
Brain的同步规则:如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那些你必须使用同步,并且读写线程都必须用相同的监视器同步。
这一部分可以先参考https://www.cnblogs.com/dolphin0520/p/3920373.html
原子性:对于看似简单的变量操作,在JVM内部可能将其拆分成多个操作比如读与写,那么如果在此过程中被中断或上下文改变,最后将得到错误的结果。
可视性:由于现代计算机通常采用缓存机制,线程并不直接修改内存而是在各自的缓存中进行操作,那么如果一个线程在缓存中修改了某值,在并未更新到内存时被其他线程访问或修改将造成错误。
Synchronized关键字
public class SynchronizedEvenGenerator extends IntGenerator{ private int currentEvenValue = 0; public synchronized int next(){ ++currentEvenValue; Thread.yield(); ++currentEvenValue; return currentEvenValue; } public static void main(String[] args){ EvenChecker.test(new SynchronizedEvenGenerator()); } }
临界区:通过synchronized关键字指定某段代码只能被单线程访问
synchronized(syncObject){ // This code can be accessed // by only one task at a time }
在某些时候程序的执行可能需要一些额外的条件才能正确执行。而当某一线程通过synchronized获得运行权后,此时如果不满足运行条件,可以使用wait()函数来放弃运行权,期望其他线程能够改变状态使得该线程能够正确运行;而当其他线程改变状态后,应该调用notify()或notifyAll()来通知任意一个或所有处在wait状态的线程重新校验状态是否满足。
public synchronized void transfer(int from, int to, double amount) throws InterruptedException{ while(accounts[from] < amount) wait(); System.out.println(Thread.currentThread()); accounts[from] -= amount; System.out.println("%10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.println("Total Balance: %10.2f%n", getTotalBalance()); notifyAll(); }
上例中通过wait()实现了对是否账户中拥有充足的余额进行了检验。不过synchronized只能指定一个条件,如果需要多个条件变量可以使用Lock类
Lock类
import java.util.concurrent.locks.*; public class MutexEvenGenerator extends IntGenerator{ private int currentEvenValue = 0; private Lock lock = new ReentrantLock(); public int next(){ lock.lock(); try{ ++currentEvenValue; Thread.yield(); ++currentEvenValue; return currentEvenValue; } finally{ lock.unlock(); } } public static void main(String[] args){ EvenChecker.test(new MutexEvenGenerator()); } }
同理,针对多个条件可以定义多个条件变量,并使用await(),signal()以及signalAll()实现相似的功能。
private final double[] accounts; private Lock bankLock; private Condition sufficientFunds; public Bank(int n, double initialBalance){ accounts = new double[n]; Arrays.fills(accounts, initialBalance); bankLock = new ReentrantLock(); // 保证不同的bank之间不会相互影响 sufficientFunds = bankLock.newCondition(); } public void transfer(int from, int to, double amount) throws InterruptedException{ bankLock.lock(); try{ while(accounts[from] < amount) sufficientFunds.await(); System.out.println(Thread.currentThread()); accounts[from] -= amount; System.out.println("%10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.println("Total Balance: %10.2f%n", getTotalBalance()); sufficientFunds.signalAll(); } finally{ bankLock.unlock(); } }
mylock.tryLock(100, TimeUnit.MILLISECONDS):在指定时间内尝试获取锁,如果不成功返回false,从而使得线程能够进行判断从而实现流程控制。
同时,Java还允许分别定义读写锁,实现对特定读写操作的控制。
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private Lock readLock = rwl.readLock(); private Lock writeLock = rwl.writeLock();
volatile关键字
其保证修饰的变量在缓存中的修改将立即被写入到主存中,而读取操作就发生在主存中。但volatile并不保证操作的原子性,即其只能保证简单数据类型的简单操作的并发正确性。
原子类
import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.*; public class AtomicIntegerTest implements Runnable { private AtomicInteger i = new AtomicInteger(0); public int getValue() { return i.get(); } private void evenIncrement() { i.addAndGet(2); } public void run() { while(true) evenIncrement(); } public static void main(String[] args){ new Timer().schedule(new TimerTask(){ public void run(){ System.err.println("Aborting"); System.exit(0); } }, 5000); // Terminate after 5 seconds 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); } } } }/* Output Aborting *///:~
线程局部变量(ThreadLocal)
线程局部变量是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储,实现每个线程都用于相同对象相同域的不同存储块,实现不同线程数据分隔。
import java.util.concurrent.*; import java.util.*; class Accessor implements Runnable{ private final int id; public Accessor(int idn){ id = idn; } public void run(){ while(!Thread.currentThread().isInterrupted()){ ThreadLocalVariableHolder.increment(); System.out.println(this); Thread.yield(); } } public String toString(){ return "#" + id + ": " + ThreadLocalVariableHolder.get(); } } public class ThreadLocalVariableHolder{ private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){ private Random rand = new Random(47); protected synchronized Integer initialValue(){ return rand.nextInt(10000); } }; public static void increment(){ value.set(value.get() + 1); } public static int get(){ return value.get(); } public static void main(String[] args) throws Exception{ ExecutorService exec = Executors.newCachedThreadPool(); for(int i=0; i<5; i++) exec.execute(new Accessor(i)); TimeUnit.SECONDS.sleep(1); // Run for a while exec.shutdownNow(); // All Accessors will quit } }/* Output #0: 9259 #3: 1862 #1: 556 #4: 962 #2: 6694 #4: 963 #1: 557 #3: 1863 #0: 9260 #3: 1864 #1: 558 #4: 964 #2: 6695 ... *///:~
通过ThreadLocal实现了每个线程拥有自己独立的value域。
死锁
虽然Java给出了许多处理并发的机制,但还是有可能出现死锁。因此必须仔细涉及程序,以确保不会出现死锁。