• Lock锁的使用


       在Java多线程中,可以使用synchronized关键字实现线程之间的同步互斥,在jdk1.5后新增的ReentrantLock类同样可达到此效果,且在使用上比synchronized更加灵活。

    观察ReentrantLock类可以发现其实现了Lock接口

    public class ReentrantLock implements Lock,java.io.Serializable

    1、使用ReentrantLock实现同步

       lock()方法:上锁

       unlock()方法:释放锁

    1. /*
    2. * 使用ReentrantLock类实现同步
    3. * */
    4. class MyReenrantLock implements Runnable{
    5. //向上转型
    6. private Lock lock = new ReentrantLock();
    7. public void run() {
    8. //上锁
    9. lock.lock();
    10. for(int i = 0; i < 5; i++) {
    11. System.out.println("当前线程名: "+ Thread.currentThread().getName()+" ,i = "+i);
    12. }
    13. //释放锁
    14. lock.unlock();
    15. }
    16. }
    17. public class MyLock {
    18. public static void main(String[] args) {
    19. MyReenrantLock myReenrantLock = new MyReenrantLock();
    20. Thread thread1 = new Thread(myReenrantLock);
    21. Thread thread2 = new Thread(myReenrantLock);
    22. Thread thread3 = new Thread(myReenrantLock);
    23. thread1.start();
    24. thread2.start();
    25. thread3.start();
    26. }
    27. }

                           

    由此我们可以看出,只有当当前线程打印完毕后,其他的线程才可继续打印,线程打印的数据是分组打印,因为当前线程持有锁,但线程之间的打印顺序是随机的。

    即调用lock.lock()代码的线程就持有了“对象监视器”,其他线程只有等待锁被释放再次争抢。

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

    synchronized关键字结合wait()notify()notifyAll()方法的使用可以实现线程的等待与通知模式。在使用notify()notifyAll()方法进行通知时,被通知的线程是JVM随机选择的。

    ReentrantLock类同样可以实现该功能,需要借助Condition对象,可实现“选择性通知”。Condition类是jdk1.5提供的,且在一个Lock对象中可以创建多个Condition(对象监视器)实例。

    Condition类的await():是当前执行任务的线程处于等待状态

    1. /*
    2. * 错误的使用Condition实现等待、通知
    3. * */
    4. class MyCondition implements Runnable{
    5. private Lock lock = new ReentrantLock();
    6. public Condition condition = lock.newCondition();
    7. public void run() {
    8. try {
    9. System.out.println("当前线程名:"+Thread.currentThread().getName()+" 开始等待时间:"+System.currentTimeMillis());
    10. //线程等待
    11. condition.await();
    12. System.out.println("我陷入了等待...");
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. }
    17. }
    18. public class MyLock{
    19. public static void main(String[] args) {
    20. MyCondition myCondition = new MyCondition();
    21. Thread thread1 = new Thread(myCondition,"线程1");
    22. thread1.start();
    23. }
    24. }

    观察运行结果可以发现,报出监视器出错的异常,解决的办法是我们必须在condition.await()方法调用前用lock.lock()代码获得同步监视器。对上述代码做出如下修改:

    1. /*
    2. * 使用Condition实现等待
    3. * */
    4. class MyCondition implements Runnable{
    5. private Lock lock = new ReentrantLock();
    6. public Condition condition = lock.newCondition();
    7. public void run() {
    8. try {
    9. //上锁
    10. lock.lock();
    11. System.out.println("当前线程名:"+Thread.currentThread().getName()+" 开始等待时间:"+System.currentTimeMillis());
    12. //线程等待
    13. condition.await();
    14. System.out.println("我陷入了等待...");
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }finally {
    18. //释放锁
    19. lock.unlock();
    20. System.out.println("锁释放了!");
    21. }
    22. }
    23. }
    24. public class MyLock{
    25. public static void main(String[] args) {
    26. MyCondition myCondition = new MyCondition();
    27. Thread thread1 = new Thread(myCondition,"线程1");
    28. thread1.start();
    29. }
    30. }

          

    在控制台只打印出一句,原因是调用了Condition对象的await()方法,是的当前执行任务的线程进入等待状态。

    Condition类的signal():是当前执行任务的线程处于等待状态

    1. /*
    2. * 使用Condition实现等待、通知
    3. * */
    4. class MyCondition implements Runnable{
    5. private Lock lock = new ReentrantLock();
    6. public Condition condition = lock.newCondition();
    7. public void run() {
    8. try {
    9. //上锁
    10. lock.lock();
    11. System.out.println(" 开始等待时间:"+System.currentTimeMillis());
    12. System.out.println("我陷入了等待...");
    13. //线程等待
    14. condition.await();
    15. //释放锁
    16. lock.unlock();
    17. System.out.println("锁释放了!");
    18. } catch (InterruptedException e) {
    19. e.printStackTrace();
    20. }
    21. }
    22. //通知方法
    23. public void signal(){
    24. try {
    25. lock.lock();
    26. System.out.println("结束等待时间:"+System.currentTimeMillis());
    27. //通知等待线程
    28. condition.signal();
    29. } finally {
    30. lock.unlock();
    31. }
    32. }
    33. }
    34. public class MyLock{
    35. public static void main(String[] args) throws InterruptedException {
    36. MyCondition myCondition = new MyCondition();
    37. Thread thread1 = new Thread(myCondition,"线程1");
    38. thread1.start();
    39. Thread.sleep(3000);
    40. myCondition.signal();
    41. }
    42. }

                

    观察结果我们成功地实现了等待通知。

    可以得知:Object类中的wait()方法等同于Condition类中的await()方法。

                    Object类中的wait(long timeout)方法等同于Condition类中的await(long time,TimeUnit unit)方法。

                    Object类中的notify()方法等同于Condition类中的singal()方法。

                   Object类中的notifyAll()方法等同于Condition类中的singalAll()方法。

       

    3、生产者消费者模式

    1. /*
    2. * 生产者、消费者模式
    3. * 一对一交替打印
    4. * */
    5. class MyServer{
    6. private ReentrantLock lock = new ReentrantLock();
    7. public Condition condition = lock.newCondition();
    8. public Boolean flag = false;
    9. public void set() {
    10. try {
    11. lock.lock();
    12. while(flag == true) {
    13. condition.await();
    14. }
    15. System.out.println("当前线程名:"+Thread.currentThread().getName()+" hello");
    16. flag = true;
    17. condition.signal();
    18. } catch (InterruptedException e) {
    19. e.printStackTrace();
    20. }finally {
    21. lock.unlock();
    22. }
    23. }
    24. public void get() {
    25. try {
    26. lock.lock();
    27. while(flag == false) {
    28. condition.await();
    29. }
    30. System.out.println("当前线程名:"+Thread.currentThread().getName()+" lemon");
    31. flag = false;
    32. condition.signal();
    33. } catch (InterruptedException e) {
    34. e.printStackTrace();
    35. }finally {
    36. lock.unlock();
    37. }
    38. }
    39. }
    40. class MyCondition1 extends Thread{
    41. private MyServer myServer;
    42. public MyCondition1(MyServer myServer) {
    43. super();
    44. this.myServer = myServer;
    45. }
    46. public void run() {
    47. for(int i = 0 ;i < Integer.MAX_VALUE;i++) {
    48. myServer.set();
    49. }
    50. }
    51. }
    52. class MyCondition2 extends Thread{
    53. private MyServer myServer;
    54. public MyCondition2(MyServer myServer) {
    55. super();
    56. this.myServer = myServer;
    57. }
    58. public void run() {
    59. for(int i = 0 ;i < Integer.MAX_VALUE;i++) {
    60. myServer.get();
    61. }
    62. }
    63. }
    64. public class MyLock{
    65. public static void main(String[] args) throws InterruptedException {
    66. MyServer myServer = new MyServer();
    67. MyCondition1 myCondition1 = new MyCondition1(myServer);
    68. MyCondition2 myCondition2 = new MyCondition2(myServer);
    69. myCondition1.start();
    70. myCondition2.start();
    71. }
    72. }

                      

    1. /*
    2. * 生产者、消费者模式
    3. * 多对多交替打印
    4. * */
    5. class MyServer{
    6. private ReentrantLock lock = new ReentrantLock();
    7. public Condition condition = lock.newCondition();
    8. public Boolean flag = false;
    9. public void set() {
    10. try {
    11. lock.lock();
    12. while(flag == true) {
    13. System.out.println("可能会有连续的hello进行打印");
    14. condition.await();
    15. }
    16. System.out.println("当前线程名:"+Thread.currentThread().getName()+" hello");
    17. flag = true;
    18. condition.signal();
    19. } catch (InterruptedException e) {
    20. e.printStackTrace();
    21. }finally {
    22. lock.unlock();
    23. }
    24. }
    25. public void get() {
    26. try {
    27. lock.lock();
    28. while(flag == false) {
    29. System.out.println("可能会有连续的lemon进行打印");
    30. condition.await();
    31. }
    32. System.out.println("当前线程名:"+Thread.currentThread().getName()+" lemon");
    33. flag = false;
    34. condition.signal();
    35. } catch (InterruptedException e) {
    36. e.printStackTrace();
    37. }finally {
    38. lock.unlock();
    39. }
    40. }
    41. }
    42. class MyCondition1 extends Thread{
    43. private MyServer myServer;
    44. public MyCondition1(MyServer myServer) {
    45. super();
    46. this.myServer = myServer;
    47. }
    48. public void run() {
    49. for(int i = 0 ;i < Integer.MAX_VALUE;i++) {
    50. myServer.set();
    51. }
    52. }
    53. }
    54. class MyCondition2 extends Thread{
    55. private MyServer myServer;
    56. public MyCondition2(MyServer myServer) {
    57. super();
    58. this.myServer = myServer;
    59. }
    60. public void run() {
    61. for(int i = 0 ;i < Integer.MAX_VALUE;i++) {
    62. myServer.get();
    63. }
    64. }
    65. }
    66. public class MyLock{
    67. public static void main(String[] args) throws InterruptedException {
    68. MyServer myServer = new MyServer();
    69. MyCondition1[] myCondition1 = new MyCondition1[10];
    70. MyCondition2[] myCondition2 = new MyCondition2[10];
    71. for(int i = 0; i < 10; i++) {
    72. myCondition1[i] = new MyCondition1(myServer);
    73. myCondition2[i] = new MyCondition2(myServer);
    74. myCondition1[i].start();
    75. myCondition2[i].start();
    76. }
    77. }
    78. }

                            

    4、公平锁与非公平锁

    Lock分为“公平锁”和“非公平锁”。

    公平锁:表示线程获取锁的顺序是按照线程加锁的顺序来的进行分配的,即先来先得FIFO先进先出顺序。

    非公平锁:一种获取锁的抢占机制,是随机拿到锁的,和公平锁不一样的是先来的不一定先拿到锁,这个方式可能造成某些线程一直拿不到锁,结果就是不公平的·。

    1. /*
    2. * 公平锁
    3. * */
    4. class MyService{
    5. private ReentrantLock lock;
    6. public MyService(boolean isFair) {
    7. super();
    8. lock = new ReentrantLock(isFair);
    9. }
    10. public void serviceMethod() {
    11. try {
    12. lock.lock();
    13. System.out.println("线程名:"+Thread.currentThread().getName()+"获得锁定");
    14. } finally {
    15. lock.unlock();
    16. }
    17. }
    18. }
    19. public class MyLock{
    20. public static void main(String[] args) {
    21. //设置当前为true公平锁
    22. final MyService myService = new MyService(true);
    23. Runnable runnable = new Runnable() {
    24. public void run() {
    25. System.out.println("线程名:"+Thread.currentThread().getName()+"运行了");
    26. myService.serviceMethod();
    27. }
    28. };
    29. Thread[] threads = new Thread[10];
    30. for(int i = 0;i < 10; i++) {
    31. threads[i] = new Thread(runnable);
    32. }
    33. for(int i = 0;i < 10; i++) {
    34. threads[i].start();
    35. }
    36. }
    37. }
               

    由打印结果可以看出,基本呈现有序的状态,这就是公平锁的特点。

    1. /*
    2. * 非公平锁
    3. * */
    4. class MyService{
    5. private ReentrantLock lock;
    6. public MyService(boolean isFair) {
    7. super();
    8. lock = new ReentrantLock(isFair);
    9. }
    10. public void serviceMethod() {
    11. try {
    12. lock.lock();
    13. System.out.println("线程名:"+Thread.currentThread().getName()+"获得锁定");
    14. } finally {
    15. lock.unlock();
    16. }
    17. }
    18. }
    19. public class MyLock{
    20. public static void main(String[] args) {
    21. //设置当前为true公平锁
    22. final MyService myService = new MyService(false);
    23. Runnable runnable = new Runnable() {
    24. public void run() {
    25. System.out.println("线程名:"+Thread.currentThread().getName()+"运行了");
    26. myService.serviceMethod();
    27. }
    28. };
    29. Thread[] threads = new Thread[10];
    30. for(int i = 0;i < 10; i++) {
    31. threads[i] = new Thread(runnable);
    32. }
    33. for(int i = 0;i < 10; i++) {
    34. threads[i].start();
    35. }
    36. }
    37. }


    非公平锁的运行结果基本都是无须的,则可以表明先start()启动的线程并不一定先获得锁。

    5、使用ReentrantReadWriteLock

    ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后的任务。这样虽然保证了实例变量的线程安全性,但是效率低下。所以在Java中提供有读写锁ReentrantReadWriteLock类,使其效率可以加快。在某些不需要操作实例变量的方法中,完全可以使用ReentrantReadWriteLock来提升该方法代码运行速度。

    读写锁表示两个锁:

    读操作相关的锁,也成为共享锁。

    写操作相关的锁,也叫排他锁。

    多个读锁之间不互斥,读锁与写锁互斥,多个写锁互斥。

    在没有线程Thread进行写入操作时,进行读操作的多个Thread可以获取读锁,但是进行写入操作时的Thread只有获取写锁后才能进行写入操作。

    1)多个读锁共享

    1. /*
    2. * 多个读锁共享
    3. * */
    4. class MyService{
    5. private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    6. public void read() {
    7. try {
    8. //读锁
    9. lock.readLock().lock();
    10. System.out.println("线程名: "+Thread.currentThread().getName()+"获取读锁" );
    11. Thread.sleep(1000);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }finally {
    15. //释放读锁
    16. lock.readLock().unlock();
    17. }
    18. }
    19. }
    20. //线程1
    21. class Thread1 extends Thread{
    22. private MyService myService;
    23. public Thread1(MyService myService) {
    24. super();
    25. this.myService = myService;
    26. }
    27. public void run() {
    28. myService.read();
    29. }
    30. }
    31. //线程2
    32. class Thread2 extends Thread{
    33. private MyService myService;
    34. public Thread2(MyService myService) {
    35. super();
    36. this.myService = myService;
    37. }
    38. public void run() {
    39. myService.read();
    40. }
    41. }
    42. public class MyLock{
    43. public static void main(String[] args) {
    44. MyService myService = new MyService();
    45. Thread1 thread1 = new Thread1(myService);
    46. Thread2 thread2 = new Thread2(myService);
    47. thread1.start();
    48. thread2.start();
    49. }
    50. }

                    

    从打印结果可以看出,两个线程几乎同时进入lock()方法后面的代码。

    说明在此时使用lock.readLock()读锁可以提高程序运行效率,允许多个线程同时执行lock()方法后的代码。

    2)多个写锁互斥

    1. /*
    2. * 多个写锁互斥
    3. * */
    4. class MyService{
    5. private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    6. public void write() {
    7. try {
    8. //写锁
    9. lock.writeLock().lock();
    10. System.out.println("线程名: "+Thread.currentThread().getName()+"获取写锁,获得时间:"+System.currentTimeMillis() );
    11. Thread.sleep(1000);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }finally {
    15. //释放写锁
    16. lock.writeLock().unlock();
    17. }
    18. }
    19. }
    20. //线程1
    21. class Thread1 extends Thread{
    22. private MyService myService;
    23. public Thread1(MyService myService) {
    24. super();
    25. this.myService = myService;
    26. }
    27. public void run() {
    28. myService.write();
    29. }
    30. }
    31. //线程2
    32. class Thread2 extends Thread{
    33. private MyService myService;
    34. public Thread2(MyService myService) {
    35. super();
    36. this.myService = myService;
    37. }
    38. public void run() {
    39. myService.write();
    40. }
    41. }
    42. public class MyLock{
    43. public static void main(String[] args) {
    44. MyService myService = new MyService();
    45. Thread1 thread1 = new Thread1(myService);
    46. Thread2 thread2 = new Thread2(myService);
    47. thread1.start();
    48. thread2.start();
    49. }
    50. }

                       

    使用写锁代码writeLock.lock()的效果就是同一时间只允许一个线程执行lock()方法后的代码。

    (3)读写/写读互斥

    1. /*
    2. * 读写/写读互斥,
    3. * */
    4. class MyService{
    5. private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    6. public void read() {
    7. try {
    8. //读锁
    9. lock.readLock().lock();
    10. System.out.println("线程名: "+Thread.currentThread().getName()+"获取读锁,获得时间:"+System.currentTimeMillis() );
    11. Thread.sleep(1000);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }finally {
    15. //释放读锁
    16. lock.readLock().unlock();
    17. }
    18. }
    19. public void write() {
    20. try {
    21. //写锁
    22. lock.writeLock().lock();
    23. System.out.println("线程名: "+Thread.currentThread().getName()+"获取写锁,获得时间:"+System.currentTimeMillis() );
    24. Thread.sleep(1000);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. }finally {
    28. //释放写锁
    29. lock.writeLock().unlock();
    30. }
    31. }
    32. }
    33. //线程1
    34. class Thread1 extends Thread{
    35. private MyService myService;
    36. public Thread1(MyService myService) {
    37. super();
    38. this.myService = myService;
    39. }
    40. public void run() {
    41. myService.read();
    42. }
    43. }
    44. //线程2
    45. class Thread2 extends Thread{
    46. private MyService myService;
    47. public Thread2(MyService myService) {
    48. super();
    49. this.myService = myService;
    50. }
    51. public void run() {
    52. myService.write();
    53. }
    54. }
    55. public class MyLock{
    56. public static void main(String[] args) {
    57. MyService myService = new MyService();
    58. Thread1 thread1 = new Thread1(myService);
    59. Thread2 thread2 = new Thread2(myService);
    60. thread1.start();
    61. thread2.start();
    62. }
    63. }

                     

    此运行结果说明“读写/写读”操作是互斥的。

    由此可表明:只要出现“写”操作,就是互斥的。




  • 相关阅读:
    远程调用之RMI、Hessian、Burlap、Httpinvoker、WebService的比较
    遍历List/Map的时候删除成员遇到的奇怪问题
    Java事务处理
    ThreadLocal学习记录
    IntelliJ IDEA+Tomcat+Nginx运行git项目
    JavaIO和JavaNIO
    Spring MVC的启动过程
    Java中的集合类
    Java中的泛型
    Java 多线程的基本概念
  • 原文地址:https://www.cnblogs.com/edda/p/12607832.html
Copyright © 2020-2023  润新知