synchronized、Lock接口、Condition接口、读写锁及ReentrantLock(重入锁) 特性及使用

1. synchronized

synchronized是为了实现线程同步而存在的

在java中,实现线程同步可以使用 synchronized关键字,用于在方法前修饰,添加了synchronized关键字,线程在执行的时候就不会被其他线程抢夺

这是因为每个Java 对象都有一个内置锁,内置锁会保护使用 synchronized 关键字修饰的方法,线程要调用该方法就必须先获得锁,否则就处于阻塞状态

(1) synchronized的3种使用方式

① 修饰实例方法,给调用方法的对象实例加锁

② 修饰静态方法,相当于是给类加锁

③ 修饰代码块,需要指定加锁对象,对给定对象加锁

① 修饰实例方法

class Visitor{
    public synchronized void visit(){ // 修饰实例方法
        System.out.println(Thread.currentThread().getName()+"正在访问,将持续1秒");
        try {
            TimeUnit.SECONDS.sleep(1);  //线程休眠1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1秒结束了,"+Thread.currentThread().getName()+"执行结束");
    }
}
public class TestLock {
    public static void main(String[] args) {
        
        Visitor visitor = new Visitor(); //生成一个Visitor实例

        new Thread(()->{
            visitor.visit();
        }, "线程1").start();

        new Thread(()->{
            visitor.visit();
        },"线程2").start();
    }
}

在这里插入图片描述
注意,上面只存在一个visitor实例,如果是下面这种写法,synchronized就不会起作用

public class TestLock {
    public static void main(String[] args) {

        Visitor visitor = new Visitor(); //生成一个Visitor实例
        Visitor visitor1 = new Visitor(); //生成第二个Visitor实例

        new Thread(()->{
            visitor.visit();
        }, "线程1").start();

        new Thread(()->{
            visitor1.visit();
        },"线程2").start();
    }
}

在这里插入图片描述

② 修饰静态方法

class Visitor{
    public static synchronized void staticVisit(){
        System.out.println(Thread.currentThread().getName()+"正在访问,将持续1秒");
        try {
            TimeUnit.SECONDS.sleep(1);  //线程休眠1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1秒结束了,"+Thread.currentThread().getName()+"执行结束");
    }
}
public class TestLock {
    public static void main(String[] args) {
        new Thread(()->{
            Visitor.staticVisit();
        }, "线程1").start();

        new Thread(()->{
            Visitor.staticVisit();
        },"线程2").start();
    }
}

在这里插入图片描述
下面这种写法,和①不同,调用两个不同实例对象的方法synchronized也会起作用,这就是因为静态方法是给类加锁,而至于多少个实例对象,也只有一个Class

public class TestLock {
    public static void main(String[] args) {

        Visitor visitor = new Visitor();
        Visitor visitor1 = new Visitor();

        new Thread(()->{
            visitor.staticVisit();
        }, "线程1").start();

        new Thread(()->{
            visitor1.staticVisit();
        },"线程2").start();
    }
}

③ 修饰代码块

class Visitor{
    public void testVisit(Visitor visitor){

        if(visitor!=null){  // 如果传入了Visitor的实例化对象
            synchronized (visitor){ // 给实例化对象上锁
                System.out.println("此时synchronized锁定的是类的实例化对象");
                System.out.println(Thread.currentThread().getName()+"正在执行");
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName()+"正在休眠1秒种");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行结束");
                System.out.println("======");
            }
        }
        else {
            synchronized (Visitor.class){ //给类上锁
                System.out.println("此时synchronized锁定的是类");
                System.out.println(Thread.currentThread().getName()+"正在执行");
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName()+"正在休眠1秒种");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行结束");
                System.out.println("======");
            }
        }
    }
}
public class TestLock {
    public static void main(String[] args) {

        Visitor visitor = new Visitor();
//        Visitor visitor1 = new Visitor();

        new Thread(()->{
            Visitor.testVisit(visitor);
        },"线程1").start();

        new Thread(()->{
            Visitor.testVisit(visitor);
        },"线程2").start();
    }
}

在这里插入图片描述

