1:基础概念

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

原子操作:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程),是不需要synchronized,通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。

例如下面的例子 IncrementAndGet类中的next()方法是一个自增1操作,为了保证线程安全加了synchronized关键字

public class IncrementAndGet() {
private int value;
   public synchronized int next(){
return value++;
   }
}

这种加锁的方式属于悲观锁的方式,效率太低。可以采用例外一种方式处理:

1. 从内存中读取value 值,假设为10, 我们把这个值称为A

2. B = A+1 得到 B = 11

3. 用A 和 内存的值相比, 如果相等(就是说在过去的一段时间,没人修改内存的值), 那就把B的值(11)写入内存,  如果不相等(就是说过去的一段时间, 有人修改了内存value 的值), 意味着A已经不是最新值了, 那就放弃这次修改, 跳回第1步去”

第三步其实就是一条硬件指令,保证原子执行。 在单个CPU上就不用说了,如果是有多个CPU, 这个指令甚至会锁住总线, 确保同一时刻只有一个CPU能访问内存!

final AtomicInteger value = new AtomicInteger(10);
@Test
public final int test1(){
for(;;){
int current = value.get();//获取当前值
int next = current+1; //设置期望值
if(value.compareAndSet(current, next)){
return next;
}
}
}

AtomicInteger类compareAndSet通过原子操作实现了CAS操作,最底层基于汇编语言实现。

CAS是Compare And Set的一个简称,如下理解:

1,已知当前内存里面的值current和预期要修改成的值new传入

2,内存中AtomicInteger对象地址对应的真实值(因为有可能别修改)real与current对比,

相等表示real未被修改过,是“安全”的,将new赋给real结束然后返回;不相等说明real已经被修改,结束并重新执行1直到修改成功

我们仔细地审视这段代码, 它根本没有加锁, 其他线程都可以进入next()方法, 读取数据,操作数据, 最后使用CAS来决定这次操作是否有效, 如果内存值被别人改过,那就再次循环尝试,这就采用了乐观锁的方式。

为了说明AtomicInteger的原子性,这里代码演示多线程对一个int值进行自增操作,最后输出结果,代码如下:

public class AtomicIntegerDemo {

    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args){
for (int i = 0; i < 5; i++){
new Thread(new Runnable() {
public void run() {
//调用AtomicInteger的getAndIncement返回的是增加之前的值
System.out.println(atomicInteger.getAndIncrement());
}
}).start();
}
System.out.println(atomicInteger.get());
}
}

输出结果如下:

0
2
1
3
4
4

原子更新数组

通过原子更新数组里的某个元素,共有3个类:

  • AtomicIntegerArray:原子更新整型数组的某个元素
  • AtomicLongArray:原子更新长整型数组的某个元素
  • AtomicReferenceArray:原子更新引用类型数组的某个元素

AtomicIntegerArray常用的方法有:

  • int getAndSet(int i, int delta):以原子方式将输入值与数组中索引为i的元素相加
  • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式更新数组中索引为i的值为update值
public class AtomicIntegerArrayDemo {
static int[] value = new int[]{1, 2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value); public static void main(String[] args){
ai.getAndSet(0,3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}

运行结果是:

  3 
  1

数组value通过构造的方式传入AtomicIntegerArray中,实际上AtomicIntegerArray会将当前数组拷贝一份,所以在数组拷贝的操作不影响原数组的值。

原子更新引用类型

需要更新引用类型往往涉及多个变量,早atomic包有三个类:

  • AtomicReference:原子更新引用类型
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
  • AtomicMarkableReference:原子更新带有标记位的引用类型。

下面以AtomicReference为例进行说明:

public class AtomicReferenceDemo {

    static class User{
private String name;
private int id; public User(String name, int id) {
this.name = name;
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getId() {
return id;
} public void setId(int id) {
this.id = id;
}
} public static AtomicReference<User> ar = new AtomicReference<User>(); public static void main(String[] args){
User user = new User("aa",11);
ar.set(user);
User newUser = new User("bb",22);
ar.compareAndSet(user,newUser);
System.out.println(ar.get().getName());
System.out.println(ar.get().getId());
}
}

运行结果为:

bb 
22

可以看到user被成功更新。

原子更新字段类

如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:

  • AtomicIntegerFieldUpdater:原子更新整型字段
  • AtomicLongFieldUpdater:原子更新长整型字段
  • AtomicStampedReference:原子更新带有版本号的引用类型。

要想原子更新字段,需要两个步骤:

  1. 每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
  2. 更新类的字段(属性)必须为public volatile

下面的代码演示如何使用原子更新字段类更新字段:

public class AtomicIntegerFieldUpdaterDemo {

    //创建一个原子更新器
private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(User.class,"old"); public static void main(String[] args){
User user = new User("Tom",15);
//原来的年龄
System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
//现在的年龄
System.out.println(atomicIntegerFieldUpdater.get(user));
} static class User{
private String name;
public volatile int old; public User(String name, int old) {
this.name = name;
this.old = old;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getOld() {
return old;
} public void setOld(int old) {
this.old = old;
}
}
}

输出的结果如下:

15 
16

最新文章

  1. 微信小程序demo2
  2. 用JS实现的类似QQ密码的输入特效
  3. java连接各种数据库代码大全
  4. ASP.NET MVC报错: Multiple types were found that match the controller named
  5. jQuery--Dom元素隐藏和显示原理(源码2.0.3)
  6. Struts2.3.16.3 基本9个jar包
  7. 非常基本的SQL 内外连接
  8. (转)如何在maven的pom.xml中添加本地jar包
  9. Go vs .NET Core 2.1
  10. 2018项目UML设计-课堂实战
  11. mac sed 使用踩坑实录
  12. golang结构体、接口、反射
  13. Linux-C-Program:makefile
  14. rownum和分析函数 over
  15. (转载)Unity里实现更换游戏对象材质球
  16. 杂记-格式化Date默认格式,日期加一天,jstl判断字符类型,ajax模拟from表单后台跳转页面,jstl访问数据库并在页面显示
  17. MySQL数据库应用 从入门到精通 学习笔记
  18. CentOS7服务器搭建百度贴吧云签到
  19. phpstrrchr()函数的问题
  20. java代码多线程实现如下

热门文章

  1. (转)Spring Boot 2 (四):使用 Docker 部署 Spring Boot
  2. Linux crm 运行
  3. nginx + uwsgi 部署 Django+Vue项目
  4. Vim怎么批量处理文件将tab变为space
  5. 深入理解Zuul之源码解析
  6. Harbor是什么
  7. [转]Jquery 点击图片在弹出层显示大图
  8. Clickhouse副本表以及分布式表简单实践
  9. 8.oop-多态
  10. Python实现机器人聊天