一、示例代码
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
类提供了compareAndSwapInt
和compareAndSwapLong
等几个方法实现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 是仅适用于方法和块但不适用于变量和类的修饰符。