synchronized关键字再回顾-脏读-可重入-锁升级

当我们访问某些方法会加上关键synchronized ,以HashTable为例的put方法为例,我们知道hashtable是线程安全的,为什么是线程安全的?他的put方法源码如下

 public synchronized V put(K var1, V var2) {
        if (var2 == null) {
            throw new NullPointerException();
        } else {
            Hashtable.Entry[] var3 = this.table;
            int var4 = var1.hashCode();
            int var5 = (var4 & 2147483647) % var3.length;

            for(Hashtable.Entry var6 = var3[var5]; var6 != null; var6 = var6.next) {
                if (var6.hash == var4 && var6.key.equals(var1)) {
                    Object var7 = var6.value;
                    var6.value = var2;
                    return var7;
                }
            }

            this.addEntry(var4, var1, var2, var5);
            return null;
        }
    }

其中他调用了addEntry方法,

private void addEntry(int var1, K var2, V var3, int var4) {
        ++this.modCount;
        Hashtable.Entry[] var5 = this.table;
        if (this.count >= this.threshold) {
            this.rehash();
            var5 = this.table;
            var1 = var2.hashCode();
            var4 = (var1 & 2147483647) % var5.length;
        }

        Hashtable.Entry var6 = var5[var4];
        var5[var4] = new Hashtable.Entry(var1, var2, var3, var6);
        ++this.count;
    }

在他的底部有一个++this.count

我们都知道++i不是原子操作,线程在工作的时候,每个线程都有自己的独立工作空间,当他们对自己本地缓存数据做修改的时候另外的线程并不知道,所以就会造成了这种数据不一致原子性问题问题,synchronized保证了线程的原子性和内存可见。

synchronized特性

1.简单加锁的方式

private  int count=10;
public void  test(){

synchronize(this){
count--;
}
}

2.

private int  count=10;
public  synchronized  void  test2(){
      count--;
}

上面这两种锁的方式是其实是等价的。都是锁定当前对象。

静态方法的锁

    静态方法是没有this的对象的,当静态方法增加synchronized的代表synchronized(T.class)。

这个锁就是锁的Class对象。

T.class是单例的吗?

一般情况下,在同一个类加载器空间他一定是。不是同一个类加载器就不是,不同的类加载器之间也不能访问。所以能访问那他一定是单例的。

脏读

假设有这样的业务场景,假设张三有100块钱,刚开始起个线程初始化100块钱但是在初始化的时候sleep了两秒,也就是他的set方法sleep了两秒,注意set方法加锁了,初始化完之后后面直接掉get获取的 方法,代码如下:

package com.tom.syn;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Account {
   private String  name;
   private double balance;

   public  synchronized void set(String  name,double  balance){
       this.name=name;

       try {
           Thread.sleep(2000);
       }catch (Exception e){
           e.printStackTrace();
       }
       this.balance=balance;
   }

    public static void main(String[] args) {
        Account  account=new Account();
        new Thread(()->account.set("zhangsan",100.0)).start();;
      
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(account.getBalance("zhangsan"));
        try {
            TimeUnit.SECONDS.sleep(2);
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(account.getBalance("zhangsan"));
   }


    public double getBalance(String  name) {
        return this.balance;
    }


}

直接说结果:

0.0
100.0

由于getBalance没有加锁,所以他不需要等待由于初始化的时候sleep了两秒所以在打印第一行的时候打印出来的结果是0.00

第二次打印因为已经初始化过了所以打印的是100.0

这就是一种脏读现象。

如果我们对读操作也加锁,getBalance方法增加synchronized,因为都是this锁所以他们是同一把锁,所以读操作会等写操作完成了之后才会进行下一步操作,保证了数据的准确性。

可重入

synchronized可重入是为了解决自己死锁的问题提出来的。

synchronized(this){

i++;

synchronized(this){
j++;
  }

}

当一个同步方法调用另一个同步方法时,外层的方法已经拿到锁,当他进入内部时发现也需要锁,然后发现锁是它自己的锁,我需要,你也需要,如果不解决这种情况就出现了死锁的情况。可重入的概念就是这样提出来的,当他发现是同一个线程申请这个锁的时候,允许,这就叫做可重入做。

锁升级

早期的jdk,这个synchronized的底层实现是重量级的重量级到这个synchronized要去操作系统去申请锁的地步,这就造成了synchronized的效率非常低。后来就有了锁升级的概念。

HotSpot的实现:如果只有第一个线程访问的时候实际上是没有给这个资源加锁的,在内部实现的时候,只是记录了这个线程的id。这时是偏向锁

偏向锁如果有线程竞争的话就会升级会自旋锁

自旋锁转圈10次之后就会升级会重量级锁,重量级锁就是去操作系统那里申请资源,这就是锁升级的一个全过程。

所以并不是cas的效率就一定比系统锁的效率要高,这个要区分实际情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值