public class TestLock {
    public static void main(String[] args) {

        Visitor visitor = new Visitor();
        Visitor visitor1 = new Visitor();

        new Thread(()->{
            visitor.testVisit(null);
        },"线程1").start();

        new Thread(()->{
            visitor1.testVisit(null);
        },"线程2").start();
    }
}

在这里插入图片描述

(2) synchronized加锁方式中断线程的方法

java提供的中断线程的方法

public void Thread.interrupt();//中断线程

public boolean Thread.isInterrupted();//判断线程是否被中断

public static boolean Thread.interrupted();//判断是否被中断并清除当前中断状态(静态方法)

interrupt() 方法的使用

interrupt()其实并没有终止线程,只是将线程的中断标记位设置为了true

如果想让线程停止执行任务,可以在重写run方法中用循环判断中断标记位,如下示例

public class MyThread extends Thread{
    @Override
    public void run() {
        int i=0;
        while(!isInterrupted()){
            i++;
            System.out.println(i+"---myThread is running");
        }
        System.out.println("线程任务结束");
    }
}
public class TestThread {
    public static void main(String[] args)  {
        MyThread myThread = new MyThread();
        myThread.start();

        System.out.println(myThread.getState());//打印线程的状态
        myThread.interrupt(); //中断线程(将中断标记位设为true)
        System.out.println(myThread.isInterrupted()); //打印标记位
        System.out.println(myThread.getState()); //打印中断后的状态
    }
}

在这里插入图片描述

2. Lock接口(显式锁)

Lock接口和synchronized类似,都是为了实现线程同步而存在的锁,不过Lock需要手动加锁和释放

(1) Lock接口的常用实现类

在这里插入图片描述

(2) Lock接口的方法

方法名称描述
void lock()获取锁
void lockInterruptibly() throws InterruptedException可中断获取锁,会响应interrupt()方法并抛出异常
boolean tryLock()尝试非阻塞的获取锁,获取成功返回true,否则返回true
boolean tryLock(long time,TimeUnit unit) hrows InterruptedException设置获取锁的超时时间,获取成功返回true,被中断或时间结束返回false
void unlock()释放锁
Condition newCondition()生成一个Condition对象,用于实现Lock锁的等待唤醒机制

(3) Condition 接口

Condition接口提供了类似Object的监视器方法,与Lock配合可以实现等待唤醒机制

Condition 接口的方法

在这里插入图片描述

(4) 读写锁

读写锁的UML类图

ReadWriteLock是独立于Lock的一个接口,ReentreantReadWriteLock是该接口的实现类,ReadLock(读锁)、WriteLock(写锁)是该实现类的静态内部类,且都实现了Lock接口
在这里插入图片描述
在这里插入图片描述
读写锁的使用示例

class Visitor{

    //读锁和写锁都需要从ReentrantReadWriteLock对象中获取
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    public Lock readLock = readWriteLock.readLock();  //获得写锁

    public Lock writeLock = readWriteLock.writeLock(); //获得读锁

    public void readVisit(){    //使用读锁执行任务

        this.readLock.lock(); //添加读锁
        System.out.println(Thread.currentThread().getName()+"获得了读锁,开始执行任务,且马上休眠1秒");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            this.readLock.unlock();
            System.out.println(Thread.currentThread().getName()+"任务结束,释放了读锁");
        }
    }

    public void writeVisit(){    //使用写锁执行任务

        this.writeLock.lock(); //添加写锁
        System.out.println(Thread.currentThread().getName()+"获得了写锁,开始执行任务,且马上休眠1秒");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            this.writeLock.unlock();
            System.out.println(Thread.currentThread().getName()+"任务结束,释放了写锁");
        }
    }
}
public class TestLock {
    public static void main(String[] args) {
        Visitor visitor = new Visitor();

        new Thread(()->{
            visitor.readVisit();
        },"线程1").start();

        new Thread(()->{
            visitor.readVisit();
        },"线程2").start();

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

        new Thread(()->{
            visitor.writeVisit();
        },"线程3").start();

        new Thread(()->{
            visitor.writeVisit();
        },"线程4").start();
    }
}

