1,Java JMM(java内存模型)
这个内存模型搞起来还是有点弯弯绕,一点点的整理吧。JMM的目的就是保证共享变量在多线程环境下的原子性,可见性,与有序性而定义的一组规范。这些问题的出现都跟CPU的设计有关系,CPU是计算机的运算核心所有的操作或者说指令都是在CPU里面运行的,而且CPU的运行效率超级高,比内存的运行速率还要高出几个数量级,做开发的肯定都知道内存比硬盘快几个数量级,会使用内存缓存来提高运行速率,但是使用了内存缓存就会出现数据不一致的问题,我们会采取各种措施来保证数据的一致性,这里不展开讨论缓存的问题。还是说回CPU,既然CPU的运行速率比内存快的多,设计者也设计了CPU的高速缓存来充分利用CPU的性能提高程序的运行效率。多了这个高速缓存,同样也会出现数据不一致的问题,于是就有了缓存一致性协议如MESI协议等,就是保证数据的一致性。各种操作系统运行在CPU等硬件之上同样要解决这个问题,于是不同的操作系统也有自己的内存模型。而java程序运行在JVM之上,而且支持平台一致,支持多线程并发,为了保证各个平台运行结果一致,保证共享数据在多线程环境下的一致有序,制定了JMM规范,所有的JVM都要遵从该规范,保证java程序的平稳高速运行。我们平时开发用到的(我现在接触到的)就是一个volatile和syncronized两个关键字,还有锁机制。当然还有其他原则,这里不涉及。而volatile的作用就是保证共享数据的可见性,使用的技术叫做内存屏障。内存屏障这个词听起来不是很好理解是英语直译,用中国话说就是内存数据的写入和读取的顺序以及方式,你不是有主内存还有cpu缓存还有什么堆内存栈内存吗?好一个内存屏障的指令可以规定数据从哪里读取,又该啥时同步。好了到这里了,一个 volatile 关键字的demo,贴在下面:
/**
* volatile 可以保证共享变量的可见性,所谓可见性就是 保证变量的操作都是在共享堆内存中进行,而不是线程变量。
*/
public class VolatileTest {
private static boolean flag = false;
private static volatile boolean volatileFlag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!flag) { //程序会在这里死循环,因为之后的代码虽然对该属性做了修改,但对于该线程不可见。
}
System.out.println("Flag is now true!");
}).start();
new Thread(() -> {
while (!volatileFlag) { //使用volatile修饰的变量只要发生变化对所有线程可见。
}
System.out.println("volatileFlag is now true!");
}).start();
Thread.sleep(1000);
flag = true;
System.out.println("Flag is set to true!");
volatileFlag = true;
System.out.println("volatileFlag is set to true!");
}
}
2,ThreadLocal 与 SimpleDateFormat
首先是平时经常使用的 SimpleDateFormat 是线程不安全的,我平时都是现用现new的,据说这样子有点浪费系统资源。查阅相关资料后发现还可以结合ThreadLocal来使用,就是将df对象绑定在线程上,在空间和效率之间取得一点平衡。ThreadLocal,这个东西设计的很奇怪。你本身是用来操作Thread类里面的threadLocalMap的,你叫个ThreadLocalMapUtils不比ThreadLocal好理解啊?总之这个类就是将一个对象绑定在线程上,不同的线程会创建不同的对象(也有可能是克隆如果实现了clone方法的话)比如下面的例子。也就是说所有线程不安全的类(变量)都可以绑定在ThreadLocal上面呗?
import java.text.DateFormat;
import java.text.SimpleDateFormat;
/**
* ThreadLocal 为每一个线程提供了独立的变量副本。也就是说 get() set() 方法是基于线程的,线程不一样获取到的对象就不一样。
* 使用场景:
* 适合保存和传递每个线程独立的数据,比如基于线程执行的用户信息,会话信息等,注意如果内部启用了新线程(子线程)会导致数据不一致。
* 而且基于线程池的线程对象不会被回收,ThreadLocal变量也就不会被回收,所以使用完毕手动清除是个好习惯 threadLocal.remove();
*
*/
public class ThreadLocalTest {
static ThreadLocal<MyObject> threadLocal = ThreadLocal.withInitial(() -> {
MyObject myObject = new MyObject();
System.out.println(myObject);//com.kuafu.MyObject@22f71333 //com.kuafu.MyObject@29702e06 这里会执行两次,因为有两个线程来访问
return myObject;
});
private static ThreadLocal<DateFormat> tlDf = ThreadLocal.withInitial(() -> {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat);//java.text.SimpleDateFormat@4f76f1a0 //java.text.SimpleDateFormat@4f76f1a0
return simpleDateFormat;
});
public static void main(String[] args) {
final MyObject myObject = threadLocal.get();
System.out.println(myObject);//com.kuafu.MyObject@22f71333
new Thread("t1"){
@Override
public void run() {
final MyObject myObject1 = threadLocal.get();
System.out.println(myObject1); //com.kuafu.MyObject@29702e06
System.out.println(myObject == myObject1); //false
System.out.println(myObject.equals(myObject1)); //false
}
}.start();
final DateFormat dateFormat = tlDf.get();
System.out.println(dateFormat); //java.text.SimpleDateFormat@4f76f1a0
new Thread("t1"){
@Override
public void run() {
final DateFormat dateFormat1 = tlDf.get();
System.out.println(dateFormat1); //java.text.SimpleDateFormat@4f76f1a0
System.out.println(dateFormat == dateFormat1); //false 这里看起来是个浅克隆?
System.out.println(dateFormat.equals(dateFormat1)); //true
}
}.start();
}
}
标签:总结,Java,基础,System,ThreadLocal,线程,内存,println,out
From: https://blog.csdn.net/lx18854869896/article/details/140889463