天天看点

Java线程安全问题(3)之Java锁相关

一.锁的理解

锁这个东西可以理解为对一份资源的一份合同,当你需要使用这个资源时,你需要签订这份合同,而根据占有线程的要求或其他要求,可能导致合同的内容存在不同的差异,因此又衍生出了不同种类的锁.

二.锁的种类

1.自旋锁

当一个线程在获取锁的时候,如果锁已经被其他线程过去,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环.例如cas操作中通过自旋修改i的值.

2.乐观锁

假定没有冲突,在修改数据时,如果发现数据和之前获取的不一致,则读取最新的数据,修改后重试修改.

3.悲观锁

假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始加锁.

#ps:乐观锁和悲观锁的区别在于,乐观锁只在

写数据时还会,对该数据进行比对,如果不同则获取,而悲观锁从读数据就开始加锁,性能不如乐观锁,常见的乐观锁如cas操作

4.独享锁(写锁)

独享锁就是我们常见的锁,给线程加上锁之后,只有该线程可以对数据进行访问,其他线程不可以再访问该数据.

5.共享锁(读锁)

共享锁是一种可以允许多个线程同时去读,但是不允许其他线程进行修改,及对线程加读锁后不可以再想该线程添加写锁.

6.可重入锁

一个线程加锁后,该线程还可以继续向该资源进行加锁的操作.例如Synchronized,ReentrantLock.

7.不可重入锁

一个线程加锁后,该线程也无法继续给自己加锁.

8.公平锁和非公平锁

是否按照先来先得的方式获得

三.Synchronized关键字使用

1.用在普通方法上的对象锁

该用法把Synchronized放在普通的方法上,然后在使用使,会禁止同一个对象再次访问该方法.而对于该类下的其他对象访问该方法时不会进行限制.

2.用在静态方法上的类锁

该方法是把Synchronized放在静态方法上,这样在同一时刻,该类下的所有对象只有一个可以执行该方法,因此也称为类锁.

源码如下:

public class SynchronizedTest {
    synchronized public void test1(){
        System.out.println(Thread.currentThread().getName()+"业务逻辑执行中");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    synchronized public static void test2(){
        System.out.println(Thread.currentThread().getName()+"业务逻辑执行中");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        SynchronizedTest test=new SynchronizedTest();
        Thread thread=new Thread(()->{
            SynchronizedTest test1=new SychronizedTest();
           test1.test2();
        });
//        SynchronizedTest test=new SynchronizedTest();
        thread.start();
        test.test2();

    }
}
           

3.synchronized代码块语法糖

synchronized(this){

方法

};这样相当于对于普工方法增加sychrongized是一个对象锁.

synchronized(类.class){

};相当于静态方法增加sychronized,是一个类锁.

*ps:类对象不会再两个jvm中产生限制

synchronized属于可重入,独享,悲观锁

四.synchronized的底层原理

1.对象在内存中存储

对象的实例字段及其对应的值存在堆中,方法存储在方法区中. 静态字段不会存在堆里.

这里重点是在堆里存储了一个对象头,对象头中存在一个引用,该引用指向了对应的类.

对象头中的内容包括:

Mark Word

Class Meta address

Arraylength

synchronized锁的标记状态及存储在MarkWord中

2.Mark Word详解

Bitfields Tag State
Hashcode Age 01 Unlocked (未锁定)
ThreadId Age 1 01 Biased/biasable (偏向锁)
Lock record address 00 Light-weight locked (轻量级锁)
Monitor address 10 Heavy-weight locked (重量级锁)
Forwarding addresss,etc 11 Marked for GC

(markword长度由造作系统决定,如果操作系统为32位则长度为32.如果操作系统为64位则长度位64.)

1.初次抢锁:当还为锁定时,MarkWord的内容为表中第一行中的内容,如果有线程来进行加锁,该线程会把第一行内容拷贝到栈帧中,栈帧名字为LockRecord,然后进行cas操作,对MarkWord进行修改,修改为第三行中的内容.如果修改成功,则成功加锁.否则则失败进行自旋,当自旋到一定的次数后进行升级.(如果cas中没有复制到为初次的状态,则直接升级)

2.抢锁失败的线程会进行自旋,当自旋到一定程度,会进行锁升级,将轻量级锁中的内容进行修改,修改为第四行中的锁.(每个对象都可能存在一个对象监视器,对象监视器会把这个锁进行升级).Mointor中存在着owner和entryList其中owner代表当前持有锁的线程.entryList存储为没抢到锁的线程.并把该线程挂起进入阻塞状态.

3.偏向锁:这个锁不会对锁进行释放.当其他线程来抢占时,会进行锁升级.然后回退会关闭偏向锁.但是锁不会修改为未锁定的状态(jdk1.6后平时在使用时偏向锁会自动开启.通过jvm参数-XX:–UseBissedLocking来禁用偏向锁)

3.synchronized和wait/notify

使用wait/notify后可以将锁进行释放,使线程进入到waiting状态.当其他线程进行唤醒时,线程会进入到阻塞状态,等待获取资源.

总结:

锁这个东西本质就是一份合同,配合着不同的场景会产生不同的效果.同时一个锁可能同时属于多种锁.但是悲观锁和乐观锁不共存,可重入和不可重入不可共存,独享和共享锁不可共存.由于所学有限,只能写这样了.忘批评指正

继续阅读