Java多线程高级_读写锁

一,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();
			}
		}
	}
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以!下面是一篇关于Java线程中的""的博客: ## Java线程中的Java线程编程中,是一种重要的同步机制,用于保护共享资源的访问。使用可以防止多个线程同时对共享资源进行修改,从而避免数据不一致的问题。 ### 1. 什么是是一种同步机制,它可以让多个线程按照一定的顺序访问共享资源。在Java中,可以是隐式的,也可以是显式的。 - 隐式:通过关键字`synchronized`来实现,它可以用于修饰方法或代码块。当一个线程进入被`synchronized`修饰的方法或代码块时,它会自动获取,并在执行完毕后释放。 - 显式:通过`java.util.concurrent.locks`包中的Lock接口及其实现类来实现,如ReentrantLock。显式需要手动获取和释放,在使用上更加灵活。 ### 2. 的作用 的主要作用是保护共享资源的访问,它可以解决多线程并发访问时可能引发的以下问题: - 竞态条件(Race Condition):当多个线程同时访问共享资源,并且对其进行操作时,可能会导致数据的不一致。 - 临界区(Critical Section):当多个线程同时访问共享资源,并且对其进行操作时,可能会导致数据的不一致。 - 死(Deadlock):当多个线程相互等待对方释放时,可能会导致程序无法继续执行。 ### 3. 的类型 Java中常用的类型包括: - 内置(Intrinsic Lock):也称为监视器(Monitor Lock),是由关键字`synchronized`来实现的。内置是基于对象的,每个对象都有一个用于同步的内置,当一个线程获取了该后,其他线程必须等待。 - 重入(Reentrant Lock):是`java.util.concurrent.locks`包中的一个显式实现类,它具有与内置类似的功能,但提供了更高级的特性,如可重入、公平和超时等。 - (Read-Write Lock):也是`java.util.concurrent.locks`包中的一个显式实现类,它区分了操作和操作,允许多个线程同时取共享资源,但只允许一个线程进行操作。 ### 4. 的使用示例 下面是一个使用内置`synchronized`来实现线程安全的示例: ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` 在上述示例中,`increment()`和`getCount()`方法都被`synchronized`修饰,这意味着同一时间只能有一个线程执行这些方法,从而保证了`count`变量的访问安全。 ### 5. 的注意事项 在使用时,需要注意以下事项: - 避免死:在获取的时候,要确保能够及时释放,避免多个线程相互等待对方释放而导致死。 - 避免饥饿:要确保所有线程都有公平获取的机会,避免某个线程一直无法获得而导致饥饿。 - 的粒度:要选择合适的粒度,尽量减小的范围,以提高程序的并发性能。 - 的性能:显式相对于内置,通常具有更高的性能,但使用不当可能导致性能问题。要根据实际情况选择合适的。 ### 总结 Java线程编程中重要的同步机制,用于保护共享资源的访问。它可以解决竞态条件、临界区和死等问题。在使用时,需要注意避免死和饥饿,选择合适的粒度和类型,以及权衡的性能。 希望本篇博客对你有所帮助!如有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值