Volatile关键字
1、证明内存可见性代码实现
import java.util.concurrent.TimeUnit;
/**
* @author fighting
* @date 2020-11-16-21:32
* 1、如果一个线程对程序中的值进行更改,另外一个线程检测到那么就直接可以证明内存可见
*/
public class VisibilityDemo {
public static void main(String[] args) {
UpData upData = new UpData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 该线程让值增加到6
upData.uptosix();
System.out.println(Thread.currentThread().getName() + "已经更改值为" + upData.a);
}).start();
while (upData.a == 0) {
//如果main线程检测到值发生改变就跳出循环程序结束,如果程序一直在运行则证明main线程检测到值改变
//循环中不能写sout语句,因为sout语句中存在synchronized 锁,不能证明volatile内存可见性
}
System.out.println("main线程检测到值改变!证明了内存可见性。");
}
}
class UpData {
// 去掉volatile关键字 线程序不会停掉 但是加上后程序会一直执行
volatile int a = 0;
public int uptosix() {
a = 6;
return a;
}
}
2、代码证明非原子性
import com.util.ThreadPollUtil;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Stream;
/**
* @author fighting
* @date 2020-11-14-23:02
* 1、使用线程池的方法来验证+1操作的原子性
*
*/
public class GrowOnePoll {
private static volatile int flag = 0;
public static void main(String[] args) throws InterruptedException {
// 创建线程计数器
CountDownLatch downLatch = new CountDownLatch(1);
// 使用线程池创建线程
ThreadPoolExecutor executor = ThreadPollUtil.executor;
executor.submit(()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
upone();
}
downLatch.countDown();
System.out.println("执行" + downLatch.getCount());
});
downLatch.await();
System.out.println(downLatch.getCount());
System.out.println(flag);
executor.shutdownNow();
Thread thread = Thread.currentThread();
ThreadGroup threadGroup = thread.getThreadGroup();
int count = threadGroup.activeCount();
Thread[] threads = new Thread[count];
threadGroup.enumerate(threads, true);
System.out.println(count);
Stream.of(threads).forEach(x -> System.out.println(x.getName()));
}
public static synchronized int upone() {
flag++;
return flag;
}
}
2.1 线程池工具类
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author fighting
* @date 2020-11-14-22:41
*/
public class ThreadPollUtil {
// 返回java虚拟机处理器的数量
private static int corePoolSize = Runtime.getRuntime().availableProcessors();
/**
* 手动通过 ThreadPoolExecutor 创建线程池
*
* corePoolSize 用于指定核心线程数量
* maximumPoolSize 指定最大线程数
* keepAliveTime 指定线程空闲后的最大存活时间
*
* 可以根据项目的需要自定义线程池的参数
*/
public static ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
corePoolSize+1,
10L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1000));
}
3、禁止指令重排序,用该方法进一步增强单例模式的安全性
使用DCL已经可以保证单例模式的安全性,但是在及其微小的情况可能下会出现因为java在编译的时候因为指令重排序而造成的你编写的代码执行顺序不同,出现意外情况,所以在要使用的单例对象前加上volatile锁,禁止其编译后指令重排。
/**
* @author fighting
* @date 2020-11-11-16:32
*
*
* 1、使用DCL(Double Check Lock 双端检锁机制)创建多线程安全的单例模式
* 2、使用volatile禁止指令重排 保证顺序执行再次对单例模式进行巩固
*
*/
public class DCLSingle {
private static volatile DCLSingle ds = null;
public DCLSingle() {
System.out.println("创建该单例的实例。。。。。");
}
public static DCLSingle getDs() {
// 双重锁校验
if (ds == null) {
synchronized(DCLSingle.class) {
if (ds == null) {
ds = new DCLSingle();
}
}
}
return ds;
}
public static void main(String[] args) {
// 测试多线程情况下会不会打印两次 创建单例对象。。。
for (int i = 0; i < 200 ; i++) {
new Thread(() -> {
DCLSingle.getDs();
}, String.valueOf(i)).start();
}
}
}
文章中如果出现错误欢迎指出