一、Lock接口
在java多线程中,可以使用synchronized关键字来实现线程之间同步互斥、但在JDK1.5之后并发包中新增加了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显示地获取和释放锁,虽然它缺少了隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取或及超时获取锁等多种synchronized关键字锁不具备的同步特性。
synchronized关键字会隐式获取锁,但是它把锁的获取和释放固化了,也就是先获取再使用。不可否认的是这种方式简化了同步的管理,可以扩展性没有显示的锁获取和释放来的好。
1、Lock接口提供的synchronized关键字不具备的主要特性
2、Lock的API
二、使用ReentrantLock实现同步
public class MyService {
private Lock lock=new ReentrantLock();
public void testMethod(){
//调用ReentrantLock对象的lock方法获取锁,调用unlock方法释放锁
lock.lock();
for (int i = 0; i < 5; i++) {
System.out.println("ThreadName="+Thread.currentThread().getName()+(" "+(i+1)));
}
lock.unlock();
}
}
public class MyThread extends Thread {
private MyService service;
public MyThread(MyService service){
super();
this.service=service;
}
@Override
public void run(){
service.testMethod();
}
}
public class Run {
public static void main(String[] args) {
MyService service=new MyService();
MyThread a1=new MyThread(service);
a1.setName("a1");
MyThread a2=new MyThread(service);
a2.setName("a2");
MyThread a3=new MyThread(service);
a3.setName("a3");
MyThread a4=new MyThread(service);
a4.setName("a4");
MyThread a5=new MyThread(service);
a5.setName("a5");
a1.start();
a2.start();
a3.start();
a4.start();
a5.start();
}
}
从运行的结果来看,当前线程打印完毕之后将锁进行释放,其他线程才可以继续打印。线程打印的数据是分组打印,因为当前线程已经持有锁,但线程之间的顺序是随机的。
三、使用Condition实现等待/通知
关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式。类ReentrantLock也可以实现同样的功能。但需要借助于Condition对象。Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(对象监视器)实例,线程对象可以注册在指定的condtion中,从而有选择进行线程通知,线程调度上更灵活。
在使用notify方法进行通知时,被通知的线程是JVM随机选择的。但ReentrantLock+Condition可以实现“选择性通知”。
synchronized就相当于整个Lock对象中只有一个单一的Conditon对象,所有线程都注册在它一个对象的身上。线程开始notifyAll时,需要通知所有Wait状态的线程,没有选择权。
1、使用Condition实现等待/通知错误用法与解决
public class MyService {
private ReentrantLock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
public void waitMethod(){
try {
//lock.lock();
System.out.println("A");
condition.await();
System.out.println("B");
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
System.out.println("锁被释放了");
}
}
}
public class MyThreadA extends Thread {
private MyService myService;
public MyThreadA(MyService myService){
this.myService=myService;
}
@Override
public void run(){
myService.waitMethod();
}
}
public class Run {
public static void main(String[] args) {
MyService myService =new MyService();
MyThreadA a=new MyThreadA(myService);
a.start();
//在控制台只打印一个字母A,原因是调用了Condition对象的await方法,使当前执行任务的线程进入等待WAITING状态
}
}
报错的异常信息是监视器出错,解决的办法是必须在condition.await方法之前调用lock.lock()代码获得同步监视器。将
lock.lock();的代码解注。最终在控制台中只打印了一个字母A,原因是调用了Condition对象的await方法。使当前线程进入了wait状态。
2、正确使用Condition实现等待/通知
public class MyService {
private Lock lock=new ReentrantLock();
public Condition condition=lock.newCondition();
public void await(){
try {
lock.lock();
System.out.println(" await时间为 "+System.currentTimeMillis());
condition.await();
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void signal(){
try {
lock.lock();
System.out.println("signal时间为"+System.currentTimeMillis());
condition.signal();
}finally{
lock.unlock();
}
}
}
public class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service){
super();
this.service=service;
}
@Override
public void run(){
service.await();
}
public static void main(String[] args) throws InterruptedException {
MyService service=new MyService();
ThreadA a=new ThreadA(service);
a.start();
Thread.sleep(3000);
service.signal();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyService service=new MyService();
ThreadA a = new ThreadA(service);
a.start();
Thread.sleep(3000);
service.signal();
}
}
程序运行结果如下图所示,成功实现了等待/通知模式.
① Object类中的wait方法相当于Condition类中的await方法.
② Object类中的wait(long timeout)方法相当于Condition类中的await(long time,TimeUnit unit)
③ Object类中的notify方法相当于Condition类中的signal方法.
④ Object类中的notifyAll方法相当于Condition类中的signalAll方法
3、使用多个Condition实现通知部分线程:
MyService:
public class MyService {
private Lock lock=new ReentrantLock();
//两个condition对象
public Condition conditionA=lock.newCondition();
public Condition conditionB=lock.newCondition();
public void awaitA(){
try {
lock.lock();
System.out.println("begin awaitA时间为" +System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
conditionA.await();
System.out.println(" end awaitA时间为" +System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void awaitB(){
try {
lock.lock();
System.out.println("begin awaitB时间为" +System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
conditionB.await();
System.out.println(" end awaitB时间为" +System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void signalAll_A(){
try {
lock.lock();
System.out.println(" signalAll_A 时间为"+System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
conditionA.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void signalAll_B(){
try {
lock.lock();
System.out.println(" signalAll_B 时间为"+System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
conditionA.signalAll();
} catch (Exception e) {
lock.unlock();
}finally{
lock.unlock();
}
}
}
public class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service){
super();
this.service=service;
}
@Override
public void run(){
service.awaitA();
}
}
public class ThreadB extends Thread {
private MyService service;
public ThreadB(MyService service){
super();
this.service=service;
}
@Override
public void run(){
service.awaitB();
}
}
测试类:
public class Run {
public static void main(String[] args) throws InterruptedException {
MyService service=new MyService();
ThreadA a=new ThreadA(service);
a.setName("A");
a.start();
ThreadB b=new ThreadB(service);
b.setName("B");
b.start();
ThreadA.sleep(3000);
service.signalAll_A();
//通过此实验可以得知,使用ReentrantLock对象可以唤醒指定种类的线程,这是控制部分线程行为的方便方式
}
}
程序运行后,只有线程A被唤醒了,这说明使用ReentrantLock+condition对象可以唤醒指定部分的线程,可以先对线程分组,然后再唤醒指定组中的线程。
四、公平锁与非公平锁
锁Lock分为"公平锁 "、“非公平锁”,公平锁表示线程获得锁的顺序是按照线程加锁的顺序来分配的,即FIFO先进先出顺序。而非公平锁就是一种获取锁的抢战机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。
public class Service {
private ReentrantLock lock;
public Service(boolean isFair){
super();
lock=new ReentrantLock(isFair);
}
public void serviceMethod(){
try {
lock.lock();
System.out.println("ThreadName= "+Thread.currentThread().getName()+"获得锁定");
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
公平锁:
public class RunFair {
/**
* 打印的结果是基本呈有序的,这就是公平锁的特点
* @param args
*/
public static void main(String[] args) {
final Service service=new Service(true);
Runnable runnable=new Runnable() {
@Override
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();
}
}
}
非公平锁:
public static void main(String[] args) {
/**
* 非公平锁的运行结果基本上是乱序的,说明先start启动的线程不代表先获得锁
*/
final Service service=new Service(false);
Runnable runnable=new Runnable() {
@Override
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();
}
}
}