通过实现网站访问计数器带你理解 轻量级锁CAS原理,还学不会算我输!!!(上)

一、实现网站访问计数器


1、线程不安全的做法


1.1、代码


package com.chentongwei.concurrency;
import static java.lang.Thread.sleep;
/**
 * @Description:
 * @Project concurrency
 */
public class TestCount {
    private static int count;
    public void incrCount() {
        count ++;
    }
    public static void main(String[] args) throws InterruptedException {
        TestCount testCount = new TestCount();
        // 开启五个线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 每个线程都让count自增100
                for (int j = 0; j < 100; j++) {
                    testCount.incrCount();
                }
            }).start();
        }
        sleep(2000);
        // 正确的情况下会输出500
        System.out.println(count);
    }
}


1.2、结果


并不一定是500,极大可能小于500。不固定。


1.3、分析


很明显上面那段程序是线程不安全的,为什么线程不安全?因为++操作其实是类似如下的两步骤,如下:


count ++;
||
// 获取count
int temp = count;
// 自增count
count = temp + 1;


很明显是先获取在自增,那么问题来了,我线程A和线程B都读取到了int temp = count;这一步,然后都进行了自增操作,其实这时候就错了因为这时候count丢了1,并发了。所以导致了线程不安全,结果小于等于500。


2、Synchronized保证线程安全


2.1、代码


package com.chentongwei.concurrency;
import static java.lang.Thread.sleep;
/**
 * @Description:
 * @Project concurrency
 */
public class TestCount {
    private static int count;
    public  void incrCount() {
        count ++;
    }
    public static void main(String[] args) throws InterruptedException {
        TestCount testCount = new TestCount();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int j = 0; j < 100; j++) {
                    synchronized (TestCount.class) {
                        testCount.incrCount();
                    }
                }
            }).start();
        }
        sleep(2000);
        System.out.println(count);
    }
}


2.2、结果


500


2.3、分析


没什么可分析的,我用了Java的内置锁Synchronized来保证了线程安全性。加了同步锁之后,count自增的操作变成了原子性操作,所以最终输出一定是500。众所周知性能不好,所以继续往下看替代方案。


3、原子类保证线程安全


3.1、代码


package com.chentongwei.concurrency;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Thread.sleep;
/**
 * @Description:
 * @Project concurrency
 */
public class TestCount {
 
    // 原子类
    private static AtomicInteger count = new AtomicInteger();
    public  void incrCount() {
        count.getAndIncrement();
    }
    public static void main(String[] args) throws InterruptedException {
        TestCount testCount = new TestCount();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int j = 0; j < 100; j++) {
                    testCount.incrCount();
                }
            }).start();
        }
        sleep(2000);
        System.out.println(count);
    }
}


3.2、结果


500


3.3、分析


所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。如AtomicBoolean,AtomicUInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。每个原子类内部都采取了CAS算法来保证的线程安全性。

上一篇:[经典面试题]在O(1)时间删除链表结点


下一篇:[雪峰磁针石博客]python经典面试题:列表和元组有什么异同?