多线程

150 阅读3分钟

目标:编写程序实现把数加1000减1000后打印出来。使用两个线程,一个读线程,一个写线程。

计算机读取数据时的操作顺序:

Iload

Icompute

Istore

1.如果两个写线程同时操作一个数字,会造成冲突(第一个写线程计算结果写入前,第二个写线程读取了之前的数据,因此第二个写线程写入计算数据时,没有包含第一个写线程的结果),需要加入一个锁2.(已经封装在synchronized关键字中了),这样一个写线程只有拿到锁才能操作数字。

3.但是读线程没有锁,读的时候同时写线程运行就会读不到最新数据,4.而常见的是写操作全部做完,再运行读线程。因此需要加一个写线程结束的判断标志,给读线程也加一个锁,拿到锁后才能读数据,5.并且读线程拿到锁的时候不能阻塞线程,以避免造成死锁(读线程拿到锁后等待写线程完成,而写线程无限等待读线程释放锁)。

public class FifthAttempt{
	private int number=0;
	private final Object lock=new Object();
	private bookean writeCompleted=false;
	
	public void read(){
		synchronized(lock){
			while(!writeCompleted){
				try{
					lock.wait();
				}
				catch(InterruptedException e){
					e.printStackTrace();
				}
				System.out.println(number);
			}
		}
	}
	public void write(int n){
		synchronized(lock){
			number+=n;
		}
	}
	
	@Test
	public void test() throws InterruptedException{
		new Thread(() -> {
			for(int i=0;i<1000;i++){
				write(1);
			}
			System.out.println("加1000已完成");
			writeCompleted=true;
			synchronized(lock){
				lock.notifyAll();
			}
		}).start();
		
		new Thread(() -> {
			for(int i=0;i<1000;i++){
				write(-1);
			}
			System.out.println("减1000已完成");
			writeCompleted=true;
			synchronized(lock){
				lock.notifyAll();
			}
		}).start();
		
		//防止读写线程同时访问数字
		Thread.sleep(1000);
		read();
	}
}

6.synchronized关键字太容易造成死锁,因此使用Java5引入的并发工具包java.util.concurrent,7.设置最长获取时间,超过时间不再等待获取锁。

8.concurrent包还用类Condition改造了wait(),notify()为await(),signal(),可以给等待唤醒设置最长时间,超过时间自动唤醒。

public class EighthAttempt{
	private int number=0;
	private final ReentrantLock lock=new ReentrantLock();
	private final condition=lock.newCondition();
	private boolean isCompleted=false;
	
	public void read(){
		lock.lock();
		while(!IsCompleted){
			try{
				condition.await();
			}
			catch(InterruptedException e){
				e.printStackTrace();
			}
			System.out.println(number);
		}
	}
	public void write(int n){
		if(lock.tryLock(1, TimeUnit.SECONDS)){
			lock.lock();
			number+=n;
			lock.unlock();
		}
	}
	
	@Test
	public void test() throws InterruptedException{
		new Thread(() -> {
			for(int i=0;i<1000;i++){
				write(1);
			}
			System.out.println("加1000已完成");
			isCompleted=true;
			lock.lock();
			condition.signal();
			lock.unlock();
		}).start();
		
		new Thread(() -> {
			for(int i=0;i<1000;i++){
				write(-1);
			}
			System.out.println("减1000已完成");
			isCompleted=true;
			lock.lock();
			condition.signal();
			lock.unlock();
		}).start();
		
		//防止读写线程同时访问数字
		Thread.sleep(1000);
		read();
	}
}

9.使用ReadWriteLock,读线程是可以同时操作数据的。当有写线程时,其他线程不能读也不能写;当没有写线程时,其他线程(读线程)可以同时操作数据。

10. 但是,ReadWriteLock读线程的同时是不允许线程的,必须等所有线程读完后才允许修改,称为悲观锁。也可以性能与稳定兼备:乐观地估计读取时不会写入,读取后再做一个检查,如果读取后没有写入,就直接使用读取的数据,乐观的估计为我们提高了效率;如果有写入,就使用悲观锁重新读取数据。检查的方式是StampedLock读取前记录一个戳,写入后修改戳,读取完成后判断这个版本号是否被修改。

public class TenthAttempt{
	private int number=0;
	private final StampedLock lock=new StampedLock();
	
	public void read(){
		long stamp=lock.tryOptimisticRead();
		if(!lock.validate(stamp)){
			stamp=lock.readLock();
			System.out.println(number);
			lock.unlockRead(stamp);
		}
		else {
			System.out.println(number);
		}
	}
	public void write(int n){
		long stamp=lock.writeLock();
		number+=n;
		lock.unlockWrite(stamp);
	}
	
	@Test
	public void test() throws InterruptedException{
		new Thread(() -> {
			for(int i=0;i<1000;i++){
				write(1);
			}
			System.out.println("加1000已完成");
		}).start();
		
		new Thread(() -> {
			for(int i=0;i<1000;i++){
				write(-1);
			}
			System.out.println("减1000已完成");
		}).start();
		
		//防止读写线程同时访问数字
		Thread.sleep(1000);
		read();
	}
}


参考:mp.weixin.qq.com/s/z1aMiMerT…