一,jdk1.5的上锁机制和解锁机制

传统的方法是通过synchronized给代码块上锁,jdk1.5之后提供了显示的锁机制,通过创建ReentrantLock对象:Lock lock = new ReentrantLock();获得一个锁,


然后调用ReentrantLock类的lock()方法上锁,unLock()方法解锁。


代码中给出了两种上锁的方式,注意注释部分。

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

public class LockTest {

	public static void main(String[] args) {
		Res r = new Res();
		init(r);
	}
	
	private static void init(final Res r) {
		//创建一个线程
		new Thread(new Runnable() {

			@Override
			public void run() {
				while(true) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					r.output("zhangsan");
				}
			}
			
		}).start();
		
		//创建第二个线程
		new Thread(new Runnable() {

			@Override
			public void run() {
				while(true) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					r.output("lisi");
				}
			}
			
		}).start();
	}

	static class Res {
		//先创建一个锁
		private final Lock lock = new ReentrantLock();
		public void output(String name) {
			//传统上锁的方法使用synchronized
			//synchronized (Res.class) {
			
			//使用jdk1.5的线程上锁机制
			lock.lock();//上锁
			try {
				for (int i = 0; i < name.length(); i++) {
					System.out.print(name.charAt(i));//打印每一个字母,循环名字的长度次,结果打印出的是名字
				}
				System.out.println();
				//}
			} finally {					
				lock.unlock();//解锁,因为使用lock()方法上锁后如果锁内的代码出现问题,比如异常,那么这个锁将永远
				//无法解除,而在finally中,可以避免这个问题。
			}
		}
	}
}



二,读写锁:


当要对共享的资源进行读写操作的时候,可以同时多个线程进行读操作,但是读的时候不允许别的线程进来写,写的时候也不允许别别的线程来写,写的时候不允许别的线程

进来读。

示例:(读写锁的使用)

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

/*
 * 创建一个类,类中提供的有读数据和写数据的方法,这两个方法共享同一个数据对象,创建多个线程,
 * 分别读和写数据,使用读写锁解决线程安全问题。
 */
public class ReadAndWriteLock {

