堆栈花园的多线程学习备忘录——锁

一、java中的锁
  • 在JDK5中,新增了Lock锁接口,有ReentrantLock实现类,ReentrantLock锁称为可重入锁,它的功能比synchronize多,更加强大。
二、锁的可重入性
  • 锁的可重入性,是指当一个线程已经获得一个对象锁时,再次请求获得该对象锁时,是可以获得的。
private synchronized void ff1(){
        System.out.println("方法1");
        ff2();
    }

    private synchronized void ff2() {
        System.out.println("方法2");
        ff3();
    }

    private synchronized void ff3() {
        System.out.println("方法3");
    }
    public static void main(String[] args) {
        Demo6 demo = new Demo6();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.ff1();
            }
        }).start();
    }
  • 开启线程,线程调用ff1方法,默认的this就是锁对象,在ff1方法中,调用了ff2方法,当前线程已经持有了锁对象。在ff2方法中,默认的锁对象也是this对象,要执行ff2,必须要获得该锁对象。虽然当前线程已经有了一个锁对象,但是还可以再次获得这个锁对象,这就是锁的可重入性。
  • 如果锁没有可重入性,那可能会造成死锁。因为线程要执行ff2方法,必须等待锁释放,但是它自己拿着这个锁,如果ff2没有执行完毕,它就不可能释放这个锁,就死锁了。
三、ReentrantLock
static Lock lock = new ReentrantLock();
    public static  void ff(){
        lock.lock();
        for(int i = 0; i <10;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
        lock.unlock();
    }
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                ff();
            }
        };
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }

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

  • 先定义一个ReentrantLock对象,在方法中,我们需要同步的位置调用lock方法,当不需要同步的时候,调用unlock方法释放锁,然后创建三个线程,分别去调用ff方法,发现,所有的线程都完成了0~9的打印,互不干扰。
  • 一般我们会在try代码块中获得Lock锁(也就是调用lock方法),在finally代码块中释放锁(unlock方法)。
  • 对于synchronize内部锁来说,如果一个线程在等待锁,只会有两个结果,要么该线程获得锁,开始执行,要么就一直等着。
    对于ReentrantLock可重入锁来说,提供了第三种可能,在等待锁的途中,程序可以根据需求,取消对锁的请求。(使用lockInterruptibly()方法,可以解决死锁问题 )
四、ReentrantReadWriteLock 读写锁

1. ReentrantReadWriteLock 读写锁基本介绍

  • synchronize内部锁和ReentrantLock锁都是独占锁(排它锁),同一时间只能有一个线程执行同步代码块,可以保证线程安全,但是效率比较低。
  • ReentrantReadWriteLock 读写锁可以理解为一种改进的排它锁,也可以称作共享排它锁。它允许多个线程同时读取共享数据,但是只允许一个线程对共享数据更新。
  • 读写锁通过读锁+写锁来完成读写操作。线程在读取共享数据前,必须持有读锁,读锁可以被多个线程持有。写锁就是排他的,线程更新共享数据前,必须先拿到写锁。当一个线程持有写锁时,其他线程不仅拿不到写锁,连读锁也拿不到。读锁只是在要读共享数据的线程之间共享,只要有一个线程有读锁,别的线程是无法获得写锁的。这保证了线程在读数据期间,没有其他线程对数据进行修改,使得读线程能够读到数据的最新值。
获得条件排他性作用
读锁写锁未被任意线程持有对要读的线程是共享的,对要写的线程是排他的允许多个要读的线程可以同时读取共享数据,并且保证了读的时候没有其他线程对共享数据修改
写锁该写锁未被其他线程持有,并且相应的读锁也未被其他线程持有对要读,要写的线程都排他保证要写的线程以独享的方式修改数据
  • 读写锁允许读读共享,读写互斥,写写互斥。

