Java多线程学习笔记 - 自增(减)不是原子操作

一、示例代码

1、创建类并运行

        创建一个继承自Thread的类。

package com.algorithm.demo.thread;

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

/**
 * 线程测试类1
 */
public class ExtendsThreadDemo extends Thread {

    private int i = 0;

    @Override
    synchronized public void run()
    {
        super.run();
        for (int k = 0; k < 1000; k++)
        {
            System.out.println("i=" + (++i) + " ThreadName=" + Thread.currentThread().getName());
        }
    }

}

      实例化这个类,并且创建多个线程,然后启动线程,你的电脑的cpu核心越多,请增加下面for循环的数量,否则不太容易出现。

//实例化
ExtendsThreadDemo demo = new ExtendsThreadDemo();

//创建线程
List<Thread> list = new ArrayList<>();
for(int i=0; i<80; i++)
{
    list.add(new Thread(demo));
}

//运行线程
list.forEach((e) -> {
    e.start();
});

// 让主线程休眠一会,以便子线程彻底执行完,否则可能打印不全
try {
    Thread.sleep(8000);
} catch (InterruptedException e) {
	e.printStackTrace();
}

2、查看运行结果

        这里的synchronized关键字加再run方法上

(1)不加synchronized关键字的结果

        多次运行的结果并不一致,很大几率不会打印80000

i=79994 aaThreadName=Thread-70
i=79995 aaThreadName=Thread-70
i=79996 aaThreadName=Thread-70
i=79997 aaThreadName=Thread-70
i=79998 aaThreadName=Thread-70

(2)加synchronized关键字的结果

        多次运行的结果一致

i=79996 aaThreadName=Thread-8
i=79997 aaThreadName=Thread-8
i=79998 aaThreadName=Thread-8
i=79999 aaThreadName=Thread-8
i=80000 aaThreadName=Thread-8

 二、原理说明

1、println()方法

        虽然println()方法在内部是synchronized同步的,但++i操作却是在进入println()之前发生的,所以有一定概率发生非线程安全问题。

public void println(String x) {
    synchronized (this) {
        newLine();
    }
}

2、原子操作

        下面这句是一个原子操作

int i = 1;

        非原子操作,i++是一个多步操作,而且是可以被中断的。i++可以被分割成3步,第一步读取i的值,第二步计算i+1;第三部将最终值赋值给i。

i++;

3、CAS操作

        CAS是Compare and swap的简称,这个操作是硬件级别的操作,在硬件层面保证了操作的原子性。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。Java中的sun.misc.Unsafe类提供了compareAndSwapIntcompareAndSwapLong等几个方法实现CAS。

        另外,在jdk的atomic包下面提供了很多基于CAS实现的原子操作类,见下图:

三、使用AtomicInteger

        使用AtomicInteger的类,不使用synchronized关键字。

package com.algorithm.demo.thread;

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

/**
 * 线程测试类1
 */
public class ExtendsThreadDemo extends Thread {

    public static AtomicInteger count = new AtomicInteger(0);


    @Override
    public void run()
    {
        super.run();
        for (int k = 0; k < 1000; k++)
        {
            //下面两种方法都可以
            System.out.println("i=" + (count.incrementAndGet()) + " ThreadName=" + Thread.currentThread().getName());
            //System.out.println("i=" + (count.addAndGet(1)) + " ThreadName=" + Thread.currentThread().getName());
        }

    }
}

        运行上面的类

ExtendsThreadDemo demo = new ExtendsThreadDemo();

List<Thread> list = new ArrayList<>();
for(int i=0; i<80; i++)
{
	list.add(new Thread(demo));
}

list.forEach((e) -> {
	e.start();
});

//让主线程休眠1秒,否则子线程输出不完整。
try {
	Thread.sleep(8000);
} catch (InterruptedException e) {
	e.printStackTrace();
}

        多次运行输出结果,都是一致的。

i=79996 ThreadName=Thread-4
i=79997 ThreadName=Thread-4
i=79998 ThreadName=Thread-4
i=79999 ThreadName=Thread-4
i=80000 ThreadName=Thread-4

四、synchronized和Atomic区别

        1、原子类和 synchronized都能保证线程安全,但是其实现原理不同。

        2、Synchronized 是仅适用于方法和块但不适用于变量和类的修饰符。

猜你喜欢

转载自blog.csdn.net/bashendixie5/article/details/123495656