为了账号安全,请及时绑定邮箱和手机立即绑定

Java多线程中的锁

标签:
Java 源码

一、Java中的锁

(一)可重入锁:

1. 当一个线程再次获取它自己已经获取的锁时,如果不被阻塞,则说明该锁是可重入锁,也就是只要该线程获取了该锁,那么可以无限次数地进入被该锁锁住的代码里。相反,如果被阻塞了,说明是不可重入锁。   

2. synchronized内部锁是可重入锁。可重入锁的原理:在锁内部维护一个线程标示,用来标示该锁目前被哪个线程占用。

当一个线程获取了该锁时。计数器的值会变成1,这时其他线程再来获取该锁时会发现锁的所有者不是自己而被阻塞挂起。但是当获取了该锁的线程再次获取锁时发现锁拥有者是自己,就会把计数器值加+1,当释放锁后计数器值-1。当计数器的值为0时,锁里面的线程标示被重置为null,这时被阻塞的线程会被唤醒来竞争获取该锁。


(二)死锁

死锁:当一个线程永远的持有一把锁,并且其他线程都尝试来获得这把锁时,就会发生死锁。

多个线程互相拥有锁,互不释放锁,造成线程死锁。   

可以通过cmd命令窗口中输入jconsole命令来检测线程情况,查看死锁。     


(三)自旋锁

自旋锁:当前线程在获取锁时,如果发现锁已经被其他线程占有,它不会马上阻塞自己,在不放弃CPU使用权的情况下,多次尝试获取(默认次数是10,可以通过使用-XX:PreBlockSpinsh参数设置该值),很有可能在后面的几次尝试中其他线程已经释放了锁。而如果尝试指定的次数后仍没有获取到锁,则当前线程才会被阻塞挂起。   

自旋锁是使用CPU时间换取线程阻塞与调度的开销,但是很有可能这些CPU时间白白浪费了。   


(四)公平锁和非公平锁

1. 根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁。   

公平锁表示线程获取锁的顺序是按照线程请求锁的先后顺序来决定的,也就是最早请求锁的线程将最早获取到锁。

如果一个锁是公平的,那么锁的获取顺序就应该符合线程请求的绝对时间顺序。    

非公平锁则是在运行时闯入,也就是先来不一定先得,它不是按照线程请求锁的先后顺序来获取锁的。    

2. ReentrantLock提供了公平锁和非公平锁的实现。    

   (1)ReetrantLock fairLock = new ReetrantLock(true);    

   (2)ReetrantLock noFairLock = new ReetrantLock(false);  如果构造参数不传递参数,则默认是非公平锁。   

3. 在没有公平性需要的前提下,尽量使用非公平锁,因为公平锁会带来性能开销。     

   

(五)独占锁和共享锁

1. 根据锁只能被单个线程持有还是能被多个线程共同持有,锁可以分为独占锁和共享锁。    

独占锁:它保证任何时候都只有一个线程能得到锁,ReetrantLock就是以独占方式实现的。   

共享锁:它可以同时由多个线程持有,如ReadWriteLock读写锁,它允许一个资源可以被多个线程同时进行操作。   

2. 独占锁是一种悲观锁,由于每次访问资源都先加上互斥锁,这就限制了并发性,因为读操作并不会影响数据的一致性,不存在线程安全问题,而独占锁只允许在同一时间由一个线程读取数据,其他线程必须等待当前线程释放锁才能进行读取。    

3. 共享锁是一种乐观锁,它放宽了加锁的条件,允许多个线程同时进行读操作。


(六)读写锁(ReadWriteLock)

读写锁既是排它锁又是共享锁。读锁(readLock)是共享锁,写锁(writeLock)是排他锁。        

排它锁:在同一时刻,只允许一个线程进行访问。          

共享锁:在同一时刻,可以允许多个线程进行访问。共享锁与共享锁之间可以同时访问的,比如可以有多个读线程进行同时读。     

