Java并发——多线程数据共享

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给出了许多处理并发的机制,但还是有可能出现死锁。因此必须仔细涉及程序,以确保不会出现死锁。


猜你喜欢

转载自blog.csdn.net/a40850273/article/details/80729466