java基础(三).ConcurrentHashMap原理,深拷贝和浅拷贝,序列化与反序列化,反射

1.ConcurrentHashMap原理

HashMap缺点:
     线程不安全,多线程情况下,同时进行resize操作可能引起死循环,还有,两个线程同时进行 put操作时,也可能导致put失败。
HashMap的key和value都允许放入null值。

HashTable缺点:
     HashTable采用sychronized来保证线程安全,但是在线程激烈竞争情况下,效率非常低。一个线程访问HashTable的同步方法时,其他线程接着访问的话,可能会进入阻塞或者轮询状态。比如线程1使用put方法添加元素,那么线程2既不能使用put添加元素,也不能使用get获取元素,导致效率降低。
HashTable不允许null值。

1.1、ConcurrentHashMap1.7

jdk1.7中采用 Segment + HashEntry 的方式实现
在这里插入图片描述
put实现
当执行put方法时,根据key的hash值,在Segment中找到对应位置,如果对应位置还未进行初始化,则通过CAS进行赋值,接着执行Segment对象的put方法通过加锁机制插入数据。

场景:线程A和线程B同时执行相同Segment的put方法

  • 1、线程A通过tryLock() 方法获取锁,进行插入操作。
  • 2、线程B获取锁失败,则执行scanAndLockForPut() 方法,在scanAndLockForPut方法中,会通过重复执行tryLock() 尝试获取锁(自旋锁的方式),在多处理环境下,重复次数为64,单处理器重复次数为1,当执行tryLock方法次数超过限制时,则执行lock() 方法挂起线程B。
  • 3、当线程A执行完插入操作时,会通过unlock() 方法释放锁,线程B获取锁继续执行。
1.2、ConcurrentHashMap1.8

数据结构:数组+链表+红黑树
并发原理: CAS + Synchronized
加锁对象:数组每个位置的头节点
在这里插入图片描述
put方法
1、根据key的hash值定位到桶的位置
2、判断table如果为null,则先初始化table
3、判断table[i]是否为null,cas添加元素,成功则跳出循环,失败则进入下一轮for循环。
4、判断是否有其他线程在扩容,如果有,则先帮忙进行扩容,扩容完再添加元素,进入真正的put操作。
5、真正的put操作,桶的位置不为空,遍历该桶的链表或者红黑树,若key已经存在,则覆盖;不存在,则采用尾插法插入到链表或者红黑树的尾部。

什么情况会导致扩容?
1、链表转换为红黑树时,如果转换时数据的长度小于64则直接扩容一倍,不转化为红黑树。如果数组长度大于64,则不会扩容,直接进行链表转红黑树的操作。
2、map中节点的总数大于阈值(总长度的0.75倍)时会进行扩容。

如何扩容?
1、创建一个新的table,是原来table的两倍。此过程是单线程创建的。
2、复制旧的map到新的map中。此过程是多线程并发完成。(将map按照线程数量平分成多个相等区域,每个线程负责一个区域的复制操作,如果一个线程完成自己的复制之后,其他线程还有未完成的,则该线程回去帮助其他线程继续复制)
扩容下标的计算跟HashMap一致。

线程之间相互帮忙会造成混乱吗?
不会。因为线程已经完成复制的位置会标记该位置已完成,其他线程看到标记会直接跳过。而对于正在执行复制任务的位置,则会直接锁住该桶,这样就不会有并发问题了。

扩容什么时候结束呢?
每个线程复制之前会将标记为sizeCtl 加1,退出时会将sizeCtl 减1,这样每个线程退出时只需要查看sizeCtl是否等于进入之前的状态就知道是否全部退出了。最后一个退出的线程,则将旧table的地址指向新table。

如何保证初始化nextTable时是单线程的?
所有调用transfer的方法(helperTransfer、addAccount)几乎都预先判断了nextTab!=null,而nextTab方法只会在transfer方法中初始化,保证第一个进来的线程初始化之后其他线程才能进入。

2.new String("")创建几个对象?

      1个或者2个,java中有String常量池,每当创建一个新对象时,就先去常量池中找是否有这个字符串,有的话就不在常量池中创建新对象,没有的话就创建一个对象,另外一个对象是在堆上创建。

3.Java序列化与反序列化原理

      3.1 Java序列化是指将java对象转化为字节序列的过程,而java反序列化是把字节序列恢复为对象的过程。
序列化的作用:

在传递和保存对象的时候,保证对象的完整性。序列化是把对象转化为有序字节流,以便在网络上传输或者保存在本地文件中。

反序列化
客户端从文件或者网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态以及描述信息,通过反序列化重建对象。

3.2 序列化与反序列化的作用以及好处:
适用于当两个java进程进行通信时

  • (1)实现了数据的持久化,通过序列化可以把数据持久的放在硬盘上(通常存放在文件里)
  • (2)利用序列化进行远程通信,即通过网络传输传送对象。

3.3 与序列化相关的API:
(1)ObjectOutputStream:表示对象输出流
它的writeObject可以对指定参数的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
(2)ObjectInputStream:表示对象输入流
它的readObject方法可以源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。

4.深拷贝和浅拷贝的区别

      浅拷贝 是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,因此是两份不同的数据,修改其中一份,另一份不会被影响。
而对于引用类型的变量只是对引用进行拷贝,这两个引用都指向同一个地址,因此修改其中一个另一个也会改变。
      深拷贝 则是对对象以及该对象关联的内容,都进行拷贝,修改其中一个,另一个不会改变。
举例:假如B复制了A,当修改了A之后,看B是否变化,如果B变化了,则说明是浅拷贝,如果B没变,则说明是深拷贝。

深拷贝可以采用的方法:
1.采用 JSON.stringify 和 JSON.parse

function deepClone(obj){
    let _obj = JSON.stringify(obj),
    objClone = JSON.parse(_obj);
    return objClone
}  

2.对对象进行深拷贝
(1)每一个对象都实现Cloneable接口,并重写clone()方法,最后在最顶层的类重写的clone()方法中调用所有的clone()方法即可实现深拷贝。

(2)通过序列化实现深拷贝,每个对象都要实现Serializable接口。

		Age a=new Age(20);
        Student stu1=new Student("张三",a,175);
        //通过序列化方法实现深拷贝
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(bos);
        oos.writeObject(stu1);
        oos.flush();
        
        ObjectInputStream ois=new ObjectInputStream(new	ByteArrayInputStream(bos.toByteArray()));
        Student stu2=(Student)ois.readObject();  
5.反射

5.1 反射的原理:
Java语言编译后会形成一个.Class文件,反射就是通过字节码文件找到某一个类,类中的方法以及属性等。

5.2 反射的实现主要借助四个类:

Class:类的对象
Constructor:类的构造方法
Field:类中的属性对象
Method:类中的方法对象

5.3 使用反射机制的步骤:
1.导入java.lang.reflect包
2.遵循三个步骤:

  • 获取你想操作的类对象
  • 调用相关反射的方法(比如getDeclaredMethods)
  • 使用反射API来操作

5.4 获取Class对象的方法:
(1)根据对象来获取

  Class c = 对象名.getClass();

(2)直接写类路径全称

Class c = java.awt.Button.class;

(3)包装类:Class c = Integer.TYPE;

(4)Class.forName

  // 要求JVM查找并加载指定的类,这时候,
  // 会把静态属性,方法以及静态代码块都加载到内存中
  Class t = Class.forName("com.TestOne");
  // 产生这个Class对象的一个实例,调用该类的无参构造方法,作用等同于new TestOne();
   t.newInstance();

5.5 Class对象可以调用的方法:

方法名介绍
getName()获得类的完整名字。
getFields()获得类的public类型的属性。
getDeclaredFields()获得类的所有属性。包括private 声明的和继承类
getMethods()获得类的public类型的方法。
getDeclaredMethods()获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes)获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors()获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes)获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance()通过类的不带参数的构造方法创建这个类的一个对象。

5.6 Class能实现的功能:
1.判断对象属于哪个类
2.获取类信息
3.构建对象
4.动态执行方法,通过invoke调用

   Class class1 = Person.class;
	Method work = class1.getDeclaredMethod("work");
	Person person = new Person();
	work.invoke(person);

5.动态操作属性

	Class class1 = Person.class;
    Person person = new Person();
    Field field = class1.getDeclaredField("age");
	//age默认值是18
    field.set(person,22);
    System.out.println(person.age);

6.动态代理

5.7 newInstance 和 new的区别:
前者是使用类加载机制,后者是创建一个新类。
newInstance的使用更加局限,只能调用无参构造,但是使用new可以调用任何public构造。
从JVM角度看,使用new关键字的时候,这个类可以没有被加载,但是使用newInstance的时候,就必须保证这个类已经加载了,这个类已经连接了。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值