Java并发编程笔记1-竞争条件&初识原子类&可重入锁

时间:2022-03-16 18:33:01

我们知道,在多线程访问一个共享变量的时候会发生安全问题。

首先看下面例子:

public class Counter {
private int count;

public void add(){
try{
for (int i = 0;i<200;i++){
Thread.sleep(
100);
this.count++;
System.out.println(
this.count);
}
}
catch (Exception e ){
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
final Counter counter = new Counter();

new Thread(new Runnable() {
public void run() {
counter.add();
}
}).start();

new Thread(new Runnable() {
public void run() {
counter.add();
}
}).start();

new Thread(new Runnable() {
public void run() {
counter.add();
}
}).start();
}
}

运行结果如下:

Java并发编程笔记1-竞争条件&初识原子类&可重入锁

如果没发生线程对数据的赃操作,3个线程执行,结果会累加到600的,看上面结果明显没有,并且出现一下重复的数据。这是因为存在3个线程同时操作同一个数字,导致输出重复数字。

 

解决办法:

  1.在方法上加上synchronized关键字。如下图:

  Java并发编程笔记1-竞争条件&初识原子类&可重入锁

虽然结果不会出现重复,但是synchronized效率及其低下,运行速度明显被拖慢。原因是,for循环中的i变量是每个线程都有独自的局部变量,各不影响,但是每个线程都要排队进入这个方法,排队睡觉,这样就导致效率低下

 

  2.在竞争条件上加synchronized,我们知道各个线程其实竞争的是count这个成员变量。因此在此地方加即可。如下图:

  Java并发编程笔记1-竞争条件&初识原子类&可重入锁

这样运行效率比方法一快了很多,因为省去了排队进入方法,排队睡觉。只需要排队取count值即可,这样效率比方法一快。

注意上图中,输出语句并不是竞争条件,并不一定要放在synchroized里面,这里放在里面是为了让线程取到值自增后立即输出,这样输出就不会发生混论,不发生抢占输出问题,一样能累加到600,

如果把输出放在synchronized外面会出现值有重复现象,因为累加后的值并没有立即输出,这样导致输出混乱,但仍然能加到600.知识输出不安全罢了。

 

  3.使用原子类型,比如将上面的代码的int count类型改成AtomicInteger count类型,我们知道获取count的值然后再自加个1是可能会出现问题的,也就是结果出现重复数字。AtomicInteger类型是以同步的方法解决这个问题的。如下图:

Java并发编程笔记1-竞争条件&初识原子类&可重入锁

结果如下图:

Java并发编程笔记1-竞争条件&初识原子类&可重入锁

可以看到数字的输出没有严格的排队,但是数据确实给你保证的了,就是完整的加到600.这也恰恰因为不是严格的进行排队,才是的这种方法比前面两种方法的效率大大改进。

 

理论总结:synchronized是一种内部锁,就是所对象内部给我们提供的,因为每一个对象有一个状态变量,相当于一个锁,进入同步块,改变这个变量。别的线程进入之后就要判断这个变量有没有改变。

 

一个线程获取它本生已经持有的锁,这是可以成功的。我们知道多个线程同时抢占同一个锁它们是失败的。因为它们之间是互斥的。但是呢,一个线程再次获取一个自己已经拿过的锁是可以成功的,那么它是能够成功的。

看如下例子:

public class Widget {

public synchronized void doSth(){

}

}
public class ChildWidget extends Widget {
@Override
public synchronized void doSth() {
super.doSth();
}
}
public class Test {
public static void main(String[] args) {

Widget w
= new ChildWidget();
w.doSth();
}
}

子类调用自己的方法的那个synchroized那个锁是w对象的这个锁,而在子类方法中的super.doSth()父类中的方法的synchronized的锁也是w对象的锁。因此不要被子类继承父类的方法中的锁所迷惑了。

因此这种方式叫做内部锁的可重入机制,也叫可重入锁。