java点点(二)

内存分析

        有一部分见内存分析(一)。

数组

        数组也是一个对象,数组元素相当于对象的成员变量(如:初始化时默认值与成员变量是一样的:基本数据类型默认值为0——数字是0,布尔是false,char为\u0000——引用数据类型默认值是null),只不过成员变量有名字,而数据中每一个元素是没有名字的。内存结构如下(尚学堂java300集第52集):

java点点(二)_第1张图片

synchronized与volatile

主内存与工作内存

        参考:java内在模型与多线程

        jvm有主内存和工作内存。主内存存储程序的类实例、静态变量等,是多个线程共享的。而工作内存是每一个线程私有的,别的线程不能直接访问,存储着它从主内存中拷贝过来的变量以及访问方法所得到的局部变量。每个线程对变量的操作都是以先从主内存将其拷贝到工作内存再对其进行操作的方式进行,多个线程之间不能直接互相传递数据通信,只能通过共享变量来进行。如下图(截自http://www.imooc.com/video/6775):

java点点(二)_第2张图片

        不同线程之间无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存进行中转。

可见性与共享变量

        可见性:一个线程对共享变量的修改能及时地被其它线程所看见。java中通过volatile、synchronized和final关键字保证了变量的可见性。但要注意:即使不使用这两个方法,很多时候变量依旧能在工作内存和主内存中保持及时更新。

        共享变量:如果一个变量在多个线程的工作内存中都存有副本,那么这个变量就是这几个线程的共享变量。线程对共享变量的操作,必须从自己的工作内存中进行,不能直接从主内存中读写。

synchronized

        java规定:线程解锁前,必须把共享变量的最新值刷新到主内存中。线程加锁时,将清空工作内存中共享变量的值,使用共享变量时重新从主内存中读取最新的值

java点点(二)_第3张图片

       当一个线程访问object的synchronized(this)的同步代码块时,它就获得了这个object的对象锁。结果,其他线程对该object对象所有同步代码块部分的访问都会被暂停。也就是说:一个锁一次只能供一个线程使用,其他线程只要访问到的代码用到了这个锁,这个线程就会被暂停。

volatile

        对volatile变量执行写操作时,会在写操作后将变量的值刷新到主内存中,类似于synchronized的释放锁。对volatile变量执行读操作时,会在读操作之前从主内存中获取该变量的最新值,类似于synchronized的加锁。如下:

java点点(二)_第4张图片

要在多线程中安全地使用volatile,必然同时满足:

        1,对变量的写入操作不能依赖于其当前值。如count = count*5便不能使用volatile,但b = true时可以的。

        2,该变量没有包含在具体其它volatile变量的不定式中,如up < down。

比较

        volatile不需要加锁,比synchronized执行效率更高。

        volatile不能保证共享变量相关操作的原子性,在多线程中仍旧有可能导致变量值的不准确。如num++,它实际分为三步:先从主内存中读取,再进行++操作,最后写到主内存中。如果一个线程在执行完第一步后被别的线程抢占了资源,那么这个线程再次执行时便不会再次从主内存中读取,这就导致了在它执行++时,使用的值并不是别的线程执行后的值。而synchronized却能保证原子性。所以在能保证线程安全时应该使用volatile,而线程不安全时使用synchonized

包装类

        对基本数据类型数据进行包装的类,叫包装类。以Integer为例。

自动装箱

        如下条语句

Integer a = 123;
        这条语句,虽然声明的是对象,而赋值时是基本数据类型,但它是不会报错的,因为编译器在编译的时候会将该语句改写为Integer a = new Integer(123)。这种将基本数据类型自动转换成其对应包装类的对象过程便是自动装箱。

自动拆箱

        与自动装箱相反,自动拆箱是将包装类的对象自动转换成对应的基本数据类型。如int a = new Integer(2)。因为编译器在编译的时候,会自动调用Integer#intValue()将Integer对象转换成基本数据类型。

缓存

        Integer对equals()进行了重写,该方法比较的是Integer的值的大小,并不是对象在内在中的地址。如:

		Integer i = new Integer(128);
		Integer i2 = new Integer(128);
		System.out.println(i.equals(i2));//true。比较值,两者都为128,所以为true
		System.out.println(i == i2);//false。比较两个对象的地址,所以为false

        但对于[-128,127]中的数据,Integer对其进行了缓存,在比较时仍旧会当作基本数据类型进行处理,因此无论调用==还是equals()时比较的都是值,所以将上面的128换成127后,两个输出语句输出的都是true。

==与equals()

        ==比较的是当前变量本身所存储的值。如果变量是基本数据类型,那么存储的就是基本数据类型的值,如果是引用数据类型,那么存储的就是对象的地址。无论存储的是什么,比较的都是该变量本身的值。

        equals():在Object的实现中,该方法就是调用了==。因此,如果子类没有重写该方法,那么它的作用和==一样,如果重写了那就得视子类重写情况而定。

try,catch,final与return

        try,catch执行后肯定会执行final。
        如果final中无return,那么返回的结果就是try或catch中返回的调用return时的结果。即使在final中对return的值进行了修改也不影响返回的结果。
        如果final中有return,那么返回的结果就是final中return的返回结果。


你可能感兴趣的:(java点点(二))