Java多线程编程六(使用Lock)


本文主要讲述如何解决非线程安全问题,感谢java多线程核心编程一书,为本系列文章提供参考借鉴

一、使用ReentrantLock
1.使用ReentrantLock方法:
  • lock():锁定
  • unlock():解除锁定
  • int getHoldCount():产讯当前线程保持此锁定的个数,也就是调用lock()方法的次数
  • int getQueueLength():返回正在等待获取此锁定的线程估计数,例如:8个线程,3个线程调用了await()方法吧,那么在调用此方法后返回的值是5,那么就说嘛有5个线程同时在等待lock的释放。
  • int getWaitQueueLength():顾名思义,其作用是返回等待与此锁定相关的给定条件Condition的线程估计数。例如:8个线程,每个线程都调用了同一个Condition的await()方法,则调用此方法返回的值是8.
  • boolean hasQueuedThread():查询指定线程是否正在等待获取此锁定。ReentrantLock.hasQueuedThread(thread);
  • boolean hasWaiters():查询是否有线程正在等待与此锁定有关的Condition条件。
  • boolean isFair():判断是否是公平锁
  • boolean isHeldByCurrentThread():查询当前线程是否保持此锁定。
  • boolean isLocked():查询此锁定是否由任意线程保持。
  • void lockInterruptibly():如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。
  • boolean tryLock():仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。
  • boolean tryLock(long timeout,TimeUnit unit):如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。
2.使用ReentrantLock实现同步效果:
public class MyServer {
    private ReentrantLock lock = new ReentrantLock();
    public void printReentrantLock() {
        try {
            lock.lock();//获取锁
            for (int i = 0; i < 5; i++) {
                System.out.println("ThreadName=" + Thread.currentThread().getName() + ",i=" + i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }
	public static void main(String[] args) {
        final MyServer myServer = new MyServer();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    myServer.printReentrantLock();
                }
            });
            thread.setName(String.valueOf(i));
            thread.start();
        }
    }
}

运行结果为:
在这里插入图片描述
由结果分析知:当前线程获取锁对象后其他线程呈阻塞状态,直到当前线程打印完毕释放锁,其他线程才能获取到锁进行打印。

这个主要进行单个方法同一个锁多线程调用的同步效果,下面来看一下多个线程交叉调用不同方法同一个锁的例子:

public class MyServer {

    private ReentrantLock lock = new ReentrantLock();

