锁的常见概念

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

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. idea 新建不了servlet文件 方法(1)

    在pem.xml中添加较新版本的servletapi包 <dependency> <groupId>javax.servlet</groupId> <arti ...

  2. ASP.NET Core MVC 之控制器(Controller)

    操作(action)和操作结果(action result)是 ASP.NET MVC 构建应用程序的一个基础部分. 在 ASP.NET MVC 中,控制器用于定义和聚合一组操作.操作是控制器中处理传 ...

  3. python编写环境(种类)

    python编写环境(种类) 官方推荐 cpython 转成C的字节码 jython转成Java的字节码 ironpython转成C#字节码 pypy转换成动态编译 开发快,运行快

  4. [实践]redhat linux5.3安装tomcat

    1.安装准备 操作系统:RedHat 5 (自带apache2.2.3) 安装tomcat前首先要安装jdk: 查看系统是否安装了jdk或tomcat的命令: rpm -qa | grep java ...

  5. android——SQLite数据库存储(创建)

    Android 专门提供了SQLiteOpenHelper帮助类,借助这个类就可以非常简单的对数据库进行创建和升级. 首先SQLiteOpenHelper是一个抽象类,在使用的时候需要创建一个自己的帮 ...

  6. Python依赖包整体迁移方法

    1.新建site-packages目录,进入到site-packages目录下: 2.在site-packages目录下执行pip freeze >requirements.txt: 3.查看r ...

  7. let 、const 、var、function声明关键字的新理解

    今天在群里看到大佬们讨论let .const 的提升问题,有个大佬问  三种声明都在什么阶段提升?  什么阶段?这个真不清楚,以前是只知道let.const存在死区,没有变量提升,一下子就懵了 后经手 ...

  8. 自定义Dialog---实现优美对话框

    PS:自定义dialog,一些系统的dialog已经不能满足开发人员的需求了,所以,我们需要自定义一个属于并且适合自己项目的对话框,无论是颜色还是功能需求上都是和自己的项目紧密相关的,一些系统的对话框 ...

  9. 学会了这些技术,你离BAT大厂不远了

    每一个程序员都有一个梦想,梦想着能够进入阿里.腾讯.字节跳动.百度等一线互联网公司,由于身边的环境等原因,不知道 BAT 等一线互联网公司使用哪些技术?或者该如何去学习这些技术?或者我该去哪些获取这些 ...

  10. 我常用的一些linux命令

    之前做过两年的运维,用过很多命令,深切体会到某些linux命令熟练掌握后对效率提升有多大.举个简单的例子,在做了研发后经常会有跑一些数据,对于结果数据的处理,我们的产品同学一般都习惯于用excel做统 ...