在这里插入图片描述
从结果可以看出,读锁可以共享(同步完成任务),而写锁不能共享

3. ReentrantLock(重入锁)

ReentrantLock是Lock(显式锁)接口下的一个实现类,也是使用频率最高的锁

补充:JUC工具包

Lock是JUC工具包java.util.concurrent里的接口,JUC也称为Java并发编程工具包,是Java 官方提供的一套专门用来处理并发编程的工具集合(接口+类)

(1) 使用ReentrantLock实现线程同步

public class TestLock {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(()->{
            account.visit();
        },"线程1").start();

        new Thread(()->{
            account.visit();
        },"线程2").start();
    }
}

class Account{
    public static int order; //记录访问序号
    private ReentrantLock reentrantLock = new ReentrantLock(); //创建重入锁

     public void visit(){
        System.out.println(Thread.currentThread().getName()+"进入了执行");
        reentrantLock.lock(); //获得重入锁
        System.out.println(Thread.currentThread().getName()+"获得了锁");
        System.out.println(Thread.currentThread().getName()+"开始执行任务");
        order ++; // 访问数+1
        try {
            TimeUnit.SECONDS.sleep(1); //调用JUC工具包的线程休眠方法,本质还是调用Thread.sleep(),不过JUC将其包装了起来
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是第"+order+"次访问的线程");
        System.out.println(Thread.currentThread().getName()+"解除了锁");
        System.out.println("==========");
        reentrantLock.unlock(); //解除重入锁
    }
}

在这里插入图片描述

(2) ReentrantLock的特点

① 是可重入锁
② 可以设置为公平锁或非公平锁
③ 可中断
④ 可以设置超时时间

① 可重入锁

可重入锁是指同一个线程可以多次获取同一把锁,就是锁可叠加,ReentrantLock和synchronized都是可重入锁

获得的锁>解除的锁时,其他线程会一直获取不了锁,也就不能执行
获得的锁<解除的锁时,会抛出异常,其他线程可以获得锁

 public void visit(){
        reentrantLock.lock(); //获得重入锁
        reentrantLock.lock(); //获得第2把锁
        order ++; // 访问数+1
        try {
            TimeUnit.SECONDS.sleep(1); //调用JUC工具包的线程休眠方法,本质还是调用Thread.sleep(),不过JUC将其包装了起来
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是第"+order+"次访问的线程");
        reentrantLock.unlock(); //解除重入锁
        reentrantLock.unlock(); //解除第2把锁
    }

② 公平锁和非公平锁

公平锁是指多个线程按队列顺序排队获得锁,队列的第一位才能得到锁,而非公平锁是可以插队的,就是线程获得锁没有顺序

公平锁其实是为了解决饥饿问题,当一个线程由于优先级太低的时候,就可能没有办法获取到时间片,就一直不能执行该线程

通过Thread.setPriority()方法可以设置线程的优先级,可以设为1~10,10为最高,1最低

ReentrantLock默认是非公平锁,可以通过有参构造器ReentrantLock fairLock = new ReentrantLock(true);去生成公平锁还是非公平锁

非公平锁运行示例

class Account{
    public static int order; //记录访问序号
    private ReentrantLock reentrantLock = new ReentrantLock(); //创建非公平重入锁

    public void unfairVisit(){ 
        while(order<10){
            reentrantLock.lock(); //获得锁
            order++; //访问次数+1
            System.out.println(Thread.currentThread().getName()+"是第"+order+"次访问的线程");
            reentrantLock.unlock(); //解除锁
        }
    }
}
public class TestLock {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(()->{
            account.unfairVisit();
        },"线程1").start();

        new Thread(()->{
            account.unfairVisit();
        },"线程2").start();
    }
}

在这里插入图片描述
从结果可以看出,因为每次线程1执行完都插队到线程2的前面,导致线程2一直没有执行的机会

使用公平锁就可以解决这种饥饿者问题

公平锁运行示例

只需要改动这一行代码,设置生成的是公平锁,其余代码一样

