作者浩说编程 来源公众号:浩说编程 〔大厂技术资源研发必备安装包限时免费获取〕 通过前两篇的内容我们了解了并发的潜在问题,以及解决部分潜在问题的方法。 本篇我们继续探寻如何解决并发的原子性问题? 前文回顾 并发编程进阶一:从并发引发的潜在问题开始 并发编程进阶二:搞定可见性、有序性问题,用‘它‘就够了 读者的收获 1、锁的概念 2、Java提供的锁技术:synchronized 3、锁和资源的对应关系 4、互斥锁 码文不易 你的关注是浩说编程持续更新的动力 浩说编程会做的更好 一、锁的概念 通过之前的文章我们了解到,并发的原子性问题是由线程切换引起的。 那么我们如果能够做到在需要的时候禁止线程切换,原子性问题就能有效解决。 锁的概念由此产生,它的作用是保证我们的业务代码在同一时刻只能被一个线程所执行。 需要注意的是,锁和受保护的资源存在对应关系。 也就是说资源A的锁LOCKA只能保护资源A,资源B的锁LOCKB只能保护资源B,LOCKA无法保护B,这点很重要。 二、Java提供的锁技术:synchronized Java通过关键字synchronized来隐式的实现上边提到的锁机制,它用来修饰方法、代码块,其中的内容被叫做临界区。 在使用synchronized之后,Java会隐式的在对应方法、代码块前后分别加入加锁lock()、解锁unlock()操作。 这种隐式操作的好处是能够保证加锁和解锁一定是成对出现的,避免忘记某个操作而造成事故级别的线程等待BUG。 修饰静态方法:classTest{synchronizedstaticvoidmethod(){临界区}}12345 还记得上面提到的锁和受保护的资源的一一对应关系吗?当synchronized修饰静态方法时,受保护的资源是当前类的Class对象。 相当于:classTest{synchronized(Test。class)staticvoidmethod(){临界区}}12345 修饰非静态方法时,受保护的资源是当前类的实例对象thisclassTest{synchronizedvoidmethod(){临界区}}12345 相当于:classTest{synchronized(this)voidmethod(){临界区}}12345 修饰代码块时,受保护的资源是传递的参数,这里是obj:classTest{修饰代码块ObjectobjnewObject();synchronized(obj){临界区}}1234567 在了解了上面的内容之后,我们就可以使用synchronized来尝试解决第一篇中count1可能引发的原子性问题:classTest{intvalue0;synchronizedvoidaddOne(){value1;}}123456 补充的HappensBefore原则 还记得上一篇文章中提到的HappensBefore原则吗? 写到这里,需要补充一条关于锁的HappensBefore原则:对一个锁的解锁HappensBefore于后续对这个锁的加锁。 这个含义可以理解成前一个线程的解锁操作对后一个线程的加锁操作可见。 也就是说前一个线程在临界区修改的共享变量(该操作在解锁之前),对后续进入临界区(该操作在加锁之后)的线程是可见的。 所以对于上面这个例子,在线程1执行完addOne方法之后,线程2在进入addOne方法时看到的value一定是1,可见性问题得以保证。 三、互斥锁 我们为上面的代码例子增加一个方法,用来获取value:classTest{intvalue0;synchronizedvoidaddOne(){value1;}获取valueintgetValue(){returnvalue;}}12345678910 试想一下,当某个线程在执行addOne方法时,其他线程同时在执行getValue方法。这个时候两个方法中的value值是否一直呢? 按照现在的写法,当线程执行addOne方法时,由于getValue方法没有加锁,所以即便在addOne方法中value的值变为了1,对于getValue方法来说依然不可见,所以拿到的value不一定是1。 想要解决这个问题,我们可以利用锁的互斥性去做,互斥性是指:对于同一个锁修饰的不同方法,在同一时刻只能执行一个。 于是代码可以这样改进,在getValue方法前使用synchronized:classTest{intvalue0;synchronizedvoidaddOne(){value1;}获取valuesynchronizedintgetValue(){returnvalue;}}12345678910 这样一来,两个方法的锁都是this,所以并发的时候,如果addOne方法先拿到了this锁,那么在方法执行完并释放this锁之前,getValue方法就无法获取到this锁,也就形成了互斥关系,保证了value的可见性。 混淆的互斥锁 对于锁的互斥性,我们可能会产生混淆的情况,还是上面的例子,我们把addOne方法修改成static静态的:classTest{intvalue0;synchronizedstaticvoidaddOne(){value1;}获取valuesynchronizedintgetValue(){returnvalue;}}12345678910 结合本篇所学,试想一下这两个方法还存在互斥关系吗?你可以回看一下上面提到的互斥锁的关键点:同一个锁。 由于addOne方法变成了静态,所以锁变成了Test。Class,而getValue方法的锁是this,锁不同了自然也就不存在互斥性了,就会引发最初的可见性问题,这点需要特别注意。 以上就是本篇关于锁的知识探索,总结一下本篇的大纲:锁的概念、Java提供的锁技术:synchronized、锁和资源的对应关系、互斥锁。浩说编程,帮你学到更多。 作者浩说编程 来源公众号:浩说编程