读与读是不互斥的,读与写,写与读,写与写都是互斥的。    

在只有读线程的条件下,多个线程可以同时进入这个方法。读线程没有线程安全问题,可以保证读的性能提高。       

读写锁需要保存的状态:   

(1)写锁重入的次数     

(2)读锁的个数    

(3)每个读锁重入的次数   

实例代码:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName: RwLock
 * @Description: 读写锁:既是排它锁又是共享锁
 * 写锁:是排它锁,同一时刻,只能有一个线程进行访问
 * 读锁:是共享锁,同一时刻,允许多个线程进行访问
 * @Author: liuhefei
 * @Date: 2019/4/5
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class RwLock {

    private Map<String, Object> map = new HashMap<>();

    //创建读写锁对象
    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    //读锁
    private Lock r = rwl.readLock();

    //写锁
    private Lock w = rwl.writeLock();

    //读操作
    public Object get(String key){
        r.lock();  //加锁
        System.out.println(Thread.currentThread().getName() + " 读操作在执行");
        try {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return map.get(key);
        }finally {
            r.unlock();  //释放锁
            System.out.println(Thread.currentThread().getName() + " 读操作执行完毕");
        }
    }

    //写操作
    public void put(String key, Object value){
        w.lock();
        System.out.println(Thread.currentThread().getName() + " 写操作在执行");
        try {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
        }finally {
            w.unlock();
            System.out.println(Thread.currentThread().getName() + " 写操作执行完毕");
        }
    }
}
/**
 * @ClassName: RwLockTest
 * @Description: 写与读:互斥
 * @Author: liuhefei
 * @Date: 2019/4/5
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class RwLockTest {

    public static void main(String[] args) {
        RwLock rwLock = new RwLock();

        //1.写与读:互斥
        //写
        new Thread(new Runnable() {
            @Override
            public void run() {
                    rwLock.put("key1" , 10);
            }
        }).start();

        //读
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(rwLock.get("key1"));;
            }
        }).start();


    }
}

锁降级:是指写锁降级为读锁。   

实现:在写锁没有释放的时候,获取到读锁,再释放写锁。    

实例代码:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName: RwLock
 * @Description:  读写锁,实现锁降级(写锁降级为读锁)
 * @Author: liuhefei
 * @Date: 2019/4/5
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class RwLock1 {

    private Map<String, Object> map = new HashMap<>();

    //创建读写锁对象
    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    //读锁
    private Lock r = rwl.readLock();
    //写锁
    private Lock w = rwl.writeLock();

    //是否要更新
    private volatile boolean isUpdate = true;  //保证可见性

    //读写操作——读写互斥,实现锁降级
    public void readWrite(){

        r.lock();  //加读锁,为了保证isUpdate能够拿到最新的值
        System.out.println(Thread.currentThread().getName() + " 读写操作在执行");
        if(isUpdate){
            r.unlock();  //释放读锁之后,所有的写线程会竞争写锁,最终只有一个线程可以获得写锁

            //执行写操作
            w.lock();  //加写锁
            map.put("key1", 12345);
            r.lock();  //加读锁,实现锁降级(写锁降级为读锁)

            w.unlock();  //释放写锁
        }
        Object obj = map.get("key1");
        System.out.println("obj = " + obj);
        r.unlock();   //释放读锁
        System.out.println(Thread.currentThread().getName() + " 读写操作执行完毕");
    }

    //读操作
    public Object get(String key){
        r.lock();  //加锁
        System.out.println(Thread.currentThread().getName() + " 读操作在执行");
        try {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return map.get(key);
        }finally {
            r.unlock();  //释放锁
            System.out.println(Thread.currentThread().getName() + " 读操作执行完毕");
        }
    }

    //写操作
    public void put(String key, Object value){
        w.lock();
        System.out.println(Thread.currentThread().getName() + " 写操作在执行");
        try {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
        }finally {
            w.unlock();
            System.out.println(Thread.currentThread().getName() + " 写操作执行完毕");
        }
    }
}
public class RwLockTest3 {

    public static void main(String[] args) {
        RwLock1 rwLock1 = new RwLock1();

        new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock1.put("key1", 100);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock1.readWrite();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("key1=" + rwLock1.get("key1"));
            }
        }).start();
    }
}

锁升级:是指把读锁升级为写锁。   

实现:在读锁没有释放的时候,获取到写锁,再释放读锁。     


ReentrantReadWriteLock不支持锁升级,只支持锁降级。    


二、Java中JDK提供的原子类    

JDK提供的原子类位于java.util.concurrent.atomic中。   

主要的类有:   

(1)原子更新基本类型;  

(2)原子更新数组;

(3)原子更新抽象类型;

(4)原子更新字段;

实例代码:

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName: Sequence
 * @Description: 三个线程同时对共享变量value做计数器
 * 会存在线程不安全问题,为了解决这个问题,使用原子操作类实现
 * @Author: liuhefei
 * @Date: 2019/3/31
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class AtomicIntegerDemo {

    //之前
    /*private int value;

    public int getValue(){
        return value++;
    }*/

    //之后
    //使用原子操作类实现
    private AtomicInteger value = new AtomicInteger(0);

    public int getValue(){
        return value.getAndIncrement();  //获取并且自增 相当于value++
        //return value.getAndAdd(10);  //每次自增加10
    }

    public static void main(String[] args) {
        AtomicIntegerDemo s = new AtomicIntegerDemo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + s.getValue());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + s.getValue());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + s.getValue());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}


