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的使用方式可以更加灵活,至少我个人还是很喜欢这种方式的,嘿嘿。
另外今天是七夕节,就到此为止,还要回家陪老婆呢,唉,就是这么忙呀。