2. ReentrantReadWriteLock 读写锁的基本使用

  • 在java.util.concurrent.locks包中定义了ReentrantWriteLock接口,该接口中定义了readLock()返回读锁,定义writeLock()返回写锁,该接口的实现类是ReentrantReadWriteLock类。注意:readLock()和writeLock()返回的锁,是同一个锁的两个部分(角色),并不是两个不同的锁!
//定义读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//获得读锁
Lock readLock = rwLock.readLock();
//获得写锁
Lock writeLock = rwLock.writeLock();
//读数据
readLock.lock();//申请读锁
try {
	//读取共享数据
}finally {
    readLock.unlock();//我们总是在finally里释放锁
}
//写数据
writeLock.lock();
try {
	//修改共享数据
}finally {
	readLock.unlock();//释放锁
}

3. 读读共享

  • ReadWriteLock读写锁可以实现多个线程同时读取共享数据,即读读共享,可以提高程序的读取数据的效率。

演示代码:

static class Service{
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss SSS");
        //定义读写锁
        ReadWriteLock rwLock = new ReentrantReadWriteLock();
        //定义方法读数据
        public void read() {
            rwLock.readLock().lock();//获取读锁
            System.out.println(Thread.currentThread().getName()+"获得读锁,开始读数据"+sdf.format(new Date()));
            try {
                TimeUnit.SECONDS.sleep(3);//模拟读数据
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                rwLock.readLock().unlock();//释放锁
            }
        }
    }

    public static void main(String[] args) {
        Service service = new Service();
        //创建几个线程,调用read方法
        for(int i = 0; i < 5; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.read();
                }
            }).start();
        }

    }

运行结果:在这里插入图片描述
从运行结果可以看出,所有的线程都是同时获得读锁,执行lock后面的代码

4. 写写互斥

  • 通过ReadWriteLock读写锁中的写锁,只允许有一个线程执行Lock()后面的代码。

演示代码:

static class Service{
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss SSS");
        //定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        //定义方法写数据
        public void write(){
            readWriteLock.writeLock().lock();//申请获得写锁
            System.out.println(Thread.currentThread().getName()+"获得写锁,开始写数据"+sdf.format(new Date()));
            try {
                TimeUnit.SECONDS.sleep(3);//模拟修改数据数据
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName()+"数据写完的时间="+sdf.format(new Date()));
                readWriteLock.writeLock().unlock();//释放锁
            }
        }
    }

    public static void main(String[] args) {
        Service service = new Service();

        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    service.write();
                }
            }).start();
        }
    }

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

在这里插入图片描述从运行结果可以看出,通过写锁,每个线程必须要“排队”写数据,实现了写写互斥。

5. 读写互斥

  • 写锁是排它锁,那么读与写也是互斥的。

演示代码:

static class Service {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss SSS");
        //定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        //定义方法读数据
        public void read() {
            readWriteLock.readLock().lock();//申请获得读锁
            System.out.println(Thread.currentThread().getName() + "获得读锁,开始读数据" + sdf.format(new Date()));
            try {
                TimeUnit.SECONDS.sleep(3);//模拟读数据
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "数据读完的时间=" + sdf.format(new Date()));
                readWriteLock.readLock().unlock();//释放锁
            }
        }

        //定义方法写数据
        public void write() {
            readWriteLock.writeLock().lock();//申请获得写锁
            System.out.println(Thread.currentThread().getName() + "获得写锁,开始写数据" + sdf.format(new Date()));
            try {
                TimeUnit.SECONDS.sleep(3);//模拟修改数据数据
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "数据写完的时间=" + sdf.format(new Date()));
                readWriteLock.writeLock().unlock();//释放锁
            }
        }
    }

    public static void main(String[] args) {
        Service service = new Service();

        new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                service.write();
            }
        }).start();
    }

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

程序创建了两个线程,第一个线程先获得读锁,开始读数据,这个时候第二个线程就进不来了,必须等第一个线程把读锁释放了,才能获得写锁,实现了读写互斥。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值