锁的常见概念

  • 互斥: 同一时刻只有一个线程执行
  • 临界区:一段需要互斥执行的代码
  • 细粒度锁: 用不同的锁对受保护资源进行精细化管理。 细粒度锁可以提高并行度,是性能优化的一个重要手段
  • 死锁 :一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象 。

synchronized

class  X{
    //修饰非静态方法
    synchronized void foo(){
       //临界区
    }
    //修饰静态方法
    synchronized static void bar(){
       //临界区
    }

    //修饰代码块
    Object obj = new Object();
    void baz(){
        synchronized(obj){
            //临界区
        }
    }
}

Java编译器会在synchronized修饰的方法或代码块前后自动加上加锁lock()和解锁unlock(),这样做的好处就是加锁lock()和解锁unlock()一定 是成对出现的,毕竟忘记解锁unlock()可是个致命的Bug(意味着其他线程只能死等下去了)。

修饰静态方法:
//修饰静态方法是用当前类的字节码文件作为锁
class  X{
    //修饰静态方法
    synchronized(X.class) static void bar(){
       //临界区
    }
}
修饰非静态方法:
//修饰非静态方法是用当前对象作为锁
class  X{
    //修饰非静态方法
    synchronized(this) static void bar(){
       //临界区
    }
}

如何用一把锁保护多个资源

受保护资源和锁之间合理的关联关系应该是N:1的关系,也就是说可以用一把锁来保护多个资源,但是不能用多把锁来保护一个资源,

使用锁的正确姿势

依转账业务作为示例

示例一:

public class Account {
    /**
     *锁:保护账⼾余额
     */
    private final   Object  balLock = new Object();
    /**
     * 账⼾余额
     */
    private Integer balance;

    /**
     * 错误的做法
     * 非静态方法的锁是this,
     * this这把锁可以保护自己的余额this.balance,保护不了别人的余额 target.balance
     *
     */
   synchronized void transfer(Account target,int amt){
        if (this.balance > amt) {
            this.balance -= amt;
            target.balance += amt;//这段代码会出现线程安全,要保证线程安全的话要使用同一个锁
        }
    }
}

示例二:

public class Account {
    /**
     *锁:保护账⼾余额
     */
    private final   Object  balLock = new Object();
    /**
     * 账⼾余额
     */
    private Integer balance;

