JAVA面试宝典面经系列之JAVA基础-含JAVA基础语法,集合,线程等方面的使用与面试热点频点问题(二)

在这里插入图片描述

Java的序列化和反序列化

序列化可以将对象转化为字节序列,字节序列可以保存在磁盘上,也可以通过网络传输,并允许程序将字节序列恢复为原来的对象,其中对象的序列化是指将一个对象写入IO流,对象的反序列化指从IO流获取到JAVA对象。
如果对象需要支持序列化机制,则它的类需要实现Serializable接口,该接口是一个标记接口,没有提供任何的方法,只是表明对象可以被序列化,Java的很多类已经实现Serializable接口,String类,Date类都实现了。
如果需要实现序列化,需要用对象流ObjectInputStream和ObjectOutputStream。当对象被序列化时调用ObjectOutputStream对象中的writeObject()方法,用来输出对象的序列。
在反序列化时,调用ObjectInputStream对象的readObject()方法,用来对象序列回复为对象。

Java的序列化工具有哪些?

JSON,Thrift,dubbo

线程的创建方式?

继承Thread类和实现Runable,Callable接口

run和start方法有什么区别?

run()方法是线程执行体,代表线程需要完成的任务,而start()方法代表启动线程。
调用start()方法启动线程时,系统将run()方法当成线程执行体处理,如果用户直接通过调用run()方法,则run()方法立即就会被执行,而且run()方法返回之前其他线程是无法并发执行的。当直接调用线程对象的run()方法系统吧线程对象作为一个普通对象,而run()方法是一个普通方法,而不是线程执行体。

volatile关键字

volatile的特性是可见性和内存屏障
volatile的可见性是什么?
volatile保证了变量在多处理器环境下的可见性。
所有的线程的共享变量都是存在主内存中的,而每个线程具备自己的工作内存,每个线程不直接操作主内存中的数据,操作流程是从主内存中获取数据到自己的线程工作线程内存中,然后操作工作内存中的数据,修改完毕再将数据放回主内存,所以在未用volatile关键字修饰的变量,线程之间的值传递是需要通过主内存的。
设两个线程AB同时操作同一个变量a,线程A将变量a放入自己的工作内存,然后进行数据操作,这时未执行完,未将变量a的值刷新到主内存中,线程B从主内存取到的值是一开始的值,也就形成了脏数据。
如果该变量a被volatile关键字修饰,可以保证线程A操作变量a后,立刻将最新的值刷新回主内存,而其他线程之前读取到的值也会作废,强迫这些线程重新从主内存获取最新的数据,从而保证被修饰的变量的可见性。

如何保证可见性?

在修改带有volatile修饰的成员变量时需要多发出一个lock命令,lock指令是一种控制指令,当多线程的场景下,lock命令可以基于总线锁或者缓存锁机制达到可见性的效果。

内存屏障

内存屏障的实现主要是通过防止指令重排,指令重排序主要是计算机中处理器为了提高程序运行效率,可能对输入的代码进行优化,不保证程序中每个语句执行的先后顺序,但可以保证程序最终执行结果和代码顺序的结果是一致的。
编译器和处理器在重排序时遵守数据依赖性,编译器和处理器的存在不会改变数据依赖关系的两个操作执行顺序。
而指令重排序是可能导致变量发生错乱的,在不需要控制线程安全的情况下是完全没问题的,如果线程安全的情况下是需要通过voliate添加内存屏障实现禁止指令重排序。

内存屏障的作用

禁止屏障两侧的指令进行重排序,即屏障下面的代码不与屏障上面的代码交换顺序
在有内存屏障的地方,线程改完变量的值后(该变量是共享变量,即从主内存获取的)马上吧该变量从线程本地内存写回主内存,并且其他线程本地内存中有的数据变量副本会失效,(这里的协议是MESI协议,也是通过线程的可见性实现的)

用voliate修饰的变量,可以实现防止cpu指令重排序的目的,实现的方式是基于4种内存屏障:读读,读写,写读,读读屏障。

对于读屏障来说,在指令前插入读屏障,会使高速缓存中数据失效,强制其他线程重新从主内存加载数据。
对于写屏障来说,在指令前插入写屏障,会使写入缓存中的最新数据更新到主内存,其他线程可见。