    public void methodA() {
        try {
            lock.lock();//获取锁
            System.out.println("method A begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
            methodB();//调用methodB
            Thread.sleep(5000);
            System.out.println("method A end   ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void methodB() {
        try {
            lock.lock();//获取锁
            System.out.println("method B begin ThreadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("method B end   ThreadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
    public static void main(String[] args) {
        String[] names = {"A", "AA", "B", "BB"};
        final MyServer myServer = new MyServer();
        Thread[] threads = new Thread[4];
        for (int i = 0; i < 2; i++) {
            threads[i] = new Thread(new Runnable() {
                public void run() {
                    myServer.methodA();
                }
            });
        }
        for (int i = 2; i < 4; i++) {
            threads[i] = new Thread(new Runnable() {
                public void run() {
                    myServer.methodB();
                }
            });
        }
        for (int i = 0; i < 4; i++) {
            threads[i].setName(names[i]);
            threads[i].start();
        }

    }

结果如下:
在这里插入图片描述
分析结果:在线程A获取锁时其他线程呈阻塞状态直至线程A执行完毕且释放锁,其他线程才可以获取到锁,同时在线程A获取锁执行过程中又成功调用了methodB,这些说明reentrantlock和synchronized关键字一样线程之间是同步顺序执行的,也都具有锁重入特性。

二、使用Condition实现等待/通知

关键字synchronized于wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以试想同样的功能,但需要借助于Condition对象。相对于notify和notifyAll,Condition具有跟高的灵活性,在使用notify和notifyAll时,被通知的线程是由JVM随机选择的,而ReentrantLock结合Condition类是可以“选择性通知的”。

1.Condition中通知/等待方法
  • await():相当于Object类中的wait()方法
  • await(long time,TimeUtil unit):相当于Object类中的wait(long timeout)方法
  • signal():相当于Object类中的notify()方法
  • signalAll():相当于Object类中的notifyAll()方法
2.简单使用:

注意:调用condition.await()之前需要执行lock.lock()来获取同步监视器。

public class MyServer {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void await() {
        try {
            lock.lock();
            System.out.println(" await 时间为 "+ System.currentTimeMillis());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void signal() {
        try {
            lock.lock();
            System.out.println(" signal 时间为 "+System.currentTimeMillis());
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
        public static void main(String[] args) throws InterruptedException {
        final MyServer myServer = new MyServer();
        new Thread(new Runnable() {
            public void run() {
                myServer.await();
            }
        }).start();
        Thread.sleep(3000);
        myServer.signal();
    }

}

运行结果为:
在这里插入图片描述

3.使用多个Condition实现”选择性“通知线程

代码如下:

public class MyServer {
    private ReentrantLock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    @SuppressWarnings("all")
    public void awaitA() {
        try {
            lock.lock();
            System.out.println("begin await A 时间为 "+ System.currentTimeMillis());
            conditionA.await();
            System.out.println("end   await A 时间为 "+ System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    @SuppressWarnings("all")
    public void awaitB() {
        try {
            lock.lock();
            System.out.println("begin await B 时间为 "+ System.currentTimeMillis());
            conditionB.await();
            System.out.println("end   await B 时间为 "+ System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    @SuppressWarnings("all")
    public void signalAll_A() {
        try {
            lock.lock();
            System.out.println(" signalall B 时间为 "+System.currentTimeMillis());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }
    @SuppressWarnings("all")
    public void signalAll_B() {
        try {
            lock.lock();
            System.out.println(" signalall B 时间为 "+System.currentTimeMillis());
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final MyServer myServer = new MyServer();
        Thread threadA = new Thread(new Runnable() {
            public void run() {

                myServer.awaitA();
            }
        });
        threadA.setName("A");
        threadA.start();
        Thread threadB = new Thread(new Runnable() {
            public void run() {
                myServer.awaitB();
            }
        });
        threadB.setName("B");
        threadB.start();
        Thread.sleep(3000);
        myServer.signalAll_B();
    }
}

在上述代码中我们定义了两个Condition,分别用于两个线程的等待和唤醒,而我们只唤醒线程B而线程A则继续等待。结果如下:
在这里插入图片描述

三、公平锁与非公平锁

公平锁就是线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出的顺序。非公平锁就是随机获取锁的,属于一种枪战机制。先来不一定先得到,谁抢到属于谁,所以是不公平的。
通过构造方法ReetrantLock(boolean isFair)来创建对应公平锁或不公平锁。无参默认为非公平锁。

四、使用ReentrantReadWriteLock类

使用ReetrantLock具有完全互斥排他的效果,即同一时间只有一个线程能够执行Reentrantlock.lock()方法后的代码。虽然这样能保持实例变量的线程安全性,但是效率确实非常低的。然而使用读写锁ReentrantReadWriteLock类,可以提高运行效率,在不需要操作实例变量的情况下,可以使用读写锁ReentrantReadWriteLock来提高任务的执行效率。
读写锁ReentrantReadWriteLock有两个锁,一个是读操作的锁即共享锁,一个是写操作锁即排它锁。在多少个读锁之间不互斥,读锁和写锁互斥,写锁和写锁互斥。在多个线程中,如果没有线程进行写操作的时,可以有多个线程同时进行读操作;而其中一个线程正在写操作时,其他线程的读操作或写操作都只能等当前的写线程执行完毕。

1.读写或写读互斥
public class MyServer {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        try {
            lock.readLock().lock();
            System.out.println("获得读锁: "+Thread.currentThread().getName()+" "+System.currentTimeMillis());
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write() {
        try {
            lock.writeLock().lock();
            System.out.println("获得写锁: "+Thread.currentThread().getName()+" "+System.currentTimeMillis());
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final MyServer myServer = new MyServer();
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                myServer.read();
            }
        });
        threadA.setName("A");
        threadA.start();
        Thread threadB = new Thread(new Runnable() {
            public void run() {
                myServer.write();
            }
        });
        threadB.setName("B");
        threadB.start();
        Thread threadC = new Thread(new Runnable() {
            public void run() {
                myServer.read();
            }
        });
        threadC.setName("C");
        threadC.start();
    }
}

结果如:在这里插入图片描述

由结果可知,读写和写读是互斥的

2.读读共享

更改上面的main方法:

    public static void main(String[] args) throws InterruptedException {
        final MyServer myServer = new MyServer();
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                myServer.read();
            }
        });
        threadA.setName("A");
        threadA.start();
        Thread threadB = new Thread(new Runnable() {
            public void run() {
                myServer.read();
            }
        });
        threadB.setName("B");
        threadB.start();
    }

结果如:
在这里插入图片描述

由结果可知:读和读共享

3.写写互斥

更改上面的main方法:

    public static void main(String[] args) throws InterruptedException {
        final MyServer myServer = new MyServer();
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                myServer.write();
            }
        });
        threadA.setName("A");
        threadA.start();
        Thread threadB = new Thread(new Runnable() {
            public void run() {
                myServer.write();
            }
        });
        threadB.setName("B");
        threadB.start();
    }

结果如:
在这里插入图片描述

由结果知:写写互斥

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值