Java线程(四):JUC包线程互斥与同步以及读写锁

时间:2023-02-13 13:13:09

前言:

1、Java线程(一):传统线程的实现、互斥与通信


一、线程的互斥

(一)传统互斥:synchronized 关键字 

1、synchronized(对象) 代码块需要锁定同一个对象,一般会锁定业务类对象,即synchronized(this)即可。

2、如静态方法互斥,则需要锁定内存中的字节码对象,即synchronized(XXX.class)。

3、synchronized 方法锁定的也是对象。

		/**
		 * synchronized 方法
		 * 其实锁定的也是Outputer 对象
		 * @param name
		 */
		public synchronized void output1(String name) {
			int len = name.length();
			for (int i = 0; i < len; i++) {
				System.out.print(name.charAt(i));
			}
			System.out.println();
		}



(二)juc互斥:Lock

1、Lock 比传统线程模型中的 synchronized  方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。

Lock lock = new ReentrantLock();

2、两个线程执行的代码片段要实现同步互斥的效果,它们必须使用同一个锁对象。

3、锁(Lock)是上在要操作的资源的类的内部方法中,而不是线程代码中。

package com.newThread;

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

/**
 * 
 * LockAndCondition.java
 * 
 * @title Lock和Condition线程同步(替代synchronized)
 * @description
 * @author SAM-SHO
 * @Date 2014-8-23
 */
public class LockAndCondition {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		LockAndCondition tLockAndCondition = new LockAndCondition();
		tLockAndCondition.init();

	}

	/**
	 * 定义初始化方法, 解决main(静态)方法不能创建内部类实例对象
	 */
	public void init() {
		FirstThread firstThread = new FirstThread();
		firstThread.start();

		SecondThread secondThread = new SecondThread();
		secondThread.start();

	}

	/**
	 * 
	 * Outputer.java
	 * 
	 * @title 内部类实现输出,锁在资源类的方法中
	 * @description
	 * @author SAM-SHO
	 * @Date 2014-8-17
	 */
	class Outputer {

		// 使用锁:LOCK解决
		Lock lock = new ReentrantLock();//同一个锁对象

		public void output3(String name) {
			int len = name.length();
			try {
				lock.lock();
				for (int i = 0; i < len; i++) {
					System.out.print(name.charAt(i));
				}
				System.out.println();
			} finally {
				lock.unlock();
			}

		}

		/**
		 * 传统互斥,使用synchronized 关键字
		 * @param name
		 */
		public synchronized void output(String name) {
			int len = name.length();
			for (int i = 0; i < len; i++) {
				System.out.print(name.charAt(i));
			}
			System.out.println();
		}
	}

	/*
	 * 线程一
	 */
	class FirstThread extends Thread {
		Outputer out = new Outputer();

		@Override
		public void run() {
			while (true) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				out.output("ShaoXiaoBao");
			}
		}
	}

	/*
	 * 线程二
	 */
	class SecondThread extends Thread {
		Outputer out = new Outputer();

		@Override
		public void run() {
			while (true) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				out.output("ZhaoXiaoNiu");
			}
		}
	}
}


二、读写锁:ReadWriteLock

1、互斥关系:读写锁,分为读锁与写锁。多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。

2、控制:这种读写锁的互斥关系是由 JVM 自己控制的,程序员只要上好相应的锁即可。

package com.newThread;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 
 * ReadAndWriteLock.java
 *
 * @title 读写锁
 * @description
 * @author SAM-SHO 
 * @Date 2014-11-29
 */
