Java基础总结
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@4f76f1a0return simpleDateFormat;});public static void main(String[] args) {final MyObject myObject = threadLocal.get();System.out.println(myObject);//com.kuafu.MyObject@22f71333new Thread("t1"){@Overridepublic void run() {final MyObject myObject1 = threadLocal.get();System.out.println(myObject1); //com.kuafu.MyObject@29702e06System.out.println(myObject == myObject1); //falseSystem.out.println(myObject.equals(myObject1)); //false}}.start();final DateFormat dateFormat = tlDf.get();System.out.println(dateFormat); //java.text.SimpleDateFormat@4f76f1a0new Thread("t1"){@Overridepublic void run() {final DateFormat dateFormat1 = tlDf.get();System.out.println(dateFormat1); //java.text.SimpleDateFormat@4f76f1a0System.out.println(dateFormat == dateFormat1); //false 这里看起来是个浅克隆?System.out.println(dateFormat.equals(dateFormat1)); //true}}.start();}}