java并发知识 - Synchronized


Synchronized如何使用:

首先这是一个java的关键字,下面介绍如何使用它:

不使用Synchronized:

public class synchronizeddemo {

    public static int a = 0;
    public static void add(){
        a++;
    }
    public static void main(String[] args) {
        ExecutorService exes = Executors.newCachedThreadPool();
        for(int i=0;i<5000;i++){
            exes.execute(()->{
                add();
            });
        }
        exes.shutdown();
        System.out.println(a);
    }
}

结果:4879

由此可以看出add不是线程安全的方法。

使用方式一: Synchronized作用在静态方法上

public class synchronizeddemo {

    public static int a = 0;
    public synchronized static void add(){
        a++;
    }
    public static void main(String[] args) {
        ExecutorService exes = Executors.newCachedThreadPool();
        for(int i=0;i<5000;i++){
            exes.execute(()->{
                add();
            });
        }
        exes.shutdown();
        System.out.println(a);
    }
}

结果:5000

由此可以看出add变成线程安全的方法了。

注意:当Synchronized作用在静态方法上的时候,它锁定的是这个类synchronizeddemo.class对象锁。

使用方式二:Synchronized作用在普通方法上

public class synchronizeddemo {

    public int a = 0;
    public synchronized void add(){
        a++;
    }
    public void sout(){
        System.out.println(a);
    }
    public static void main(String[] args) {
        synchronizeddemo s = new synchronizeddemo();

        ExecutorService exes = Executors.newCachedThreadPool();
        for(int i=0;i<5000;i++){
            exes.execute(()->{
                s.add();
            });
        }
        exes.shutdown();
        try {
            Thread.sleep(300);
            s.sout();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
结果:5000

注意:注意上述两个例子,因为静态方法不属于实例对象,普通方法作用实例对象,所以一个线程调用静态方法,另一个线程调用非静态方法,二者不会互斥。

使用方法三:synchronized同步代码块

public class synchronizeddemo {
    public int a = 0;

    public void add(){
        a++;
    }
    public void sout(){
        System.out.println(a);
    }
    public static void main(String[] args) {
        synchronizeddemo s = new synchronizeddemo();

        ExecutorService exes = Executors.newCachedThreadPool();
        for(int j=0;j<10;j++){
            exes.execute(()-> {
                synchronized (s){
                    for (int i = 0; i < 100; i++) {
                        s.add();
                    }
                }
            });
        }
        exes.shutdown();
        try {
            Thread.sleep(300);
            s.sout();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:1000

注意:当需要同步的方法比较多,可以用Synchronized圈住一个代码块,注意 synchronized (s){...}实际上锁住的是当前对象实例。

Synchronized底层原理

首先我们编写两种锁的实现方式

1、将synchronized写在方法上

2、将synchronized写在方法里的this上

直接查看字节码指令


我们可以看到红色箭头的地方,有两条字节码指令,monitorenter和monitorexit。

monitorenter表明了代码的开始位置,当前线程试图获取test2对象实例的锁,如果锁计数器为0,那么成功取得monitor,并将计数器置位1,monitorexit标明了截止位置,运行该指令后计数器减1,最后计数器为0时,其他线程才有机会获得这个对象实例的锁。如果有异常发生,会默认执行第二个monitorexit,强制释放锁。这就是JVM帮我们做到的锁的控制。

额外知识点:当一个拥有该实例锁的线程再次执行该实例的方法(i++)这是计数器会变成2(1+1),这就是可重入锁!同理释放也是不断减一,减到0后释放锁。


偏向锁:

在无竞争的环境下,当一个线程拥有一个锁,再次请求锁定的资源时,不会进行额外的申请锁释放锁步骤,而是直接使用资源。

轻量级锁:

在竞争环境下,偏向锁失效后会升级为轻量级锁,轻量级锁适用于线程交替执行同步块的场合。

自旋锁:

说白了就是暂时的自我循环等待,不让线程进入内核态的阻塞,一旦得到锁就执行程序,否则到一定时候就要进入内核态挂起。


猜你喜欢

转载自blog.csdn.net/qq_31615049/article/details/80588284