public class ReadAndWriteLock {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ReadAndWriteLock tReadAndWriteLock= new ReadAndWriteLock();
		tReadAndWriteLock.init();
	}
	
	private void init() {
		Queue tQueue = new  Queue();
		OneReadThread oneRThread = new OneReadThread(tQueue);
		new Thread(oneRThread).start();
		
		OneWriteThread oneWThraed= new OneWriteThread(tQueue);
		new Thread(oneWThraed).start();
		
		TwoReadThread twoRThread = new TwoReadThread(tQueue);
		new Thread(twoRThread).start();
		
		TwoWriteThread twoWThread= new TwoWriteThread(tQueue);
		new Thread(twoWThread).start();
	}
	
	/**
	 * @title 读的线程
	 * @description
	 * @author SAM-SHO 
	 * @Date 2014-8-23
	 */
	class OneReadThread implements Runnable{
		
		private Queue tQueue;
		
		public OneReadThread(Queue tQueue) {
			super();
			this.tQueue = tQueue;
		}

		@Override
		public void run() {
			for (int i = 0; i < 5; i++) {
				tQueue.read();
			}
			
		}
	}
	
	class TwoReadThread implements Runnable{
		
		private Queue tQueue;
		
		public TwoReadThread(Queue tQueue) {
			super();
			this.tQueue = tQueue;
		}

		@Override
		public void run() {
			for (int i = 0; i < 5; i++) {
				tQueue.read();
			}
			
		}
	}
	
	/**
	 * @title 写的线程
	 * @description
	 * @author SAM-SHO 
	 * @Date 2014-8-23
	 */
	class OneWriteThread implements Runnable{
		
		private Queue tQueue;
		
		public OneWriteThread(Queue tQueue) {
			super();
			this.tQueue = tQueue;
		}

		@Override
		public void run() {
			for (int i = 3; i < 7; i++) {
				tQueue.write("woshibei-- " + i);
				
			}
			
		}
	}
	
	class TwoWriteThread implements Runnable{
		
		private Queue tQueue;
		
		public TwoWriteThread(Queue tQueue) {
			super();
			this.tQueue = tQueue;
		}

		@Override
		public void run() {
			for (int i = 0; i < 2; i++) {
				tQueue.write("woshibei-- " + i);
				
			}
		}
	}
	
	/**
	 * @title 具体业务类
	 * @description
	 * @author SAM-SHO 
	 * @Date 2014-8-23
	 */
	class Queue{
		//共享数据,只能有一个线程能写该数据,但可以多个线程同时读数据
		private Object data = null;
		
		//使用读写锁
		ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
		
		//读数据的方法
		public void read() {
			
			readWriteLock.readLock().lock();//上锁
			try {
				
				System.out.println("开始读数据.....");
				Thread.sleep((long) (Math.random()*1000));
				System.out.println(Thread.currentThread().getName()+"已经读到数据: " + data);
			
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally{
				readWriteLock.readLock().unlock();//解锁
			}
			
		}
		
		//写数据的方法
		public void write(Object tData) {
			
			readWriteLock.writeLock().lock();//上锁
			
			try {
				System.out.println("准备写数据.....");
				Thread.sleep((long) (Math.random()*1000));
				this.data = tData;
				System.out.println(Thread.currentThread().getName()+"已经写完数据: " + data);
			
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally {
				readWriteLock.writeLock().unlock();//解锁
			}
			
		}
		
	}

}


3、利用读写锁实现一个简单的缓存类。

package com.newThread;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 
 * CacheDemo.java
 * 
 * @title 利用读写锁实现缓存
 * @description
 * @author SAM-SHO
 * @Date 2014-11-30
 */
public class CacheDemo {

	// 模拟缓存容器
	private Map<String, Object> cache = new HashMap<String, Object>();

	// 读写锁对象
	private ReadWriteLock rwl = new ReentrantReadWriteLock();
	public Object getData(String key) {
		rwl.readLock().lock();// 先读锁锁住
		Object value = null;
		try {
			value = cache.get(key);// 从缓存取数据
			if (value == null) {
				rwl.readLock().unlock();// 如果没数据,读锁解除
				rwl.writeLock().lock();//保证去数据库取值,只发生一遍,只有一个线程升级成写锁
				try {
					if (value == null) {
						value = "aaaa";// 赋值,去数据库查询
					}
				} finally {
					rwl.writeLock().unlock();//写锁解除
				}
				rwl.readLock().lock();
			}
		} finally {
			rwl.readLock().unlock();
		}
		return value;
	}
}


三、同步

(一)传统同步:wait、notify

1、wait、notify必须在 synchronized  代码块内部。不然会报状态不对的 Exception 。

	/**
	 * 
	 * Bussiness
	 *
	 * @title 业务实现类
	 * @description 用到的共同数据的若干方法应该设计在一个类身上
	 * 
	 * @author SAM-SHO 
	 * @Date 2014-8-17
	 */
	class Bussiness {
		private boolean bShouldSub = true;
		
		/*
		 * 循环10次的方法
		 * if 可以用 while代替,会多检查一次
		 * synchronized(对象) 与 对象.wait 必须为同一个
		 */
		public synchronized void eachChild(int index) {
			while(!bShouldSub) {//一开始是true,那么就是执行循环
				try {
					this.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
			for (int i = 0; i <= 10; i++) {
				System.out.println("子 线程-- "+i+ ", 第"+index+"循环");	
			}
			
			bShouldSub = false;
			this.notify();//唤醒主线程
			
		}
		
		/*
		 * 循环50次的方法
		 * if 可以用 while代替
		 */
		public synchronized void eachMain(int index) {
			while(bShouldSub) {//一开始是true,那么就会wait
				try {
					this.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
			for (int i = 0; i <= 30; i++) {
				System.out.println("主 线程---- "+i + ", 第"+index+"循环");
			}
			
			bShouldSub = true;
			this.notify();//唤醒子线程
		}
		
	}


(二)juc同步通信:Condition

1、Lock只能实现线程的互斥,不能实现通信。Condition的功能类似于传统线程技术中的wait、notify 的功能。

		Lock lock = new ReentrantLock();//condition是基于锁之上的
		Condition condition = lock.newCondition();

2、在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待


3、Condition  的两个方法:condition.await();//等待,condition.signal();//唤醒

	/**
	 * 
	 * Bussiness
	 *
	 * @title 业务实现类 Condition 实现
	 * @description 用到的共同数据的若干方法应该设计在一个类身上
	 * 
	 * @author SAM-SHO 
	 * @Date 2014-8-17
	 */
	class BussinessCondition {
		
		Lock lock = new ReentrantLock();//condition是基于锁之上的
		Condition condition = lock.newCondition();
		private boolean bShouldSub = true;
		
		/*
		 * 循环10次的方法
		 * if 可以用 while代替 可以再次检查条件,防止虚假唤醒
		 */
		public void eachChild(int index) {
			lock.lock();//锁掉
			try{
				while(!bShouldSub) {
					try {
						condition.await();//等待
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				
				for (int i = 0; i <= 10; i++) {
					System.out.println("子 线程-- "+i+ ", 第"+index+"循环");	
				}
				
				bShouldSub = false;
				condition.signal();//唤醒
			}finally{
				lock.unlock();//解锁
			}
		}
		
		/*
		 * 循环50次的方法
		 * if 可以用 while代替
		 */
		public void eachMain(int index) {
			lock.lock();//锁掉
			
			try{
				while(bShouldSub) {
					try {
						condition.await();//等待
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				
				for (int i = 0; i <= 50; i++) {
					System.out.println("主 线程---- "+i + ", 第"+index+"循环");
				}
				
				bShouldSub = true;
				condition.signal();//等待
			}finally{
				lock.unlock();//解锁
			}
		}
		
	}

4、Condition 是基于Lock 的。一个 Lock 可以有多个 Condition 。

5、利用Lock 和 Condition  实现一个阻塞队列。(线程队列  ArrayBlockingQueue 类提供了这项功能)

1)如有两种功能的线程(如生产者、消费者)。 如果只用一个 condition ,取的condition只能唤醒取的,放的condition只能唤醒放的。

2)如果有5个放的,其中一个放的等待,然后去唤醒,只能去唤醒另外的放的,还是达不到唤醒取的效果。所以需要用两个condition,区分2种不同功能的线程。

package com.newThread;

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

/**
 * 
 * BoundedBuffer.java
 *
 * @title 线程队列  ArrayBlockingQueue 类提供了这项功能,因此没有理由去实现这个示例类。) 
 * @description
 * 如果只用一个condition(取的condition只能唤醒取的,放的condition只能唤醒放的),如果有5个放的,
 * 其中一个放的等待,然后去唤醒,只能去唤醒另外的放的,还是达不到唤醒取的作用。所以用两个condition,区分
 * 2种不同功能的线程。
 * @author SAM-SHO 
 * @Date 2014-8-25
 */
public class BoundedBuffer {
	final Lock lock = new ReentrantLock();
	//两个Condition基于同一个Lock
    final Condition notFull  = lock.newCondition(); //“放”功能的线程的 Condition
    final Condition notEmpty = lock.newCondition(); //“取”功能的线程的 Condition

    final Object[] items = new Object[100];//容器容量为100
    int putptr, takeptr, count;

    /**
     * 往队列放数据
     * @param x
     * @throws InterruptedException
     */
    public void put(Object x) throws InterruptedException {
    	lock.lock();
    	try {
    		while (count == items.length) {//放满了
    			notFull.await();//"放"的 Condition 等待
    		}  
    		items[putptr] = x; //把 x 放入队列
    		if (++putptr == items.length) {//如果位置放到100了,从新从 0 开始放
    			putptr = 0;
    		}
    		++count;//放进去一个就 count++
    		notEmpty.signal();//唤醒"取"的 Condition
    	} finally {
    		lock.unlock();
    	}
    }
 
    /**
     * 从队列取数据
     * @return
     * @throws InterruptedException
     */
    public Object take() throws InterruptedException {
    	lock.lock();
    	try {
    		while (count == 0) {//队列中没有数据
    			notEmpty.await();//"取"的等待
    		}
    		Object x = items[takeptr]; 
    		if (++takeptr == items.length) {//取值已到 100 从 0开始取
    			takeptr = 0;
    		}
    		--count;//取走一个 count--
    		notFull.signal();//唤醒"放"的
    		return x;
    	} finally {
    		lock.unlock();
    	}
    } 

}