 private ReentrantLock reentrantLock = new ReentrantLock(true); //创建公平重入锁

在这里插入图片描述
可以看出,使用公平锁后,线程1和线程2在交替运行,这是因为每当线程执行完后,需要排队,且是排在队列的末尾,那么就不会出现插队的情况,也就避免了饥饿者问题

③ 可响应中断

可中断是指某个线程在等待获取锁的过程中可以主动终止线程,注意是在获取锁的过程中被中断,直接调interrupt()方法即可,同时会抛出InterruptException

lockInterruptibly()和lock()的区别

lockInterruptibly():可中断上锁

lock():普通上锁

lockInterruptibly 可以响应中断,就是在被执行interrupt()方法后,会抛出InterruptedException中断异常,即线程被中断

说得更明白一点就是,都知道获取锁需要等待其他线程释放锁,那么就是在等待获取锁的这个过程中,当被中断,可以抛出异常,而普通的lock不会抛出异常

lock() 不响应中断,被 interrupt() 后不会立即中断,而只是将中断标记位设置true

class Account{
    public static int order; //记录访问序号
    private ReentrantLock reentrantLock = new ReentrantLock(); //创建重入锁

    public void interruptVisit(){
        try {
            System.out.println(Thread.currentThread().getName()+"进入了执行");

            reentrantLock.lockInterruptibly(); // 加上可中断锁

            System.out.println(Thread.currentThread().getName()+"获得了可中断锁");
            System.out.println(Thread.currentThread().getName()+"开始执行任务");

            order++; //访问次数+1
            System.out.println(Thread.currentThread().getName()+"是第"+order+"次访问的线程");

        } catch (InterruptedException e) {  // 抛出中断线程的异常
            System.out.println(Thread.currentThread().getName()+"被中断了!!!");
            e.printStackTrace();
        }
        finally {
            reentrantLock.unlock(); //解除可中断锁
            System.out.println(Thread.currentThread().getName()+"解除了可中断锁");
            System.out.println("==========");
        }
    }
}
public class TestLock {
    public static void main(String[] args) {
        Account account = new Account();

        Thread thread1 = new Thread(()->{
            account.interruptVisit();
        },"线程1");

        Thread thread2 = new Thread(()->{
            account.interruptVisit();
        },"线程2");

        thread1.start();
        thread2.start();

        thread1.interrupt(); //中断线程1
    }
}

在这里插入图片描述
从运行结果可以看出,当中断thread1的时候,抛出了异常

④ 可设置获得锁的超时时间

使用tryLock()方法,如果返回是false,则说明没有获取到锁

tryLock()的重载:tryLock(long timeout, TimeUnit unit)
第一个参数为数字,即多少时间为超时,第二个参数为时间单位

class Account{
    public static int order; //记录访问序号
    private ReentrantLock reentrantLock = new ReentrantLock(); //创建重入锁

    public void keepLock(){ //持有锁的线程任务
        reentrantLock.lock();
        System.out.println(Thread.currentThread().getName()+"获得了锁,将持有3秒");
        try {
            TimeUnit.SECONDS.sleep(3); //使线程休眠3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("3秒到了,"+Thread.currentThread().getName()+"释放了锁");
        reentrantLock.unlock();
    }

    public void testTryLock(){ //获取锁的线程任务
        try {
            System.out.println(Thread.currentThread().getName()+"正在尝试获取锁");
           if(!reentrantLock.tryLock(2,TimeUnit.SECONDS)) {  // 如果2秒后都没有获得锁
               System.out.println(Thread.currentThread().getName()+"执行了2秒也没获得锁");
           }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class TestLock {
    public static void main(String[] args) {
        Account account = new Account();

        new Thread(()->{
            account.keepLock(); // 持有锁的任务
        },"线程1").start();

        new Thread(()->{
            account.testTryLock(); // 获取锁的任务
        },"线程2").start();
    }
}

在这里插入图片描述

ReentrantLock和synchronized的区别

ReentrantLock 就是对 synchronized 的升级,目的也是为了实现线程同步

ReentrantLock 是一个类,synchronized 是一个关键字

ReentrantLock 是 JDK 实现,synchronized 是 JVM 实现

ReentrantLock可响应中断抛出异常,synchronized不能

synchronized 可以自动释放锁,ReentrantLock 需要手动释放

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值