在Java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,也就是上锁。在JDK1.5之中,新增了ReentrantLock类也能够达到相同的效果。并且在扩展功能上更强、更加灵活。
Lock是一个接口,也就是ReentrantLock以及ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock的的接口。
Lock接口的三个实现类能够实现锁的一些操作,下面是官方的一些介绍:
以及其基本的使用方法:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
使用ReentrantLock可以进行上锁操,保证了部分的线程安全以及防止出现脏读。
package testThread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ThreadA extends Thread {
private TestReentrantLock testReentrantLock;
public ThreadA(TestReentrantLock testReentrantLock) {
// TODO Auto-generated constructor stub
this.testReentrantLock = testReentrantLock;
}
@Override
public void run() {
// TODO Auto-generated method stub
testReentrantLock.testA();
}
}
class TestReentrantLock {
private Lock lock = new ReentrantLock();//得到ReentrantLock对象
public void testA() {
try {
//lock.lock();//进行上锁
System.out.println("方法A拿到了锁!");
System.out.println("---begin----这里是方法A,当前线程的名字为:" + Thread.currentThread().getName());;
System.out.println("进入方法A的时间为:" + System.currentTimeMillis());
Thread.sleep(3000);//当前线程睡眠3秒
System.out.println("---end-----退出方法A的时间为:" + System.currentTimeMillis());
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
//lock.unlock();//释放锁
//System.out.println("方法A释放了锁!");
}
}
}
public class ReentrantLockTest {
public static void main(String[] args) {
TestReentrantLock testReentrantLock = new TestReentrantLock();
ThreadA a1 = new ThreadA(testReentrantLock);
a1.setName("A1");
a1.start();
ThreadA a2 = new ThreadA(testReentrantLock);
a2.setName("A2");
a2.start();
ThreadA a3 = new ThreadA(testReentrantLock);
a3.setName("A3");
a3.start();
}
}
这里将上锁和释放锁的代码都给注释掉了,输出结果如下:
可以看到现在线程随机调用方法A。
当去掉注释之后,也就是进行上锁操作,输出结果如下:
如果有多个锁的话,必须都要释放掉才能被其他线程所使用:
这里新添加了一个锁,不进行释放,那么其他的线程将永远无法得到执行的机会,输出结果如下:
因为lock2没有释放锁,所以一直线程一直在等待。
类ReentrantLock可以完全的互斥排他,这也就说明了同一个时间内只能有一个线程可以执行ReentrantLock.lock之后的内容。这样虽然保证了线程的安全性。但是如果有时候想要在上锁的时候进行其他的读取操作就不可以了,这时候ReentrantReadWriteLock这个类就可以帮助实现读取的操作。
读写锁顾名思义有两种锁,一种是读锁,一种是写锁。读锁也称为共享锁,在一个线程进入读锁的时候,即使不进行释放锁,其他的线程也可以进入读锁的内容中进行操作。写锁也成为排他锁,也就是互斥的,当一个线程上了写锁之后,其他的线程就不能进行操作了。
如果读个线程都是用读锁的话:
package testThread;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ReentrantReadWrite {
private ReentrantReadWriteLock.ReadLock readLock = new ReentrantReadWriteLock().readLock();
public void read() throws InterruptedException {
try {
readLock.lock();//上读锁
System.out.println("获取了读锁!,当前线程名称为:" + Thread.currentThread().getName());
System.out.println("现在的时间为:" + System.currentTimeMillis());
Thread.sleep(5000);
} finally {
readLock.unlock();//释放读锁
}
}
}
class ThreadB extends Thread {
private ReentrantReadWrite readWrite;
public ThreadB(ReentrantReadWrite readWrite) {
this.readWrite = readWrite;
}
@Override
public void run() {
try {
this.readWrite.read();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ReentrantReadWriteLockTest {
public static void main(String[] args) {
ReentrantReadWrite readWrite = new ReentrantReadWrite();
ThreadB b1 = new ThreadB(readWrite);
b1.setName("b1");
ThreadB b2 = new ThreadB(readWrite);
b2.setName("b2");
ThreadB b3 = new ThreadB(readWrite);
b3.setName("b3");
b1.start();
b2.start();
b3.start();
}
}
其他的线程也都可以进行操作,输出结果:
但如果将其换成写锁的话:
其输出结果又成同步的了,也就是必须需要等待锁的释放:
除了读读锁可以共享之外,其他的写写、读写、写读都不能够实现共享操作,均需要等待锁的释放然后争抢到锁之后进行操作。
关键字synchronized与wait和notify/notifyAll方法结合可以实现等待/通知模式,ReentrantLock类也可以实现相同的功能,但是需要借助Condition对象。也就是通过Lock中定义的newCondition方法得到的Condition对象。而且使用Condition更加灵活,可以实现多路通知功能,也就是在一个Lock对象里面创建多个Condition实例,线程对象可以注册在指定的Condition中,从而有选择性的进行线程通知。
···
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyService {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
public void awaitA() {
try {
lock.lock();//在使用Condition前必须调用
System.out.println("Condition调用await");
conditionA.await();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB() {
try {
lock.lock();//在使用Condition前必须调用
System.out.println("Condition调用await");
conditionA.await();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalAll_A() {
try {
lock.lock();
conditionA.signalAll();
System.out.println("Condition调用signalAll");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalAll_B() {
try {
lock.lock();
conditionA.signalAll();
System.out.println("Condition调用signalAll");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
class ThreadC extends Thread {
private MyService myService;
public ThreadC(MyService myService) {
this.myService = myService;
}
@Override
public void run() {
myService.awaitA();
}
}
class ThreadD extends Thread {
private MyService myService;
public ThreadD(MyService myService) {
this.myService = myService;
}
@Override
public void run() {
myService.awaitB();
}
}
public class ConditionTest {
public static void main(String[] args) {
MyService myService = new MyService();
ThreadC c = new ThreadC(myService);
ThreadD d = new ThreadD(myService);
c.start();
d.start();
myService.signalAll_A();
}
}
···
输出:
这里两个线程都进行了await操作,然后其中一个线程执行了signalAll操作所以该线程结束了等待,但是另一组线程由于没有进行通知所以还在等待中,Condition的等待通知模式十分灵活。
可以在创建ReentrantLock的时候传入参数:true或者false。如果是true则锁定为公平锁,公平锁也就是表示线程获取锁的顺序是按照线程加锁的顺序来分配的,相反如果是false,这就是非公平锁,也就是随机获取锁的。说白了就是哪个线程先start,然后哪个线程就先拿到锁,这个就是公平锁。反之,即是哪个线程先start,它并不一定能够先拿到锁,这就是非公平锁。
公平锁/非公平锁测试:
package testThread;
import java.util.concurrent.locks.ReentrantLock;
class MyFairService {
private ReentrantLock lock ;
public MyFairService(boolean isFair) {
lock = new ReentrantLock(isFair);
}
public void serviceMethod() {
try {
lock.lock();
System.out.println("ThreadName=" + Thread.currentThread().getName()
+ " 获得锁定");
} finally {
lock.unlock();
}
}
}
public class FairLock {
public static void main(String[] args) {
final MyFairService service = new MyFairService(true); //改为false就为非公平锁了
Runnable runnable = new Runnable() {
public void run() {
System.out.println("**线程: " + Thread.currentThread().getName()
+ " 运行了 " );
service.serviceMethod();
}
};
Thread[] threadArray = new Thread[10];
for (int i=0; i<10; i++) {
threadArray[i] = new Thread(runnable);
}
for (int i=0; i<10; i++) {
threadArray[i].start();
}
}
}
运行结果:
线程的运行是随机的,但是线程获得锁的顺序是与运行顺序一致的。
如果修改为非公平锁,也就是ReentrantLock参数传入false。
运行结果为:
此时线程的运行顺序和获得锁的顺序是不一致的。
如果不传入参数,默认为非公平锁。可以使用isFair()方法测试该锁定是否为公平锁。
Lock的newCondition方法上述已经介绍。
还有一些其他的方法:
int getHoldCount():查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
int getQueueLength():返回正在等待获取此锁定的线程的估计数,比如有5个线程,1个线程首先调用lock()方法,那么在调用getQueueLength()方法返回值就是4,说明4个线程正在等待lock 的释放。
int getWaitQueueLength(Condition condition):返回等待与此锁定相关的给定条件Condition的线程估计数,比如有5个线程,每个线程都执行了同一个Condition的await方法,那么调用getWaitQueueLength之后返回的值就是5.
boolean hasQueuedThread(Thread thread):查询指定线程是否正在等待获取此锁定。
boolean hasQueuedThreads():查询是否有线程正在等待获取此锁定。
boolean hasWaiters(Condition condition):查询是否有线程正在等待与此锁定有关的Condition条件,比如有5个线程调用了Condition的await方法,那么调用该方法也就是反悔了true。
boolean isHeodByCurrentThread():查询当前线程是否保持此锁定。lock()方法调用之前是false,调用之后是true。
boolean isLocked():是否此锁定有任意线程保持。
void lockInterruptibly():如果当前线程未被中断,则获取此锁定,如果已经被中断则抛出异常。
boolean tryLock():仅在调用时锁定未被另一个线程保持的情况下,才会获取该锁定。也就是说如果有线程调用了lock方法,并且还没有释放,那么该方法返回false。否则如果释放了则返回true。
boolean tryLock(long timeout,TimtUnit unit):在给定的时间内,该锁定没有被另一个线程保持,并且当线程未被中断,则获取该锁定。
awaitUntil(Date date):Condition调用该方法后,到达传入的时间后自动唤醒,其中如果有其他唤醒操作那么立即唤醒。