Java并发入门之互斥锁-解决原子性问题

Posted BangBoom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发入门之互斥锁-解决原子性问题相关的知识,希望对你有一定的参考价值。

原子性问题主要由多核情况的线程切换引起。

引发解决思路:线程切换不单单禁止CPU中断就可以解决,如果多核情况下,每个都有多个线程,中断其中一个CPU的其中一个线程切换,其他CPU也有权限进来操作共享变量。所以java的创建一个互斥锁的概念,保证多个线程同一个共享对象的修改是互斥的。

简易锁

lock unlock
缺陷:但是没有根据锁的是什么资源,不知道保护是什么资源。

改进后的锁模型

锁应该对应锁的资源,就是你用锁锁你的钱财,还是你的笔记本,具体到一个物品。

把要保护的资源体现出来,这个资源创建一把锁LR,用来锁住和解锁操作。还要和资源进行关联。
Java并发入门之互斥锁-解决原子性问题-

锁技术:synchronize

特点:可以用来修改非静态方法,静态方法,方法块。而且可以自动上锁和自动解锁,不用担心忘记解锁的情况。

特点2:非静态方法锁的当前实例对象this;静态锁是类的class对象,例如下面代码的X.class。

class X { // 修饰非静态方法 synchronized void foo() { // 临界区 } // 修饰静态方法 synchronized static void bar() { // 临界区 } // 修饰代码块 Object obj = new Object(); void baz() { synchronized(obj) { // 临界区 } }} 

修饰非静态方法

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

修饰静态方法

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

Java并发入门之互斥锁-解决原子性问题

用 synchronized 解决 count+=1 问题

class SafeCalc { long value = 0L; long get() { return value; } synchronized void addOne() { value += 1; }}

如果有多个线程使用一个资源,每个方法都要加上锁,否则会有可见性问题。因为addOne()的value虽然对其他线程是可见的( 解锁前的操作对于后续的解锁是可见的并且根据线程的传递性规则说明是可见的。。根据Happens-Before的管程中锁的规则),但是get()的value资源确是不可见的,因为你不是在没有解锁和加锁的操作,不符合happens-before中的规则,所以不可见。必须get()加锁才符合规则,才是可见的。注意两个方法用的都是同一把锁对象this,即如果线程A访问addONe()方法,线程B没有钥匙可以打开get()方法。(下图代码实验)

Java并发入门之互斥锁-解决原子性问题

锁与受保护的资源的关系

关系:现实中多个锁可以保护同一资源(一个房间可以加多个锁),但是并发中一个锁可以保护多个资源(电影院包场,一张票锁多个位置(不太确定))。

class SafeCalc { static long value = 0L; synchronized long get() { return value; } synchronized static void addOne() { value += 1; }}

注意:如果两个方法锁的是同一个资源,一个是非静态和一个静态方法,锁的对象是不同的,前者是this,后者是class对象,就是说多个线程在两个方法之间都是没有互斥关系,随便进出的导致并发问题。

Java并发入门之互斥锁-解决原子性问题

测试代码(相同锁,不同方法,只能锁一个)

两个方法都是非静态方法。

public class demo02 { // 以下代码来源于【参考 1】 int x = 0; volatile boolean v = false; public synchronized void writer() { x = 42; v = true; System.out.println(new Date()+"我写入啦"+Thread.currentThread()); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void reader() {
System.out.println(new Date()+"我读到啦"+Thread.currentThread()); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } }
public static long calc() throws InterruptedException { final demo02 test = new demo02(); // 创建两个线程,执行 add() 操作 Thread th1 = new Thread(()->{ test.writer(); }); Thread th2 = new Thread(()->{ test.reader(); }); // 启动两个线程 th1.start(); th2.start(); // 等待两个线程执行结束 th1.join(); th2.join(); return 1; }
public static void main(String[] args) throws InterruptedException { calc();
}
}

运行结果

测试代码2(不同锁,不同方法,没有互斥效果)

reader() 改为静态方法
writer() 非静态方法

public class demo02 {  int x = 0; volatile boolean v = false; public synchronized void writer() { x = 42; v = true; System.out.println(new Date()+"我写入啦"+Thread.currentThread()); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized static void reader() {
System.out.println(new Date()+"我读到啦"+Thread.currentThread()); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } }
public static long calc() throws InterruptedException { final demo02 test = new demo02(); // 创建两个线程,执行 add() 操作 Thread th1 = new Thread(()->{ test.writer(); }); Thread th2 = new Thread(()->{ test.reader(); }); // 启动两个线程 th1.start(); th2.start(); // 等待两个线程执行结束 th1.join(); th2.join(); return 1; }
public static void main(String[] args) throws InterruptedException { calc();
}
}

运行结果

总结

受保护的资源的路径:球场的入口,类似java的main入口。
锁:相当于门票。
保护的资源:相当于座位。


以上是关于Java并发入门之互斥锁-解决原子性问题的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程实战之互斥锁

Java并发编程实战 04死锁了怎么办?

Java并发编程实战 04死锁了怎么办?

并发编程之互斥锁

Java并发工具类原子类

Day840.原子类-Java 并发编程实战