	public static void main(String[] args) {
		final Queue q = new Queue();
		for(int i=0;i<3;i++) {
			//创建三个线程,读数据
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					while(true) {
						q.getData();
					}
				}
			}).start();
			
			//创建三个线程设置数据
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					while(true) {
						q.setData(new Random().nextInt(10000));
					}
				}
			}).start();
		}		
	}

	static class Queue {
		private Object data = null;//该data是共享的数据,只能有一个线程能够写该数据,但是可以同时又多个线程可以读该数据
		//创建读写锁,在读方法上上读锁,读的时候不能写,在写方法上上写锁,写的时候不能读,也不能别的线程进来写
		ReadWriteLock rwl = new ReentrantReadWriteLock();//读写所
		public void getData() {
			rwl.readLock().lock();//上读锁
			try {
				System.out.println(Thread.currentThread().getName()
						+ "start get data");
				try {
					Thread.sleep(new Random().nextInt(1000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()
						+ "had get data :" + data);
			} finally {
				rwl.readLock().unlock();
			}
		}
		
		public void setData(Object data) {
			rwl.writeLock().lock();
			try {
				System.out.println(Thread.currentThread().getName() + "begin set data");
					Thread.sleep(new Random().nextInt(1000));
				this.data = data;
				System.out.println(Thread.currentThread().getName() + "has set data :" + data);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally {
				rwl.writeLock().unlock();
			}
		}
	}
}



读写锁的使用:设计一个缓存系统。

原理:当用户来请求数据时,先通过缓存系统,缓存系统然后去数据库里面取。当缓存系统取数据后,缓存起来,当下次用户在请求相同的数据时,就直接从缓存系统里面直接取,二不需要再从数据库中取,就可以提高效率。

示例:通过map集合存取键值,用户通过请求(键)获取数据(值),当缓存的有时,就不去数据库再次获取。

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

public class CacheSystem {

	//创建一个map集合存储缓存 信息,缓存在存储设备中都是键值对的形式存在的。
	private Map<String, Object> cache = new HashMap<>();
	public static void main(String[] args) {
		
	}

	//一般的程序员直接在该方法上面加一个锁就可以了,就是面试官要的答案,但是使用jdk1.5线程机制则体现水平
	private ReadWriteLock rwl = new ReentrantReadWriteLock();
	public Object getData(String key) {
		rwl.readLock().lock();//当线程进去读的时候上一个读锁
		Object value;
		try {
			value = cache.get(key);
			if(value== null) {
				//当发现值为空时,就将解除读锁,上一个写锁,写入数据
				rwl.readLock().unlock();
				rwl.writeLock().lock();
				try {
					/*
					 * 下面还要在判断一次的原因是:当多个线程一开始获取数据时,没有,也就是上面一个value为null满足,
					 * 这时候读锁被解,最先读的那个线程上写锁,然后其余线程阻塞在这里,这时候这个线程进去写入数据,写
					 * 完后释放写锁,之前阻塞的线程获取写锁,进入后如果不判断value是不是为空,那么会重复获取数据,实
					 * 际是访问数据库,这时候就没有意义了。
					 */
					if (value == null) {
						value = "abc";//这里实际是取queryDB;
					}
				} finally {
					rwl.writeLock().unlock();					
				}
				rwl.readLock().lock();
			}
		}finally {
			rwl.readLock().unlock();
		}
		return value;
	}
}



三,jdk1.5提供了显式的锁机制,也可以实现线程间的通信。使用显示锁机制实现通信,使用的是Condition类对象。

方法:

1,创建Lock的子类对象:Lock lock = new ReentrantLock();

2,通过Lock对象获取Condition对象,返回用来与此 Lock 实例一起使用的 Condition 实例。

Condition t1 = lock.newCondition();可以创建多个Condition用来等待(await()方法)和唤醒(signal()方法)指定的Condition对象。也就是说方法之间的同步,可以每个方法都占有一个Condition对象,这样线程执行完该方法后就可以通过不同的Condition对象唤醒指定的线程。传统的线程间通信是先通过synchronized给方法上锁,当一个线程执行完某个方法后,别的多个线程在等待时(wait()方法),这个线程通过notify()方法只能唤醒某一个在等待的线程,如果唤醒多个,则使用notifyAll()方法,唤醒所有的线程,而不是唤醒需要被唤醒的线程来执行它该执行的线程。而Condition对象则是唤醒应该醒的线程,不该醒的则继续等待。


示例:三个线程之间的通信。

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

/*
 * 需求:子线程循环10次,接着主线程循环100次,接着子线程又循环10,子线程在循环10次,如此反复执行50次。
 * 分析:将要用到的共同数据(包括同步锁)或共同算法的若干个方法归结到同一个类身上,这种方式体现了高类聚和程序的健壮性。
 * 
 * 改进一下该程序,让三个线程之间通信。当第一个子线程执行完后,第二个子线程执行,执行完后主线程执行,并且之后按照这个
 * 顺序轮换执行。
 */
public class ConditionDemo {

	public static void main(String[] args) {
		//创建共同类的对象
		final Business business = new Business();
		//子线程执行100次
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i=0;i<50;i++) {
					business.childThread();
				}
			}
		}).start();
		//创建第二个子线程,让三个线程之间进行通信
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i=0;i<50;i++) {
					business.childThread2();
				}
			}
		}).start();
		
		//主线程执行50次
		for(int i=0;i<50;i++) {
			business.mainThread();
		}
	}

	//传统的线程通信室通过synchronized在方法上面上锁,然后使用wait()和notify()等待和唤醒处于等待的线程。
	//在jdk1.5提供了Condition类,可以明确的指定唤醒哪个等待的线程
	static class Business {
		final Lock lock = new ReentrantLock();
		//创建三个Condition对象,标志三个线程的等待与唤醒
		Condition condition1 = lock.newCondition();
		Condition condition2 = lock.newCondition();
		Condition condition3 = lock.newCondition();
		//boolean flag = true;//该标志代表是不是该子线程执行循环了,一开始是子线程先执行
		int shuldSub = 1;//一开始执行50次循环的子线程执行
		//子线程循环10次的代码
		public /*synchronized*/ void childThread() {
			lock.lock();
			try {
				//判断sulldSub的值,如果不是等于1,则等待,否则循环开始
				while (shuldSub != 1) {
					try {
						//this.wait();
						condition1.await();//调用该方法的线程等待
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				for (int i = 0; i < 10; i++) {
					System.out.println(Thread.currentThread().getName()
							+ " loop of " + (i + 1));
				}
				shuldSub = 2;//循环结束后,把标志置为2,然后唤醒第二个子线程(执行20次循环的)
				//this.notify();
				condition2.signal();
			} finally {
				lock.unlock();
			}
		}
		//子线程2,让子线程2循环20次
		public /*synchronized*/ void childThread2() {
			lock.lock();
			try {
				//判断shuldSub是不是等于2,不是则condition2等待,否则执行循环
				while (shuldSub != 2) {
					try {
						//this.wait();
						condition2.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				for (int i = 0; i < 20; i++) {
					System.out.println(Thread.currentThread().getName()
							+ " loop of " + (i + 1));
				}
				shuldSub = 3;//执行完后,将标志置为3,轮到主线程执行了,唤醒主线程的等待
				//this.notify();
				condition3.signal();
			} finally {
				lock.unlock();
			}
		}
		
		//主线程执行循环100次的方法
		public /*synchronized*/ void mainThread() {
			lock.lock();
			try {
				while (shuldSub != 3) {
					try {
						//this.wait();
						condition3.await();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				for (int i = 0; i < 100; i++) {
					System.out.println(Thread.currentThread().getName()
							+ " loop of " + (i + 1));
				}
				shuldSub = 1;
				//this.notify();
				condition1.signal();//唤醒等待的condition1
			} finally {
				lock.unlock();
			}
		}
	}
}