共享模型之无锁(CAS和valotile)

共享模型之无锁-CAS

  • 取款案例
  • 解决方式
    • 解决方式一
    • 解决方式二
    • 总结方式二---无锁CAS
    • CPU核心数

取款案例

public class TestAccount {

    public static void main(String[] args) {
    	//初始化余额为10000
        Account account = new AccountUnsafe(10000);
        //1000个线程同时运行
        Account.demo(account);
    }
}
class AccountUnsafe implements Account {

    private Integer balance;

    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }

    @Override
    public Integer getBalance() {
        //synchronized (this) {
            return this.balance;
        //}
    }

    @Override
    public void withdraw(Integer amount) {
        //synchronized (this) {
            this.balance -= amount;
        //}
    }
}

interface Account {
    // 获取余额
    Integer getBalance();

    // 取款
    void withdraw(Integer amount);

    /**
     * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
     * 如果初始余额为 10000 那么正确的结果应当是 0
     */
    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        //定义开始时间
        long start = System.nanoTime();
        //1000个线程同时运行
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
            //必须使1000个线程同时运行完成,主线程main才能结束
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //定义结束时间
        long end = System.nanoTime();
        //输出余额和运行时间
        System.out.println(account.getBalance()
                + " cost: " + (end-start)/1000_000 + " ms");
    }
}

结果:

第一次:
2020 cost: 187 ms
第二次:
2370 cost: 193 ms
第三次:
4340 cost: 214 ms

每次运行结果都不同,说明多个线程并发运行对会对共享变量产生影响。多个线程交错访问使 account.withdraw 取款方法的线程不安全。

解决方式

解决方式一

加synChronized对象锁,先简单了解一下,下节介绍。

public class TestAccount {

    public static void main(String[] args) {
    	//初始化余额为10000
        Account account = new AccountUnsafe(10000);
        //1000个线程同时运行
        Account.demo(account);
    }
}
class AccountUnsafe implements Account {

    private Integer balance;

    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }

    @Override
    public Integer getBalance() {
        synchronized (this) {
            return this.balance;
        }
    }

    @Override
    public void withdraw(Integer amount) {
        synchronized (this) {
            this.balance -= amount;
        }
    }
}
interface Account {
    // 获取余额
    Integer getBalance();

    // 取款
    void withdraw(Integer amount);

    /**
     * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
     * 如果初始余额为 10000 那么正确的结果应当是 0
     */
    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        //定义开始时间
        long start = System.nanoTime();
        //1000个线程同时运行
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
            //必须使1000个线程同时运行完成,主线程main才能结束
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //定义结束时间
        long end = System.nanoTime();
        //输出余额和运行时间
        System.out.println(account.getBalance()
                + " cost: " + (end-start)/1000_000 + " ms");
    }
}

只需对余额balance的操作加上synChronized重量级锁。

解决方式二

使用CAS无锁方式

package com.lx;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class TestAccount {
    public static void main(String[] args) {
        Account account = new AccountCas(10000);
        Account.demo(account);
    }
}
//cas无锁方式实现
class AccountCas implements Account {
    private AtomicInteger balance;

    public AccountCas(int balance) {
        this.balance = new AtomicInteger(balance);
    }

    @Override
    public Integer getBalance() {
        return balance.get();
    }

    @Override
    public void withdraw(Integer amount) {
        while(true) {
            // 获取余额的最新值
            int prev = balance.get();
            // 要修改的余额
            int next = prev - amount;
            // 真正修改
            if(balance.compareAndSet(prev, next)) {
                break;
            }
        }
        //balance.getAndAdd(-1 * amount);
    }
}

interface Account {
    // 获取余额
    Integer getBalance();

    // 取款
    void withdraw(Integer amount);

    /**
     * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
     * 如果初始余额为 10000 那么正确的结果应当是 0
     */
    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        //定义开始时间
        long start = System.nanoTime();
        //1000个线程同时运行
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
            //必须使1000个线程同时运行完成,主线程main才能结束
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //定义结束时间
        long end = System.nanoTime();
        //输出余额和运行时间
        System.out.println(account.getBalance()
                + " cost: " + (end-start)/1000_000 + " ms");
    }
}

结果:

0 cost: 205 ms

原理分析:
关键在于

private AtomicInteger balance;

public AccountCas(int balance) {
        this.balance = new AtomicInteger(balance);
    }
@Override
    public void withdraw(Integer amount) {
        while(true) {
            // 获取余额的最新值
            int prev = balance.get();
            // 要修改的余额
            int next = prev - amount;
            // 真正修改
            if(balance.compareAndSet(prev, next)) {
                break;
            }
        }
        //balance.getAndAdd(-1 * amount);
    }

查看AtomicInteger 源码

public class AtomicInteger extends Number implements java.io.Serializable {
    
    private volatile int value;
	......
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    public AtomicInteger() {
    }
    ......
}

AtomicInteger 将余额balance封装进去,volatile 用来修饰value。volatile下节再主要介绍。
volatile能够保证程序的可见性和有序性,简单来讲,能购保证多个线程拿到的都是最新的数据。

其中的关键是 compareAndSet,它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作。
共享模型之无锁(CAS和valotile)_第1张图片
如上图,线程1初始额度为100,做-10的操作,余额理应为90。在此期间线程2修改了初始额度为90,初始额度发生改变,则compareAndSet 会返回false,while(true)继续循环。初始额度变为90,做-10操作,再此期间线程2修改了初始额度为80,则compareAndSet 会返回false,while(true)继续循环。初始额度变为80,做-10操作,再此期间没有线程2的参与,则操作成功,break跳出循环。

总结方式二—无锁CAS

  1. 获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰
  2. CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果
  3. 为什么无锁效率高?
    无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,,虽然不会进入阻塞,但如果没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
  4. CAS 的特点
    • CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
    • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
    • CAS 体现的是无锁并发、无阻塞并发。
    • 线程数少于CPU核心数时,使用CAS比较高效。

CPU核心数

CUP核数即一个CPU由多少个核心组成,核心数越多,代表这个CPU的运转速度越快,性能越好。对于同一个数据处理,一核CPU相当于1个人处理数据,双核CPU相当于2个人处理同一个数据,4核CPU相当于4个人去处理同一个数据,因此处理核心数越多,CPU的工作效率也就越高

你可能感兴趣的:(并发,多线程,并发编程)