一、实现网站访问计数器
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算法来保证的线程安全性。