在Java的内存层面可以理解
当有线程需要使用到一个用voliate修饰的变量时,必须从主内存获取到这个变量,当线程工作内存中重新赋值这个变量时,立刻将这个变量写入主内存,然后使其他线程中的这个变量失效,并重新获取最新的值。

sychronized关键字

当存在多个线程操作同一个共享数据,这时需要保证有且只有一个线程可以拿到这个数据,即只有一个线程可以操作共享数据,其他线程必须等这个线程释放/处理完数据后才可以进行后续的操作,这是互斥锁的概念。能达到互斥访问目的的锁,当一个共享数据被一个线程加上互斥锁后,其他线程必须等待当前线程操作完数据才可以执行某个方法或者代码块。这是sychronized的作用之一,还有一个作用是保证线程变化,保证可见性。

sychronized的特点

1.一个锁只能被一个线程获取,其他线程要进入等待状态。
2.每个实例对象有自己的一把锁(this),不同的实例之间互不影响,但当锁的对象是class以及sychronized修饰的是静态方法时,所有对象公用一把锁。
3.sychronized修饰的方法,不管该方法是正常执行还是有异常,都会释放锁资源

sychronized的原理

如下例子:
在这里插入图片描述
利用javac SychronizedDemo2.java命令生成class文件然后通过反编译命令查看class文件信息
信息如下:
在这里插入图片描述
其中monitorenter和monitorexit即是实现互斥锁的关键。
每个对象只与一个monitor锁关联,而同一时间内monitor只会被同一线程获取。
当monitor计数器为0时,说明当前资源可被访问,当前线程获取资源且计数器+1
当发生可重入锁时monitor数+1

虚拟机针对Sychronized的优化

锁的状态一共4种,无锁,偏向锁,重量级锁,轻量级锁。
当锁竞争的状态不同,锁可以从偏向锁到轻量级锁,如果并发继续提升,会升级到重量级锁。但是锁的升级过程是单向的。并不是可逆且闭环的,即只能升级,不会出现锁的降级。

在这里插入图片描述

偏向锁

偏向锁的概念是JDK6后引入的,是针对sychronized做的优化。锁在并发情况下,希望不存在多线程的竞争,这把锁可以让同一个线程多次获取,因此减少一些线程获取锁可能存在的CAS操作。从而引入偏向锁。偏向锁的概念是当一个线程获取过一个锁之后,这个锁就进入偏向模式,Mark word也会进入偏向锁结构,当这个线程需要再次获取锁时,不需要再同步,省去获取锁的过程中各种申请锁资源的操作,提升程序性能。在没有锁竞争的情况下,偏向锁与很好的优化效果。但是当锁竞争激烈的情况下,偏向锁会失效,因为这种情况下获取锁的线程通常都是不同的线程,所以不应该使用偏向锁,偏向锁失效后,即并发量提升的情况下,会升级到轻量级锁。

轻量级锁

当偏向锁失效时,Mark word结构变为轻量级锁结构,轻量级锁提升性能的主要场景是:当大部分锁在同一时期,不存在竞争。这里再提一下偏向锁的概念,偏向锁的概念是,不存在锁的激烈竞争下,同一个线程获取锁。

sychronized的注意事项

锁对象是不能为null的,因为锁的相关信息(锁标记位,是否偏向锁)都存储在对象头中。
作用域不宜过大,因为影响其他资源操作对象
实际应用中尽量使用java.util.concurrent包中的类

sychronized锁有什么不足?

效率不高:锁释放的场景少,只有代码跑完或者异常抛出才释放。获取锁的时候不能设置超时,也不可以中断使用锁的线程。
锁状态未知:无法知道当前线程是否获取到锁,Lock就可以看到锁的状态。

线程池的参数如何选配?

主要分为三种场景,CPU密集型,IO密集型,混合型
CPU密集型:尽量用较小的线程池,一般CPU核心数+1,因为CPU密集型的CPU占用率很高,如果过多的话,CPU过度切换。
IO密集型:可以使用稍大一点的线程池,一般是2*N(N是CPU核心数),IO密集型的CPU占用率不高
混合型:可以分为IP密集型和CPU密集型任务,用不同的线程池处理。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值