1.2.3 Java锁相关 - synchronized详解

Java中锁的概念

  • 自旋锁:为了不放弃CPU执行事件,循环的使用CAS技术对数据尝试进行更新,直至成功。
  • 悲观锁:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。
  • 乐观锁:假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改。
  • 独享锁(写):给资源加上写锁,线程可以修改资源,其他线程不能再加锁。(单写)
  • 共享锁(读):给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁。(多读)
  • 可重入锁、不可重入锁:线程拿到一把锁之后,可以自由进入同一把锁所同步的其他代码。
  • 公平锁、非公平锁:争抢锁的顺序,如果是按先来后到,则为公平。

几种重要的锁实现方式:SynchronizedReentrantLockReentrantReadWriteLock

同步关键字synchronized

属于最基本的线程通信机制,基于对象监视器实现的。

Java中的每个对象都与一个监视器相关联,一个线程可以锁定或解锁。

一次只有一个线程可以锁定监视器。

试图锁定该监视器的任何其他线程都会被阻塞,直到它们可以获得该监视器上的锁定为止。

特性: 可重入、独享、悲观锁

锁的范围: 类锁、对象锁、锁消除、锁粗化。

提示:同步关键字,不仅是实现同步,根据JMM规定还能保证可见性(读取最新主内存数据,结束后写入主内存)。

同步关键字加锁原理

在这里插入图片描述

同步关键字加锁原理 - 轻量级锁
在这里插入图片描述

在这里插入图片描述

偏向锁到轻量级锁

在这里插入图片描述

重量级锁 - 监视器(monitor)

在这里插入图片描述

过程解析

第一次取得锁时会在对象的Markword标志为偏向锁,此时同一个用户来使用,不用进行取锁操作,直到有其他的用户也想取这个锁,对象的Markword标志就会升级为轻量级锁,若上一个用户正在使用,这个用户自旋重锁,自旋CAS,若没有在使用,则直接开始竞争,若在并发高时,上个用户使用时间过长,这个用户不断的自旋重锁达到一定的次数(可以自己设置),则会升级为重量级锁,在每次自旋的时候会等待一段时间,以节约CPU资源。

锁消除、锁粗化

  • 锁消除:在热点代码,运行时Jit编译优化,将两个锁变成一个更大的锁包含两个内容。
  • 锁粗化:在热点代码,运行时Jit编译优化,把锁消除,进入时不需要监视器。

类锁和对象锁

我们都知道类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。

静态方法锁和方法锁的区别就在于,类的每个对象静态方法是一样的,所以静态方法锁相当于类锁,而类的每个对象方法是不一样的,所以方法锁就相当于对象锁。

//对象锁
public class ObjectSyncDemo1 {

    public void test1() {
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread() + " 我开始执行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我执行结束");
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒钟,让前一个线程启动起来
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
输出结果:
Thread[Thread-0,5,main] 我开始执行
Thread[Thread-1,5,main] 我开始执行
Thread[Thread-0,5,main] 我执行结束
Thread[Thread-1,5,main] 我执行结束
//类锁
public class ObjectSyncDemo1 {

    public void test1() {
        synchronized (ObjectSyncDemo1.class) {
            try {
                System.out.println(Thread.currentThread() + " 我开始执行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我执行结束");
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒钟,让前一个线程启动起来
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
输出结果:
Thread[Thread-0,5,main] 我开始执行
Thread[Thread-0,5,main] 我执行结束
Thread[Thread-1,5,main] 我开始执行
Thread[Thread-1,5,main] 我执行结束
//方法锁
public class ObjectSyncDemo1 {
    
    public synchronized void test1() {
            try {
                System.out.println(Thread.currentThread() + " 我开始执行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我执行结束");
            } catch (InterruptedException e) {
            }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒钟,让前一个线程启动起来
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
输出结果:
Thread[Thread-0,5,main] 我开始执行
Thread[Thread-1,5,main] 我开始执行
Thread[Thread-0,5,main] 我执行结束
Thread[Thread-1,5,main] 我执行结束
//静态方法锁
public class ObjectSyncDemo1 {

    public static synchronized void test1() {
            try {
                System.out.println(Thread.currentThread() + " 我开始执行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我执行结束");
            } catch (InterruptedException e) {
            }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒钟,让前一个线程启动起来
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
输出结果:
Thread[Thread-0,5,main] 我开始执行
Thread[Thread-0,5,main] 我执行结束
Thread[Thread-1,5,main] 我开始执行
Thread[Thread-1,5,main] 我执行结束

synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。

当然同步方法和同步代码块都会有这样的缺陷,只要用了synchronized关键字就会有这样的风险和缺陷。既然避免不了这种缺陷,那么就应该将风险降到最低。这也是同步代码块在某种情况下要优于同步方法的方面。例如在某个类的方法里面:这个类里面声明了一个对象实例,SynObject so=new SynObject();在某个方法里面调用了这个实例的方法so.testsy();但是调用这个方法需要进行同步,不能同时有多个线程同时执行调用这个方法。

这时如果直接用synchronized修饰调用了so.testsy();代码的方法,那么当某个线程进入了这个方法之后,这个对象其他同步方法都不能给其他线程访问了。假如这个方法需要执行的时间很长,那么其他线程会一直阻塞,影响到系统的性能。

如果这时用synchronized来修饰代码块:synchronized(so){so.testsy();},那么这个方法加锁的对象是so这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法时没有影响的,因为他们持有的锁都完全不一样。

可重入锁

public class ObjectSyncDemo2 {

    public synchronized void test1(Object arg) {
        System.out.println(Thread.currentThread() + " 我开始执行 " + arg);
        if (arg == null) {
            test1(new Object());
        }
        System.out.println(Thread.currentThread() + " 我执行结束" + arg);
    }

    public static void main(String[] args) throws InterruptedException {
        new ObjectSyncDemo2().test1(null);
    }
}
输出结果:
Thread[main,5,main] 我开始执行 null
Thread[main,5,main] 我开始执行 java.lang.Object@31b7dea0
Thread[main,5,main] 我执行结束java.lang.Object@31b7dea0
Thread[main,5,main] 我执行结束null

我们可以看出synchronized是个可重入锁,在某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

你可能感兴趣的:(1.2.3 Java锁相关 - synchronized详解)