三、Lock锁

1. Lock锁需要显示地获取lock()和释放锁unlock(),繁琐,但是代码编写灵活。      

   Synchronized不需要显示地获取和释放锁,简单。 

2. 使用Lock可以方便的实现公平性;   

3. Lock可以非阻塞的获取锁,能被中断的获取锁,也能超时获取锁;

实例代码1:

package com.lhf.thread6;

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

/**
 * @ClassName: Counter
 * @Description: 使用Lock锁实现计数器,并保证线程安全
 * @Author: liuhefei
 * @Date: 2019/4/4
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class Counter {

    private int value;

    //保证所有线程共用一把锁
    Lock lock = new ReentrantLock();   //创建锁对象

    public int getNext(){
        //Lock lock = new ReentrantLock();   //创建锁对象,如果放到此处将会出现线程不安全问题
        lock.lock();   //加锁
        int a = value++;
        lock.unlock();  //释放锁

        return a;
    }

    public static void main(String[] args) {
        Counter counter = new Counter();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + counter.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + counter.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + counter.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();


    }
}

实例代码2:

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

/**
 * @ClassName: MyLock
 * @Description: 实现Lock接口,自定义一个锁,这个锁是不可重入锁
 * @Author: liuhefei
 * @Date: 2019/4/4
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class MyLock implements Lock {

    //标志,判断是否拿到锁,默认为false表示没有拿到锁
    private boolean isLocked = false;

    @Override
    public synchronized void lock() {  //使用synchronized保证所有进来的线程都要等待前一个线程执行完毕

        while (isLocked){   //如果拿到锁,就执行wait方法,进入等待状态;
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        isLocked = true;

    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    /**
     * 释放锁
     */
    @Override
    public synchronized void unlock() {
       isLocked = false;
       notify();  //唤醒锁
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
/**
 * @ClassName: Counter1
 * @Description:  使用自定义锁实现计数器并保证线程安全
 * @Author: liuhefei
 * @Date: 2019/4/4
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class Counter1 {

    private MyLock myLock = new MyLock();

    private int value;

    public int getNext(){
        myLock.lock();  //加锁
        value++;   //原子性操作
        myLock.unlock();  //释放锁
        return value;
    }

    public static void main(String[] args) {
        Counter1 counter1 = new Counter1();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + counter1.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + counter1.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println(Thread.currentThread().getName() + "--" + counter1.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    }
}


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消