Java多线程再升级(一)

Java多线程再升级(一)

关于同步,之前已经介绍了synchronized关键字,并且通过该关键字实现了包括生产者消费者等很多的内容。
但是,java中实现多线程的同步并不止synchronized关键字这一种方案,还有一种用lock类实现的方法,今天,就总结一下锁的相关内容——ReentrantLock类和ReentrantReadWriteLock类。

ReentrantLock类

Reentrantlock类的使用方法很简单,如下所示:

public class LockTest {
	private Lock lock=new ReentrantLock();
	public void testMethod() {
		lock.lock();
		for(int i=0;i<3;i++) {
			System.out.println(Thread.currentThread().getName()+(i+1));
		}
		lock.unlock();
	}
}
public class New_thread extends Thread{
	private LockTest testlock;
	public New_thread(LockTest t) {
		this.testlock=t;
	}
	public void run() {
		testlock.testMethod();
	}
}
public class Test_thread{
	public static void main(String[] args){
		LockTest test=new LockTest();
		New_thread t1=new New_thread(test);
		New_thread t2=new New_thread(test);
		New_thread t3=new New_thread(test);
		New_thread t4=new New_thread(test);
		New_thread t5=new New_thread(test);
		t1.start();t2.start();t3.start();t4.start();t5.start();
		
	}
}

运行结果:
在这里插入图片描述
从结果可以看出,通过对ReentrantLock类的使用,从上锁到释放锁,可以很好的使线程得到同步。Lock.lock()方法的使用就相当于线程拥有了“对象监视器”,其他线程只有等待锁被释放的时候再次争夺,效果和使用synchronized一样,线程还是顺序执行的。

Condition的使用

ReentrantLock类有一个配合的类,就是Condition,这个类有很大的作用。
Object类中有wait方法以及与其对应的notify方法,可以实现等待/通知机制,而Condition也有和其类似的方法,就是await和signal方法。
使用方法如下:

public class LockTest {
	private Lock lock=new ReentrantLock();
	public Condition condition=lock.newCondition();
	public void testMethod() {
		try {
			lock.lock();
			System.out.println(System.currentTimeMillis());
			condition.await();
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	public void signal() {
		try {
			lock.lock();
			System.out.println(System.currentTimeMillis());
			condition.signal();
		}
		finally {
			lock.unlock();
		}
	}
}
public class New_thread extends Thread{
	private LockTest testlock;
	public New_thread(LockTest t) {
		this.testlock=t;
	}
	public void run() {
		testlock.testMethod();
	}
}
public class Test_thread{
	public static void main(String[] args){
		try {
		LockTest test=new LockTest();
		New_thread t1=new New_thread(test);
		t1.start();
		Thread.sleep(1000);
		test.signal();
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
}

实验结果:
在这里插入图片描述
当然,Object类中有notifyAll的实现可以实现唤醒所有的线程,同样,Condition中也有signalAll方法,使用方法如下:

public class LockTest {
	private Lock lock=new ReentrantLock();
	public Condition condition1=lock.newCondition();
	public Condition condition2=lock.newCondition();
	public void awaitA() {
		try {
			lock.lock();
			System.out.println("Start awaitA:"+System.currentTimeMillis());
			condition1.await();
			System.out.println("End awatiA:"+System.currentTimeMillis());
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	public void awaitB() {
		try {
			lock.lock();
			System.out.println("Start awaitB:"+System.currentTimeMillis());
			condition2.await();
			System.out.println("End awaitB:"+System.currentTimeMillis());
		}catch(InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	public void signalAllA() {
		try {
			lock.lock();
			System.out.println("signalAllA:"+System.currentTimeMillis());
			condition1.signalAll();
		}finally {
			lock.unlock();
		}
	}
	public void signalAllB() {
		try {
			lock.lock();
			System.out.println("signalAllB:"+System.currentTimeMillis());
			condition2.signalAll();
		}finally {
			lock.unlock();
		}
	}
}
public class New_thread extends Thread{
	private LockTest testlock;
	public New_thread(LockTest t) {
		this.testlock=t;
	}
	public void run() {
		testlock.awaitA();
	}
}
public class Another_thread extends Thread{
	private LockTest testlock;
	public Another_thread(LockTest t) {
		this.testlock=t;
	}
	public void run() {
		testlock.awaitB();
	}
}
public class Test_thread{
	public static void main(String[] args){
		try {
		LockTest test=new LockTest();
		New_thread t1=new New_thread(test);
		Another_thread t2=new Another_thread(test);
		t1.start();
		t2.start();
		Thread.sleep(2000);
		test.signalAllB();
		Thread.sleep(2000);
		test.signalAllA();
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
}

运行结果:
在这里插入图片描述
从结果中可以看出,condition类可以完成和Object类比较相似的功能,这样的情况下,就可以通过对condition的控制,唤醒不同类型的线程,而不是唤醒所有的线程,这样可以更加灵活的控制。

有了condition类的辅助,现在可以实现生产者消费者了,下面是代码的位置,实现的是多对多的生产者消费者模型,功能和上一次的类似,只是使用的方法是lock类和condition类的合作完成的。
生产者-消费者(lock和condition实现)
提取码:iktn
CSDN链接
运行结果:
在这里插入图片描述

公平锁和非公平锁

锁lock分为“公平锁”和“非公平锁”。公平锁表示线程获取锁的时候按照线程加锁的顺序执行,即先来先得。非公平锁就是一种获得锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁。
代码如下所示:

public class LockTest {
	private Lock lock=new ReentrantLock();
	public LockTest(boolean isFair) {
		lock=new ReentrantLock(isFair);
	}
	public void testMethod() throws InterruptedException{
		try {
			lock.lock();
			System.out.println(Thread.currentThread().getName()+"获得锁定");
		}finally {
			lock.unlock();
		}
	}
}
public class New_runnable implements Runnable{
	LockTest test;
	public New_runnable(LockTest t) {
		this.test=t;
	}
	public void run(){
		System.out.println(Thread.currentThread().getName()+"运行了");
		try {
			test.testMethod();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
当为公平锁时,主函数如下所示:
public class Test_thread{
	public static void main(String[] args) throws InterruptedException{
		final LockTest test=new LockTest(true);
		New_runnable r=new New_runnable(test);
		Thread[] threadArray=new Thread[5];
		for(int i=0;i<5;i++) {
			threadArray[i]=new Thread(r);
		}
		for(int i=0;i<5;i++) {
			threadArray[i].start();
		}
	}
}

结果如图所示:
在这里插入图片描述
若使用非公平锁,主函数如下所示:

public class Test_thread{
	public static void main(String[] args) throws InterruptedException{
		final LockTest test=new LockTest(false);
		New_runnable r=new New_runnable(test);
		Thread[] threadArray=new Thread[5];
		for(int i=0;i<5;i++) {
			threadArray[i]=new Thread(r);
		}
		for(int i=0;i<5;i++) {
			threadArray[i].start();
		}
	}
}

运行结果如下所示:
在这里插入图片描述
从上述结果可以看出,公平锁得到锁的机制基本是有序的,并且,先加锁的会先获取锁;而非公平锁的基本上不是有序的,先加锁的也不一定先获得锁。

Lock中常用的方法

因为lock中方法众多,这里我们介绍一些常用的,至于使用方法,大家就自己尝试吧(实在太多,并且都很简单):

方法名作用
getHoldCount查询当前线程保持此锁定的个数,就是调用lock方法的次数
getQueueLength返回正在等待获取此锁的线程估计数
getWaitQueueLength返回等待与此锁定相关的给定条件condition的线程估计数
hasQueuedThread查询指定的线程是否正在等待获取此锁
hasQueuedThreads查询是否有线程正在等待获取此锁
hasWaiters作用是查询是否有线程正在等待与此锁有关的condition条件
isFair判断是否是公平锁(默认是公平锁)
isHelyByCurrentThread查询当前线程是否保持此锁定
isLocked查询此锁定是否由任意的线程保持
lockInterruptibly如果当前线程未被中断,则获取锁定,若已经被中断则出现异常
tryLock仅在调用时锁定未被另一个线程保持的情况下才获得该锁定
tryLock(long timeout,TimeUnit unit)如果锁定在给定等待时间内没有被另外一个线程保持,且当前线程未被中断,则获取该锁定。
awaitUntil等到什么时候自动唤醒,但是可以提前唤醒。

使用ReentrantReadWriteLock类

ReentrantLock具有完全的互斥排他的效果,同一时间只有一个线程在执行lock方法后的任务,这样很大程度上保持了实例变量的线程安全性。但是,这种锁定的方式同时也降低了执行的效率。
而ReentrantReadWriteLock锁可以解决这样的问题。读写锁表示有两个锁,一个读锁,一个写锁。多个读锁之间不互斥,读锁和写锁互斥,写锁和写锁互斥。
在没有写入操作的时候,多个线程可以同时进入并且获得写锁。而写入操作只有等上一个写锁结束以后才可以进行写入操作。同样,在多个线程进行读取操作的时候,同一时刻只允许一个线程进行写入操作。
总结起来:“读写”、“写写”互斥,“读读”非互斥。
代码如下所示:

public class LockTest {
	private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	public void readTest() throws InterruptedException{
		try {
			lock.readLock().lock();
			System.out.println(Thread.currentThread().getName()+"获得读锁"
					+System.currentTimeMillis());
			Thread.sleep(2000);
		}finally {
			lock.readLock().unlock();
		}
	}
	public void writeTest() throws InterruptedException{
		try {
			lock.writeLock().lock();
			System.out.println(Thread.currentThread().getName()+"获得写锁"
					+System.currentTimeMillis());
			Thread.sleep(2000);
		}finally {
			lock.writeLock().unlock();
		}
	}
}
public class Another_thread extends Thread{
	private LockTest testlock;
	public Another_thread(LockTest t) {
		this.testlock=t;
	}
	public void run() {
		try {
			testlock.writeTest();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
public class New_thread extends Thread{
	private LockTest testlock;
	public New_thread(LockTest t) {
		this.testlock=t;
	}
	public void run() {
		try {
			testlock.readTest();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
public class Test_thread{
	public static void main(String[] args) throws InterruptedException{
		LockTest test=new LockTest();
		New_thread tr1=new New_thread(test);
		New_thread tr2=new New_thread(test);
		New_thread tr3=new New_thread(test);
		New_thread tr4=new New_thread(test);
		Another_thread tw1=new Another_thread(test);
		Another_thread tw2=new Another_thread(test);
		Another_thread tw3=new Another_thread(test);
		Another_thread tw4=new Another_thread(test);
		tr1.start();tr2.start();
		Thread.sleep(10000);
		tw1.start();tw2.start();
		Thread.sleep(10000);
		tr3.start();tw3.start();
		Thread.sleep(10000);
		tw4.start();tr4.start();
	}
}

运行结果:
在这里插入图片描述
由此可以验证上述的结论。
以上就是java多线程再升级第一篇的内容,有点多……
这一篇的主要内容就是ReentrantLock和ReentrantReadWriteLock的类的使用方式,完全可以通过lcok的使用来替换掉synchronized关键字的使用。
掌握这种用lock的使用方式可以更加灵活,至少我个人还是很喜欢这种方式的,嘿嘿。

另外今天是七夕节,就到此为止,还要回家陪老婆呢,唉,就是这么忙呀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值