Java高并发12-避免伪共享和锁机制

时间:2020-12-03 00:15:00 来源:互联网 作者: 神秘的大神 字体:

一、复习

二、如何避免伪共享

  • 在JDK8之前是使用填充字节的方式来避免伪共享的,我们最终的目的其实就是希望单个变量能够独占一个缓存行。
  • 我们举一个类的例子
package com.ruigege.OtherFoundationOfConcurrent2;

public class FiledLong {
 public volatile long value =0L;
 public long p1,p2,p3,p4,p5,p6;
}

  • 可以看这个类,如果cache行伪64个字节,那么正好能够占满,七个long类型的变量,其中p1-p6都是用来占位,还有一个对象的头占用八个字节,正好64个字节。
  • 在JDK8中提供了一个注解,用于避免伪共享
@sun.misc.Contended
class FiledLong2{
 public volatile long value=0L;
}
  • 用法:既可以用修饰类也可以用来修饰变量。

注意点:@Contended注解只能用于Java的核心类,比如rt包下的类,如果用户的类需要使用这个注解的时候,需要添加JVM的参数:-XX:-RestrictContended,填充的默认宽度为128,要自定义宽度可以设置-XX:ContendedPaddingWidth参数

三、出现伪共享内存的条件

  • 在多线程下访问同一个缓存行的多个变量,才会出现伪共享变量的问题,如果在单线程下访问多个变量反而会加速访问。

四、乐观锁和悲观锁

1.悲观锁

  • 定义:悲观锁认为外界对数据的修改持保守态度,认为数据很容易被外界修改,在数据被处理之前必须先加锁,这种锁是排他锁,一个线程获取了锁之后,其他线程只能等待或者抛出异常。获取锁的线程,对记录进行操作,然后提交事务释放锁。下面举一个例子
 //使用悲观锁来获取
 EntryObject entry = query("select * from table1 where id =#{id} for update",id);
 //修改记录内容,根据计算修改entry记录的属性
 String name=generatorName(entry);
 entry.setName(name);
 
 //update操作
 int count = updateZ("update table1 set name=#{name},age=#{age} where id=#{id}",entry);
 return count;
  • 对于上面的代码,使用了事务切面的方法,只要进入这个方法种就开始执行事物一直到这个方法结束,多个线程调用这个方法的时候,只有一个线程能够获取锁,其他线程就会阻塞挂起,直到原线程释放锁
  • 乐观锁是相对悲观锁而存在的的方式,一般认为如果只是访问数据那么就是可以不用加锁,只有要更新数据的时候,才会正式对数据冲突与否进行检测,具体来说,根据update返回的行数让用户决定如何去做。将上面的例子改为乐观锁。
package com.ruigege.OtherFoundationOfConcurrent2;

public class UpdateEntry2 {

 public int updateEntry(long id) {
  //使用乐观锁获取指定记录
  EntryObject entry = query("select * from table1 where id=#{id}",id);
  
  //
  String name = generatorName(entry);
  entry.setName(name);
  
  //update操作
  int count = update("update table1 set name=#{name},age=#{age},version=${version}+1 where id=#{id} and version =#{version}",entry);
  return count;
 }
}
  • 对比上面的代码就可以知道根据version来进行更新数据,更新成功的话,就会给version+1,其他线程进行更新的时候,如果vesion不对的话,那么就会停止更新。我们也是使用不断地循环来获取锁而解决更新的问题
package com.ruigege.OtherFoundationOfConcurrent2;

public class updateEntry3 {

 boolean result = false;
 int retryNum = 5;
 while(retryNum>0) {
  //使用乐观锁获取记录
  EntryObject entry = query("select * from table1 where id=#{id}",id);
  String name = generatorName(entry);
  entry.setName(name);
  
  //update操作
  int count = update("update table1 set name=#{name},age=#{age},version=${version}+1 where id=#{id} and version =#{version}",entry);
  //返回的行如果不是0的话说明更新成功了,那么即刻跳出循环
  if(count == 1) {
   result = true;
   break;
  }
  retryNum--;
  
 }
 return result;
}
  • 乐观锁并不会使用数据库提供的锁机制,一般在表中添加version字段或者使用业务状态来实现,乐观锁直到提交时才锁定,所以不会产生任何死锁。

五、公平锁和非公平锁

  • 公平锁表示线程获取锁的顺序是按照先到先得的原则,非公平锁在运行的时候闯入,也就是不一定先到先得。
  • ReentrantLock提供了公平和非公平锁。

六 、源码:

  • 所在包:com.ruigege.OtherFoundationOfConcurrent2
  • https://github.com/ruigege66/ConcurrentJava
  • CSDN: https://blog.csdn.net/weixin_44630050
  • 博客园:https://www.cnblogs.com/ruigege0000/
  • 欢迎关注微信公众号:傅里叶变换,个人账号,仅用于技术交流 1000