美文网首页
多线程高并发编程之ReentrantLock

多线程高并发编程之ReentrantLock

作者: Minority | 来源:发表于2020-03-05 15:37 被阅读0次

以下代码均通过使线程睡眠模拟实际业务场景来解释原理

Case1(reentrantlock替代synchronized):

package ReentrantLock;
import java.util.concurrent.TimeUnit;

class ReentrantLock1 {
    synchronized void m1(){
        for (int i = 0; i < 10; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
    }

    synchronized void m2(){
        System.out.println("m2.....");
    }

    public static void main(String[] args) {
        ReentrantLock1 r1=new ReentrantLock1();
        new Thread(r1::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r1::m2).start();
    }
}
/*====================output========================
0
1
2
3
4
5
6
7
8
9
m2.....
*/

使用synchronized 编程时,写法如上,上例中由于m1锁定this,只有m2执行完毕的时候,m2才能执行。下面使用ReentrantLock来替代synchronized :

package ReentrantLock;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ReentrantLock2 {

    //Lock是ReentrantLock实现的接口
    Lock lock=new ReentrantLock();

    void m1(){
       
        lock.lock();    //相当于synchronized(this)
        try {
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {                          //finally一定得写,用来手工释放锁
            lock.unlock();
        }
    }

    void m2(){
        lock.lock();
        try {
            System.out.println("m2....");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLock2 r1=new ReentrantLock2();
        //映射方式start
        //new Thread(r1::m1).start();

        /*lambda表达式写法
        new Thread(()->{
            r1.m1();
        },"t1").start();
        */
        
        //普通写法
        new Thread(new Runnable(){

            @Override
            public void run() {
                r1.m1();
            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r1::m2).start();
    }
}

Lock是ReentrantLock实现的接口,本例的结果和上面使用synchronized 得到的结果相同。reentrantlock是可以用于替代synchronized的锁(手动上锁,手动释放)

知识点:

  • 需要注意的是,必须要手动释放锁(非常重要,ReentrantLock是手工锁
  • 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放

Case2(ReentrantLock之trylock):

package ReentrantLock

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ReentrantLock3 {
    Lock lock=new ReentrantLock();

    void m1(){

        lock.lock();    //相当于synchronized(this)
        try {
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


    void m2(){


        /*第一种方法:lock.tryLock(),若拿着锁返回true,否则返回false
        *
        boolean locked=lock.tryLock();
        System.out.println("m2..."+locked);
        if (locked) lock.unlock();*/

        boolean locked=false;
        try {
            //第二种用法:尝试等n秒,如果还是没有拿到锁,则该干嘛干嘛
            locked=lock.tryLock(5,TimeUnit.SECONDS);
            System.out.println("m2...."+locked);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (locked) lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLock3 r1=new ReentrantLock3();
        new Thread(r1::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r1::m2).start();
    }
}

使用reentrantlock可以进行“尝试锁定”trylock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。所以,reentrantlock比synchronized更加灵活,其能进行等待获取锁。在效率上,两者没有什么区别啦(对synchronized进行底层优化后,以前的synchronized锁的粒度是比较重的),而且,还能指定reentrantlock锁的公平性

知识点:

  • 使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行
  • reentrantlock有两种使用方式:可以根据trylock的返回值来判断是否锁定,也可以指定trylock的时间。lock.tryLock()可以根据对返回的布尔值的判断来继续执行,而不会抛出异常。但指定时间时,由于trylock(time)会抛出异常,所以要注意unlock的处理,必须方法finally中
    1.第一种方法:lock.tryLock(),若拿着锁返回true,否则返回false
        boolean locked=lock.tryLock();
        System.out.println("m2..."+locked);
        if (locked)   lock.unlock(); 
        else   ....
    
    1. 第二种用法:尝试等n秒,如果还是没有拿到锁,则该干嘛干嘛
    boolean locked=lock.tryLock();
    try {
            locked=lock.tryLock(5,TimeUnit.SECONDS);
      } catch (InterruptedException e) {
            e.printStackTrace();
      } finally {
             if (locked) lock.unlock();
      }
    

Case3(ReentrantLock之lockInterruptibly):

package ReentrantLock;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLock4 {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();

        Thread t1=new Thread(()->{
            lock.lock();
            try {
                System.out.println("t1 start");
                //t1一直运行,其他等待lock的线程永远等待
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                System.out.println("t1 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        t1.start();

        Thread t2=new Thread(()->{

            try {
                //如果使用lock.lock();则t2会永远等待,并不能打断t2的执行
                //lock.lock();

                //lock.lockInterruptibly();可以在等不到资源的情况下把该线程(t2)打断,不再执行
                //注意:t2是不可能打断t1的,没这个权利。所谓的打断就是结束等待线程
                lock.lockInterruptibly(); //可以对interrupt()做出响应
                System.out.println("t2 start");
                TimeUnit.SECONDS.sleep(5);
                System.out.println("t2 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        });

        t2.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t2.interrupt();//打断线程2的等待
    }
}
/*====================output========================
t1 start
t2结束等待
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
    at ReentrantLock.ReentrantLock4.lambda$main$1(ReentrantLock4.java:40)
    at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
    at ReentrantLock.ReentrantLock4.lambda$main$1(ReentrantLock4.java:48)
    at java.lang.Thread.run(Thread.java:745)
*/

使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断。

上面的例子中,首先new出一个ReentrantLock,t1获得lock锁时,进行永久睡眠。紧接着t2通过lock.lockInterruptibly();尝试着获得锁,由于锁被t1占着,所以t2获得锁失败,就一直等待。这时,调用t2.interrupt();就可以直接把t2线程打断,t2不在等待,而是直接结束线程。

注意:

  • lock.lockInterruptibly();可以在等不到资源的情况下把该线程(t2)打断,不再执行
  • 注意:t2是不可能打断t1的,没这个权利。所谓的打断就是结束等待线程

Case4(ReentrantLock之公平锁):

package ReentrantLock;

import java.util.concurrent.locks.ReentrantLock;


//本身是从Thread继承的,所以不用再实现runnable接口
public class ReentrantLock5 extends Thread {

    //公平锁t1和t2交换执行
    private static ReentrantLock lock=new ReentrantLock(true);//参数为true表示为公平锁,默认是非公平的锁。请对比输出结果


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"获得锁");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        ReentrantLock5 r1=new ReentrantLock5();
        Thread th1=new Thread(r1);
        Thread th2=new Thread(r1);
        th1.start();
        th2.start();
    }
}
/*====================output========================
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
。。。。。。
*/

默认的synchronized是非公平锁,并不关心谁等的时间长,由线程调度器来进行调度,ReentrantLock还可以指定公平锁。就是谁等的时间长就让谁来获得这把锁。

使用private static ReentrantLock lock=new ReentrantLock(true);创建ReentrantLock对象时,参数为true表示为公平锁,默认是非公平的锁。从执行的结果可以看出,t1和t2是交替执行的,如果把公平锁的参数设为false,那么结果将是其中一个线程执行完,另一个再执行。



参考:马士兵老师java多线程高并发编程

相关文章

  • 多线程高并发编程之ReentrantLock

    以下代码均通过使线程睡眠模拟实际业务场景来解释原理 Case1(reentrantlock替代synchroniz...

  • GCD的使用,同步和异步,串行和并行

    多线程并发(同时)执行,其实就是CPU快速地在多线程之间的快速调度,就会造成多线程并发执行的假象;多线程下,不要相...

  • 4.Lock的使用

    1.ReentrantLock 在Java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥...

  • java多线程高级用法

    ReentrantLock类的使用 在java多线程中,可以使用synchronized关键字来实现线程之间的同步...

  • Java并发 | ReentrantLock类

    ReentrantLock可重入锁 在Java多线程中,可以使用synchronized关键字来实现线程之间同步互...

  • java多线程高并发

    “高并发和多线程”总是被一起提起,给人感觉两者好像相等,实则高并发 ≠ 多线程 多线程是完成任务的一种方法,高并发...

  • 什么是高并发与多线程?

    高并发和多线程”总是被一起提起,给人感觉两者好像相等,实则高并发 ≠ 多线程 多线程是完成任务的一种方法,高并发是...

  • 带你搞懂Java多线程(一)

    什么是多线程 多线程也叫并发编程,那么在写多线程之前,我们先来了解一下并发编程的基础概念。①CPU核心数和线程数的...

  • 高并发(6)- 多线程之间的协作

    @[TOC](高并发(6)- 多线程之间的协作) 前言 线程的协作 一、什么是线程的协作顾名思义,线程的协作就是多...

  • ReentrantLock源码通读(二)

    介绍 上一篇,主要是介绍了多线程之间请求锁的流程。而ReentrantLock还提供了Condition实现,拓展...

网友评论

      本文标题:多线程高并发编程之ReentrantLock

      本文链接:https://www.haomeiwen.com/subject/zexfrhtx.html