    /**
     * 正确的做法,但是会导致整个转账系统的串行
     *
     * Account.class是所有Account对象共享的,
     * 而且这个对象是Java虚拟机在加载Account类的时候创建的,
     * 所以我们不用担心它的唯一性
     *
     * 这样还有个弊端:所有的转账都是串行了
     */
    void transfer2(Account target,int amt){
        synchronized(Account.class){
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
}

这样的话转账操作就成了串行的了,正常的逻辑应该只锁转入账号和被转入账户;不影响其他的转账操作。稍作改造:

示例三:

public class Account {
    /**
     *锁:保护账⼾余额
     */
    private final Object lock;
    /**
     * 账⼾余额
     */
    private Integer balance;

    //私有化无参构造
    private Account(){}
    //设置一个传递lock的有参构造
    private Account(Object lock){
        this.lock = lock;
    }

    /**
     * 转账
     */
    void transfer(Account target,int amt){
        //此处检查所有对象共享锁
        synchronized(lock){
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
}

这个方法虽然能够解决问题,但是它要求创建Account对象的时候必须传入同一个对象,

还有就是传递对象过于麻烦,写法繁琐缺乏可行性。

示例四:

public class Account {

    /**
     * 账⼾余额
     */
    private Integer balance;

    /**
     * 转账
     */
    void transfer(Account target,int amt){
        //此处检查所有对象共享锁
        synchronized(Account.class){
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
}

用Account.class作为共享的锁,锁定的范围太大。 Account.class是所有Account对象共享的,而且这个对象是Java虚拟机在加载Account类的时候创建的,所以我们不用担心它的唯一性。使用Account.class作为共享的锁,我们就无需在创建Account对象时传入了。

这样新的问题就出来了虽然用Account.class作为互斥锁,来解决银行业务里面的转账问题,虽然这个方案不存在 并发问题,但是所有账户的转账操作都是串行的,例如账户A转账户B、账户C转账户D这两个转账操作现实 世界里是可以并行的,但是在这个方案里却被串行化了,这样的话,性能太差。所以如果考虑并发量这种方法也不行的

正确的写法是这样的(使用细粒度锁):

示例五:

public class Account {

    /**
     * 账⼾余额
     */
    private Integer balance;

    /**
     * 转账
     */
    void transfer(Account target,int amt){
        //锁定转出账户
        synchronized(this){
             //锁住转入账户
            synchronized(target){
                if (this.balance > amt) {
                    this.balance -= amt;
                    target.balance += amt;
                }
            }
        }
    }
}

我们试想在古代,没有信息化,账户的存在形式真的就是一个账本,而且每个账户都有一个账本,这些账本 都统一存放在文件架上。银行柜员在给我们做转账时,要去文件架上把转出账本和转入账本都拿到手,然后做转账。这个柜员在拿账本的时候可能遇到以下三种情况:

  1. 文件架上恰好有转出账本和转入账本,那就同时拿走;
  2. 如果文件架上只有转出账本和转入账本之一,那这个柜员就先把文件架上有的账本拿到手,同时等着其 他柜员把另外一个账本送回来;
  3. ​ 转出账本和转入账本都没有,那这个柜员就等着两个账本都被送回来。

细粒度锁有可能会出现死锁

  • 死锁 :一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象 。
  • 两个线程彼此拿着对方的资源都不释放就会导致死锁,
  • 使用细粒度锁可能会导致死锁

如果有客户找柜员张三做个转账业务:账户 A转账户B 100元,此时另一个客户找柜员李四也做个转账业务:账户B转账户A 100元,于是张三和李四同时都去文件架上拿账本,这时候有可能凑巧张三拿到了账本A,李四拿到了账本B。张三拿到账本A后就等着 账本B(账本B已经被李四拿走),而李四拿到账本B后就等着账本A(账本A已经被张三拿走),他们要等 多久呢?他们会永远等待下去…因为张三不会把账本A送回去,李四也不会把账本B送回去。我们姑且称为死等吧。

如何避免死锁
  1. 互斥,共享资源X和Y只能被一个线程占用;
  2. 占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源x;
  3. 不可抢占,其他线程不能强行抢占线程T1占有的资源;
  4. 循环等待,线程1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待。

只要破坏其中一个就可以避免死锁

等待-通知机制

用synchronized实现等待-通知机制

  • synchronized 配合wait(),notif(),notifyAll()这三个方法能够轻松实现.
  • wait(): 当前线程释放锁,进入阻塞状态
  • notif(),notifAll(): 通知阻塞的线程有可以继续执行,线程进入可执行状态
  • notif()是会随机地地通知等待队歹一个线程
  • notifyAll()会通知等待队列中的所有线程,建议使用notifAll()

wait与sleep区别:

sleep是Object的中的方法,wait是Thread中的方法

wait会释放锁,sleep不会释放锁

wait需要用notif唤醒,sleep设置时间,时间到了唤醒

wait无需捕获异常,而sleep需要

wait(): 当前线程进入阻塞


**** 码字不易如果对你有帮助请给个关注****

**** 爱技术爱生活 QQ群: 894109590****

锁和synchronized的更多相关文章

  1. Java 锁机制 synchronized

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/75126630 本文出自[赵彦军的博客] 1.前言 在多线程并发编程中Synchro ...

  2. ReenTrantLock可重入锁和synchronized的区别

    ReenTrantLock可重入锁和synchronized的区别 可重入性: 从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入 ...

  3. 码农会锁,synchronized 对象头结构(mark-word、Klass Pointer)、指针压缩、锁竞争,源码解毒、深度分析!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 感觉什么都不会,从哪开始呀! 这是最近我总能被问到的问题,也确实是.一个初入编程职场 ...

  4. java多线程(三)——锁机制synchronized(同步语句块)

    用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法之行一个长时间的任务,那么B线程必须等待比较长的时间,在这样的情况下可以使用synchronized同步语句快来解 ...

  5. java多线程(二)——锁机制synchronized(同步方法)

    synchronized Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中 ...

  6. Java Learning:并发中的同步锁(synchronized)

    引言 最近一段时间,实验室已经倾巢出动找实习了,博主也凑合了一把,结果有悲有喜,BAT理所应当的跪了,也收到了其他的offer,总的感受是有必要夯实基础啊. 言归正传,最近在看到java多线程的时候, ...

  7. Java 线程锁机制 -Synchronized Lock 互斥锁 读写锁

    (1)synchronized 是互斥锁: (2)ReentrantLock 顾名思义 :可重入锁 (3)ReadWriteLock :读写锁 读写锁特点: a)多个读者可以同时进行读b)写者必须互斥 ...

  8. 多线程里面的关键字,wait, notfiy, 锁(synchronized), lock接口

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  9. 浅谈Java中的锁:Synchronized、重入锁、读写锁

    Java开发必须要掌握的知识点就包括如何使用锁在多线程的环境下控制对资源的访问限制 ◆ Synchronized ◆ 首先我们来看一段简单的代码: 12345678910111213141516171 ...

  10. Java线程锁,synchronized、wait、notify详解

    (原) JAVA多线程这一块有点绕,特别是对于锁,对锁机制理解不清的话,程序出现了问题也很难找到原因,在此记录一下线程的执行以及各种锁. 1.JAVA中,每个对象有且只有一把锁(lock),也叫监视器 ...

随机推荐

  1. ASP.NET Core Web API 跨域(CORS) Cookie问题

    身为一个Web API,处理来自跨域不同源的请求,是一件十分合理的事情. 先上已有的文章,快速复制粘贴,启用CORS: Microsoft:启用 ASP.NET Core 中的跨域请求 (CORS) ...

  2. Java——字符串

    1.不可变的String String对象是不可变的.String类中的每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改修改后的字符串内容. public ...

  3. Linux软件的安装

    yum -y groups install "GNOME Desktop"  安装桌面系统startx  安装完成后输入指令进入到桌面化指令 安装tomcat sudo yum i ...

  4. HashMap这些问题你知道吗?

    HashMap是Java面试中的常考点之一,而且其<Key,Value>结构也是开发中常常用到的结构之一.或许你使用过HashMap,但是你知道下面这些问题吗? HashMap的底层结构是 ...

  5. Ubuntu 17 安装Chrome浏览器

    1.进入下载文件存放目录 cd Downloads 2.下载chrome文件 2.1 32位使用如下命令 wget https://dl.google.com/linux/direct/google- ...

  6. 从0到1发布一个npm包

    从0到1发布一个npm包 author: @TiffanysBear 最近在项目业务中有遇到一些问题,一些通用的方法或者封装的模块在PC.WAP甚至是APP中都需要使用,但是对于业务的PC.WAP.A ...

  7. 曹工杂谈:Java 类加载还会死锁?这是什么情况?

    一.前言 今天事不是很多,正好在Java交流群里,看到一个比较有意思的问题,于是花了点时间研究了一下,这里做个简单的分享. 先贴一份测试代码,大家可以先猜测一下,执行结果会是怎样的: import j ...

  8. print,cat打印格式及字符串引号格式,去掉字符串空格 in R

    print 函数的打印格式: ##no quote print out > x <- letters[1:5] > print(x,quote=F,);print(x,quote=T ...

  9. luoguP3588_[POI2015]PUS

    题意 有一个\(n\)个数的序列,已知其中的\(k\)个数,然后有\(m\)个信息,每个信息给出区间\([l,r]\),和\(k\)个数,表示区间\([l,r]\)中这\(k\)个数大于剩下的\(r- ...

  10. TensorFlow Object Detection API 迁移学习

    https://blog.csdn.net/ctwy291314/article/details/80999645 https://www.cnblogs.com/gmhappy/p/9472361. ...