目录
23 CAS(乐观锁)
- synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。
- CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
Synchronized虽然确保了线程的安全,但是在性能上却不是最优的,Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。
尽管Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。
所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
//每个线程让count自增100次
for (int i = 0; i < 100; i++) {
count.incrementAndGet();
}
}
}).start();
}
try{
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(count);
}
使用了原子类后同样可以保证原子性,而且性能回比Synchronized好。而Atomic操作的底层实现正是利用的CAS机制。
什么是CAS机制
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
这样说或许有些抽象,我们来看一个例子:
1.在内存地址V当中,存储着值为10的变量。
2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。
3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。
4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。
5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。
6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。
7.线程1进行SWAP,把地址V的值替换为B,也就是12。
从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。
CAS的缺点:
1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
24 synchronized 和 volatile 的区别是什么?
volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。
volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。
也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。
-
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
-
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
-
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
-
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
-
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
25synchronized 和 Lock 有什么区别?
-
首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
-
synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
-
synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
-
用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
-
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
-
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
26 synchronized 和 ReentrantLock 区别是什么?
synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
-
ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
-
ReentrantLock可以获取各种锁的信息
-
ReentrantLock可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。
27 反射机制
- 反射机制是什么
JAVA反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类信息以及动态调用对象内容就称为java语言的反射机制。 - 反射的作用
-
在运行时判断任意一个对象所属的类;
-
在运行时构造任意一个类的对象;
-
在运行时判断任意一个类所具有的成员变量和方法;
-
在运行时调用任意一个对象的方法;
- 反射的实现
我们知道,要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个class对象就保存了这个类的一切信息。
反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。
有三种方法获得类的Class对象:Class.forName(String className)、className.class、实例对象.getClass(); - 反射涉及到的API
反射首先获取Class对象;然后获取Method类和Field类;最后通过Method和Field类进行具体的方法调用或属性访问。
1:在运行时获取对象所属类的类名等信息
对象名.getClass().getName();
2:通过反射机制创建class对象(三种方法)
Class class1 = Class.forName(className);
Class class2 = 对象名.getClass();
Class class3 = 对象名.class;
```java
3:在运行时,通过创建class对象,获取自己的父类信息
```java
Class<?> clazz = Class.forName(当前类);
Class<?> parentClass = clazz.getSuperclass();
parentClass.getName();//获得父类名
4:通过反射机制创建一个类的对象
1:反射创建class对象(见上面)
2:Classname 对象=classname.newInstance(参数);
5:获取类的全部方法,存于一个数组中
//创建class对象
Class<?> clazz = Class.forName(ClassName);
// 返回声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
Method[] getDeclaredMethods();
//返回可被访问的公共方法
Method method[] = clazz.getMethods();
6:获取类的全部字段,存于一个数组中
Class<?> clazz = Class.forName(classname);
// 取得本类已声明的所有字段,包括私有的、保护的
Field[] field = clazz.getDeclaredFields();
// 取得本类中可访问的所有公共字段
Field[] filed1 = clazz.getFields();
7:操作类/对象 的某个属性(包括私有)
Class<?> clazz = Class.forName(classname);
//返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。 包括公共、私有、保护的字段。
Field field = clazz.getDeclaredField(字段名);
//禁用Java权限修饰符的作用,无视方法权限限制进行访问
field.setAccessible(true);
// void set(Object obj, Object value) 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
field.set(该类的一个对象, 字段值)
8:调用类/对象 的某个方法(包括私有)
Class<?> clazz = Class.forName(classname);
// Method getMethod(String name, Class<?>... parameterTypes)
//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
//获取一个公有函数
Method method = clazz.getMethod(方法名,参数类型);
//调用具体某个实例对象的这个公有方法
method.invoke(实例对象,参数值);
// Method getDeclaredMethod(String name, Class<?>... parameterTypes)
//返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 包括私有、保护、公有方法
//获取一个私有函数
Method private_method=class.getDeclaredMethod(函数名,参数类型);
//禁用Java权限限定符的作用,使私有函数可访问
private_method.setAccessible(true);
//调用具体实例对象的这个方法
private_method.invoke(实例对象,参数);
28 序列化和反序列化
简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
什么情况下需要序列化:
- a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
- b)当你想用套接字在网络上传送对象的时候;
- c)当你想通过RMI传输对象的时候;
序列化的参考博客:https://www.cnblogs.com/9dragon/p/10901448.html
29 代理模式
代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能.
这就符合了设计模式的开闭原则,即在对既有代码不改动的情况下进行功能的扩展。
举个例子来说明代理的作用:明星与经纪人之间就是被代理和代理的关系,明星出演活动的时候,明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)
java当中代理模式分为三种
-
静态代理
在使用静态代理时,被代理对象与代理对象需要一起实现相同的接口或者是继承相同父类,因此要定义一个接口或抽象类,每生成一个被代理对象的同时要生成一个代理对象,例如,每个明星都有要一个经纪人。
// 接口
interface IStar {
void sing();
}
// 真实对象
class LDHStar implements IStar {
@Override
public void sing() {
System.out.println("刘德华唱歌");
}
}
// 代理类需要有真实对象的控制权 (引用)
class ProxyManger implements IStar {
// 真实对象的引用
private IStar star;
public ProxyManger() {
super();
}
public ProxyManger(IStar star) {
super();
this.star = star;
}
@Override
public void sing() {
System.out.println("唱歌前准备");
star.sing();
System.out.println("善后工作"); }
}
class Test{
public static void main(String[] args) {
// 创建明星对象
IStar ldh = new LDHStar();
ProxyManger proxy = new ProxyManger(ldh);
proxy.sing();
}
}
静态代理总结:
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展.
缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
-
动态代理
在动态代理中,代理类是在运行时期生成的。因此,相比静态代理,动态代理可以很方便地对委托类的相关方法进行统一增强处理,如添加方法调用次数、添加日志功能等等。
动态代理机制的相关类和接口- java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入目标接口、目标接口的类加载器以及InvocationHandler便可为目标接口生成代理类及代理对象。
- java.lang.reflect.InvocationHandler:该接口包含一个invoke方法,通过该方法实现对委托类的代理的访问,是代理类完整逻辑的集中体现,包括要切入的增强逻辑和进行反射执行的真实业务逻辑。
- java.lang.ClassLoader:类加载器类,负责将类的字节码装载到Java虚拟机中并为其定义类对象,然后该类才能被使用。Proxy静态方法生成动态代理类同样需要通过类加载器来进行加载才能使用,它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的而非预存在于任何一个.class 文件中。
JDK动态代理使用步骤
JDK动态代理的一般步骤如下:
-
创建被代理的接口和类;
-
实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;
-
调用Proxy的静态方法,创建代理类并生成相应的代理对象;
使用代理。
package com;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Callable;
public class Test{
//父亲接口
interface HelloService{
String hello(String name);
String hi(String msg);
}
//接口的一个继承,被代理的对象
public class HelloServiceImpl implements HelloService{
@Override
public String hello(String name) {
return "Hello " + name;
}
@Override
public String hi(String msg) {
return "Hi, " + msg;
}
}
//继承InvocationHandler的一个代理类
public class MyInvocationHandler implements InvocationHandler{
// 真实业务对象
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强逻辑
System.out.println("PROXY : " + proxy.getClass().getName());
// 反射调用,目标方法
Object result = method.invoke(target, args);
// 增强逻辑
System.out.println(method.getName() + " : " + result);
return result;
}
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 生成要被代理类的实例
HelloService helloService = new Test().new HelloServiceImpl();
// 创建InvocationHandler
InvocationHandler myInvocationHandler = new Test().new MyInvocationHandler(helloService);
HelloService proxy = (HelloService)Proxy.newProxyInstance(helloService.getClass().getClassLoader(), helloService.getClass().getInterfaces(), myInvocationHandler);
proxy.hello("sss");
proxy.hi("SSSS");
}
}
对代理这个部分理解不是很深刻,以后再补充
30 克隆clone()
Object类中自带的clone方法使用的是浅克隆,除了八个基本类型以外都是传递地址,假设定义一个类A的定义如下,对其进行克隆的时候int a是深复制,类对象B b是浅复制,要想实现深复制则需要对类B实现clone()方法。
class A{
int a;
B b;
String s;
}
package com;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Callable;
public class Test{
class Test1 implements Cloneable{
String s1;
void setString(String str) {
s1 = str;
}
String reString() {
return s1;
}
protected Object clone() throws CloneNotSupportedException {
Test1 t = (Test1)super.clone();
return t;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Test1 test1 = new Test().new Test1();
test1.setString("aaaa");
Test1 test12 = (Test1) test1.clone();
System.out.println(test1.reString() == test12.reString());
System.out.println(test1.reString().hashCode());
System.out.println(test12.reString().hashCode());
test12.setString("BBBB");
System.out.println(test1.reString());
System.out.println(test1.reString().hashCode());
System.out.println(test12.reString());
System.out.println(test12.reString().hashCode());
}
}
有两个地方要注意:
- 在克隆过程中,String类可以说是深复制
- 一维数组是深复制,但是二维就不是了
int[] a={3,1,4,2,5};
int[] b=a.clone();
b[0]=10;
System.out.println(b[0]+" "+a[0]);
输出为10 3
可见改变了b的值,但是没有改变a的元素的值
int[][] a={{3,1,4,2,5},{4,2}};
int[][] b=a.clone();
b[0][0]=10;
System.out.println(b[0][0]+" "+a[0][0]);
输出为10 10
int[][] a={{3,1,4,2,5},{4,2}};
int[][] b=a.clone();
b[0][0]=10;
System.out.println(b[0][0]+" "+a[0][0]);
System.out.println(a[0]==b[0]);
第5句输出为true。