Java八股文001

Mysql数据库

SQL标准的事务隔离级别

以下说的都是并发情况也就是并发事务

  1. 读未提交:一个事务可以读取到其他事务还没有提交的更改,这是数据库事务的最低隔离级别;可以导致脏读、幻读、不可以重复读;
  2. 读已提交:一个事务只能读取到其他事务已经提交的更改;可以解决脏读,但是无法处理不可重复读、幻读;
  3. 可以重复读:一个事务对于同一个字段的多次读取结果是一致的,除非是本身事务对于字段进行了修改,可以避免脏读、不可重复读的问题;但是幻读仍可发生;
  4. 可串行化:事务最高隔离级别,会严重降低并发度,所有事务均按照顺序执行,互不干扰,可以避免脏读、不可重复读、幻读;

脏读

一个事务A修改了一个表中的属性P,P本来的属性是11,事务A想要修改属性P,修改成P=P-1也就是10。这时候另一个事务B读取这个表中的这个记录的属性P,读取到了P=10,此时事务A回滚了,没有将修改提交到数据库,那么P=11,但是事务B获取到的是10;导致事务B出现了脏读;

修改丢失

一个事务A读取一个数据,另一个事务B也读取了这个数据,事务A更新了这个数据,然后事务B也更新了这个数据,这时候事务A的修改会被覆盖掉,也就是丢失了;

不可重复读

在一个事务A内,对于一个数据在第一次读取的时候是一个值V,另一个事务B此时也访问并且修改了这个数据,将其修改为V1,这时事务A第二次读取这个数据,获取到的值为V1,在同一个事务A内多次获取的数据的值不一致;

幻读

一个事务A进行了一次范围查询,查询来一批数据,此时事务B在这个范围插入了一些记录,这时事务A再次按照相同的范围进行查询,发现查询出来的数据比上一次查询出来的数据要多,仿佛出现了幻觉;

InnoDB 行锁是通过对索引数据页上的记录加锁实现的,MySQL InnoDB 支持三种行锁定方式:

  • 记录锁(Record Lock):也被称为记录锁,属于单个行记录上的锁。
  • 间隙锁(Gap Lock):锁定一个范围,不包括记录本身。
  • 临键锁(Next-Key Lock):Record Lock+Gap Lock,锁定一个范围,包含记录本身,主要目的是为了解决幻读问题(MySQL 事务部分提到过)。

记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。在 InnoDB 默认的隔离级别 REPEATABLE-READ 下,行锁默认使用的是 Next-Key Lock。但是,如果操作的索引是唯一索引或主键,InnoDB 会对 Next-Key Lock 进行优化,将其降级为 Record Lock,即仅锁住索引本身,而不是范围。

TCP 三次握手

建立一个 TCP 连接需要“三次握手”,缺一不可:

  • 一次握手:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 SYN_SEND 状态,等待服务端的确认;
  • 二次握手:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 SYN_RECV 状态;
  • 三次握手:客户端发送带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务端都进入ESTABLISHED 状态,完成 TCP 三次握手。

为什么要三次握手?三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。

  1. 第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
  2. 第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
  3. 第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常

也可以用打电话举例子:
A(client)给B(server)打电话

  1. A拨号给B打电话,B电话响了,这时候B听到电话响了看了一下来电显示是A打过来的。此时A什么都不知道,B知道A可以打电话并且自己B可以收到别人给自己打的电话;(client什么都不知道, server知道自己接收正常client发送正常);
  2. B接听电话,然后说了一下我电话响了,你可以听到我说的么。这时候A知道自己发送、接收正常、B发送、接收正常。B知道自己接收正常、A发送正常;
  3. A听见B在问自己,也回复了一下,说我听见了。这时候A知道自己发送、接收正常,B发送、接收正常。B知道自己接收正常、自己发送正常、A发送正常、A接收正常;
    缺少了任何一步骤都会导致不能确认一端是否接收发送正常;

TCP四次挥手

断开一个 TCP 连接则需要“四次挥手”,缺一不可:

  1. 第一次挥手:客户端发送一个 FIN(SEQ=x) 标志的数据包->服务端,用来关闭客户端到服务端的数据传送。然后客户端进入 FIN-WAIT-1 状态。
  2. 第二次挥手:服务端收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (ACK=x+1)标志的数据包->客户端 。然后服务端进入 CLOSE-WAIT 状态,客户端进入 FIN-WAIT-2 状态。
  3. 第三次挥手:服务端发送一个 FIN (SEQ=y)标志的数据包->客户端,请求关闭连接,然后服务端进入 LAST-ACK 状态。
  4. 第四次挥手:客户端发送 ACK (ACK=y+1)标志的数据包->服务端,然后客户端进入TIME-WAIT状态,服务端在收到 ACK (ACK=y+1)标志的数据包后进入 CLOSE 状态。此时如果客户端等待 2MSL 后依然没有收到回复,就证明服务端已正常关闭,随后客户端也可以关闭连接了。

只要四次挥手没有结束,客户端和服务端就可以继续传输数据!

为什么要四次挥手?TCP 是全双工通信,可以双向传输数据。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。

举个例子:
A 和 B 打电话,通话即将结束后。

  1. 第一次挥手:A 说“我没啥要说的了”
  2. 第二次挥手:B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话.
  3. 第三次挥手:于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”
  4. 第四次挥手:A 回答“知道了”,这样通话才算结束.

锁状态

其实有图更好理解 偷懒不画了

死锁

死锁产生条件:

  • 互斥条件:共享资源被一个线程占用
  • 请求与保持条件(占有且等待):一个进程因请求资源而被阻塞时,对已经获得资源保持不释放
  • 不可剥夺条件(不可抢占):进程已获得资源,在未使用完之前,不能进行剥夺
  • 循环等待条件:多个线程 循环等待资源,而且是循环的互相等待

举例子:
一条河两岸(S1岸和S2岸),河上有两座单向单人桥(B1桥和B2桥),B1从S1通往S2, B2从S2通往S1,有两个人(P1甲和P2乙),P1在S1岸上去往S2岸,P2在S2岸去往S1岸,P1上B1桥之后就将B1桥独占,不允许任何人上桥,P2上B2桥之后就将B2桥独占,不允许任何人上桥;P1 和 P2到达对岸之后没有释放各自占有的桥,这时候P1和P2又想返回S1(P1到了S2后想回S1)和S2(P2到了S1之后想回S2),这时候就需要上返回的桥(P1想上B2返回S1, P2想上B1返回S2);但是发现还有别人占用,只能等待,P1 P2都在等待对方持有的锁释放,这样循环等待就死锁了;


死锁

活锁

死锁会阻塞,一直等待对方释放资源,一直处在阻塞状态;活锁会不停的改变线程状态尝试获得资源。
活锁有可能自行解开,死锁则不行。

一条河两岸(S1岸和S2岸),河上有一座双向向单人桥(B1桥),有两个人(P1甲和P2乙),P1在S1岸上去往S2岸,P2在S2岸去往S1岸,P1看到桥上没有人上桥,P2看桥上没有人上桥,两人都上桥往对岸走,双方走到一半发现有人,然后都后退让出桥,让对方过桥。双方退到各自岸边后再次查看桥上是否有人过桥,发现没有,然后又都上桥过桥,到一半发现有人,又退出,如此反复,构成活锁;


活锁

饥饿

一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。一直有线程级别高的暂用资源,线程低的一直处在饥饿状态。 比如ReentrantLock显示锁里提供的不公平锁机制,不公平锁能够提高吞吐量但不可避免的会造成某些线程的饥饿

一条河两岸(S1岸和S2岸),河上有一座单人桥(B1桥),有两个以上的人要过桥,谁抢到桥谁就过桥,其中有一个人动作较慢每次都被动作快的人抢先一步上桥,导致这个人一直没有办法上桥。这个人这种情况就是饥饿,由于动作慢(优先级低等)导致一直没有办法过桥。

AQS抽象同步队列器

个人简单理解

抽象队列器中主要有几个参数:

  • state 存储当前共享资源的锁状态 0未被锁定, >0已经有线程获取到锁;
  • CLH队列的头尾节点 存储的是等待锁资源的线程队列,该队列是一个双向链表存储,其中存放:当前等待锁资源的线程、前驱节点、后继节点;

加锁主要流程:

  • 如果一个线程获取一个共享资源,使用CAS操作设置AQS的state属性从0-》1,如果成功则加锁成功并将共享资源的独占线程设置为当前线程;
  • 如果设置state没有成功,那么就将当前线程加入到CLH队列中等待;当共享资源可用的时,会unpark CLH中的park的线程;

线程状态

  • NEW: 初始状态,线程被创建出来但没有被调用 start() 。
  • RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态。
  • BLOCKED:阻塞状态,需要等待锁释放。
  • WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
  • TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  • TERMINATED:终止状态,表示该线程已经运行完毕。

状态转换

图源:挑错 |《Java 并发编程的艺术》中关于线程状态的三处错误


转换关系图

线程上下文切换

线程在执行过程中会有自己的运行条件和状态(也称上下文);比如 程序计数器 虚拟机栈 本地方法栈各种栈信息;如果出现当前线程不在占用cpu的情况,需要保存这些运行条件和状态(上下文);出现cpu不在占用的情况有以下几种:

  1. 当前线程主动让出cpu,比如调用了sleep(), wait()等方法;
  2. 时间片用完了,当前线程占用cpu的时间片使用完了主动让出;因为操作系统防止一个线程长时间占用cpu导致其他线程或者进程饿死;
  3. 调用了阻塞类型的系统终端,比如请求IO,线程被阻塞;
  4. 被终止或者结束运行
    前三种会发生线程上下文切换,意味着需要保留当前线程上下文以便后续当前线程获取到cpu之后恢复现场继续运行;

线程池状态

  • RUNNING:运行状态,线程池创建好之后就是该状态,如果不调用关闭方法,线程池会在整个程序运行期间都是此状态;
  • SHUTDOWN:关闭状态,不在接受新的任务提交,但是会将已经保存在任务队列中的任务处理完
  • STOP:停止状态,不在接受新的提交任务,并且会终端正在执行的任务以及放弃任务队列中已有任务
  • TIDYING:整理状态,所有任务都执行完毕之后(包括任务队列中的任务也需要执行完毕),当前线程池中的活动线程数降为0时的状态。到此状态后,会调用线程池的terminated()方法
  • TERMINATED:销毁状态,当线程池执行完terminated()方法之后就会变成此状态

如果不调用关闭方法线程池除了RUNNING状态不会涉及到其他状态,除非调用shutdown相关方法;如下

  1. 当调用 shutdown() 方法时,线程池的状态会从 RUNNING 到 SHUTDOWN,再到 TIDYING,最后到 TERMENATED 销毁状态。
  2. 当调用 shutdownNow() 方法时,线程池的状态会从 RUNNING 到 STOP,再到 TIDYING,最后到 TERMENATED 销毁状态。

Lombok实现原理

主要时在编译期间对字节码进行处理,Java中javac对java进行编译主要步骤

  1. 词法分析
  2. 语法分析
  3. 填充符号表
  4. 插入式注解处理器处理
  5. 语义分析
  6. 解语法糖
  7. 生成字节码

lombok就是实现了插入式注解处理器,通过插入式注解处理器可以读取、修改、添加抽象语法树中的任意元素;

CGLIB代理

CGLIB代理不要求类必须实现接口,一个普通的类也可以被代理;它使用的是ASM字节码增强技术,可以在运行时对字节码进行修改和生成;

/**
 * @Author:
 * @ProjectName: data-content
 * @Description: 发送消息的逻辑代码
 */
@Slf4j
public class SendMessageDomain {
    public String sendMessage(String message, Long timeout) {
        log.info("发送消息 消息内容 {} {}", message, timeout);
        log.info("消息体");
        log.info("消息发送完了");
        return "success " + message + timeout;
    }
}
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author:
 * @ProjectName: data-content
 * @Description:
 */
@Slf4j
public class SendMessageInterceptor implements MethodInterceptor {
    /**
     * @param o           被代理的对象目标对象
     * @param method      被代理的方法 目标对象的目标方法
     * @param objects     目标方法的参数列表
     * @param methodProxy 用于调用目标方法的代理方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        log.info("增强方法");
        log.info("被增强方法: {}", method.getName());
        log.info("被增强对象: {} {}", JSON.toJSONString(o), o);
        log.info("被增强方法参数: {}", JSON.toJSONString(objects));
        log.info("代理方法: {}", methodProxy);
        Object invoke = methodProxy.invokeSuper(o, objects);
        log.info("被增强方法返回值: {}", invoke);
        return invoke;
    }
}

import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.Enhancer;

/**
 * @Author:
 * @ProjectName: data-content
 * @Description:
 */
@Slf4j
public class ProxyFactory {
    public static Object createProxy(Class<?> targetClass) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(targetClass.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(targetClass);
        // 设置方法拦截器
        enhancer.setCallback(new SendMessageInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

import lombok.extern.slf4j.Slf4j;

/**
 * @Author:
 * @ProjectName: data-content
 * @Description:
 */
@Slf4j
public class Main {
    public static void main(String[] args) {
        SendMessageDomain sendMessageDomain = new SendMessageDomain();
        Object proxy = ProxyFactory.createProxy(SendMessageDomain.class);
        log.info("Result: {}", proxy);
        String s = ((SendMessageDomain) proxy).sendMessage("hello cglib", 100L);
        log.info("Result: {}", s);
    }
}

Result: com.ihawk.data.cotent.test.proxy.cglib.SendMessageDomain$$EnhancerByCGLIB$$55cb1706@210ab13f
增强方法
被增强方法: sendMessage
被增强对象: {} com.ihawk.data.cotent.test.proxy.cglib.SendMessageDomain$$EnhancerByCGLIB$$55cb1706@210ab13f
被增强方法参数: ["hello cglib",100]
代理方法: net.sf.cglib.proxy.MethodProxy@1500b2f3
发送消息 消息内容 hello cglib 100
消息体
消息发送完了
被增强方法返回值: success hello cglib100
Result: success hello cglib100

JDK代理模式

  1. 必须要实现接口的类型才能被代理

使用

  1. 定义接口以及实现类,实现类可以实现多个接口
  2. 定义一个增加目标类的实现类H,该实现类H实现InvocationHandler接口,并实现invoke方法,invoke中调用目标类的目标方法并且实现在目标类目标方法前后的增强逻辑
  3. 使用public static Object newProxyInstance(ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h)方法获取一个代理对
  4. 获取到代理对象后转成目标对象接口 然后调用指定的方法
/**
 * @Author:
 * @ProjectName: data-content
 * @Description: 业务接口
 */
public interface ISendMessageService {
    String sendMessage(String message, int timeout);
}

/**
 * @Author:
 * @ProjectName: data-content
 * @Description: 业务接口实现类
 */
@Slf4j
public class SendMessageServiceImpl implements ISendMessageService {
    /**
     * 业务逻辑动作
     *
     * @param message
     */
    @Override
    public String sendMessage(String message, int timeout) {
        log.info("需要发送的内容 {} {}", message, timeout);
        log.info("已经发送完成");
        return "success" + message + timeout;
    }
}

/**
 * @Author:
 * @ProjectName: data-content
 * @Description: 增强类型 对于一个目标类型进行增强 以及扩展相关逻辑的功能
 */
@Slf4j
public class SendMessageInvocationHandler implements InvocationHandler {
    /**
     * 目标对象 被增强的对象
     */
    private final Object target;

    /**
     * 初始化时赋值被增强的对象
     *
     * @param target
     */
    public SendMessageInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * @param proxy  生成的代理对象
     * @param method 被增强的方法
     * @param args   方法参数 被增强的方法的参数列表
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("Invoking method {}", method.getName());
        log.info("arguments {}", JSON.toJSONString(args));
        log.info("proxy {} {}", JSON.toJSONString(proxy), proxy);
        //调用目标对象的目标方法
        Object result = method.invoke(target, args);
        log.info("Method {} result {}", method.getName(), result);
        return result;
    }
}


/**
 * @Author:
 * @ProjectName: data-content
 * @Description: 根据目标方法获取一个代理对象
 */
public class ProxyFactory {
    /**
     *
     * @param target 被代理的目标对象
     * @return
     */
    public static Object getProxy(Object target) {
        /**
         * 1 目标对象的类加载器
         * 2 目标对象的接口列表
         * 3 目标对象的增加对象
         */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                                      new SendMessageInvocationHandler(target));
    }
}

/**
* @Author:
* @ProjectName: data-content
* @Description:
*/
@Slf4j
public class MainProxy {

    public static void main(String[] args) {
        SendMessageServiceImpl sendMessageService = new SendMessageServiceImpl();
        Object proxy = ProxyFactory.getProxy(sendMessageService);
        log.info("Starting sendMessageService {}", sendMessageService);
        log.info("Starting proxy {}", proxy);
        String s = ((ISendMessageService) proxy).sendMessage("Hello, World!", 100);
        log.info("Result: {}", s);
    }
}

//结果输出
Starting sendMessageService com.ihawk.data.cotent.test.proxy.jdk.SendMessageServiceImpl@255b53dc
Starting proxy com.ihawk.data.cotent.test.proxy.jdk.SendMessageServiceImpl@255b53dc
Invoking method sendMessage
arguments ["Hello, World!",100]
proxy {} com.ihawk.data.cotent.test.proxy.jdk.SendMessageServiceImpl@255b53dc
需要发送的内容 Hello, World! 100
已经发送完成
Method sendMessage result successHello, World!100
Result: successHello, World!100

TCP/IP4分层&OSI分层

TCP/IP4分层

  1. 应用层
  2. 传输层
  3. 网络层
  4. 网络接口层

OSI分层

  1. 应用层 为计算用户提供服务
  2. 表示层 数据处理(编解码 加解密 压缩解压缩)
  3. 会话层 管理(建立 维护 重连)应用程序之间的会话
  4. 传输层 提供两台主机进程传输通信提供通用数据传输服务
  5. 网络层 路由和寻址
  6. 数据链路层 帧编码 误差纠正控制
  7. 物理层 传输比特数据流

两种模型分层对应关系

TCP 分层的 1 对应 OSI分层的 1 2 3
TCP 分层的 2 对应 OSI分层的 4
TCP 分层的 3 对应 OSI分层的 5
TCP 分层的 4 对应 OSI分层的 6 7

泛型

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。

  1. 泛型类
/此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{

    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}

//使用这个类
Generic<Integer> genericInteger = new Generic<Integer>(123456);
  1. 泛型接口
public interface Generator<T> {
    public T method();
}
//实现这个接口的类
//不指定这个泛型的具体类型
class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}
//指定这个泛型的具体类型
class GeneratorImpl implements Generator<String> {
    @Override
    public String method() {
        return "hello";
    }
}

3.泛型方法

   public static < E > void printArray( E[] inputArray )
   {
         for ( E element : inputArray ){
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
   // 使用
   // 创建不同类型数组:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray  );
printArray( stringArray  );

注意: public static < E > void printArray( E[] inputArray ) 一般被称为静态泛型方法;
在 java 中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的泛型<E>

finally中的代码一定会执行吗

不一定会被执行: <a id="section1"></a>

  1. 当执行到finally之前 jvm已经退出了,这时候不会执行其中的代码块;如下
try {
    System.out.println("Try to do something");
    throw new RuntimeException("RuntimeException");
} catch (Exception e) {
    System.out.println("Catch Exception -> " + e.getMessage());
    // 终止当前正在运行的Java虚拟机
    System.exit(1);
} finally {
    System.out.println("Finally");
} 
  1. 程序所在的线程死亡
  2. cpu关闭

方法重载

在遇到方法重载时候,方法既有多个参数的又有可变参数,在调用方法的时候具体调用哪一个方法;具体参数的优先级高于可变参数的方法,也就是说优先匹配具有具体参数的方法;如下

public class VariableLengthArgument {

    public static void printVariable(String... args) {
        System.out.println("可变参数");
        for (String s : args) {
            System.out.println(s);
        }
    }

    public static void printVariable(String arg1, String arg2) {
        System.out.println("具体参数 "+arg1 + arg2);
    }

    public static void main(String[] args) {
        printVariable("a", "b");
        printVariable("a", "b", "c", "d");
    }
}
//输出内容  
具体参数 ab
可变参数
a
b
c
d

可变参数在编译之后会变为一个数组类型的数据;

移位运算

Java 中有三种移位运算符:

  • << :左移运算符,向左移若干位,高位丢弃,低位补零。x << n,相当于 x 乘以 2 的 n 次方(不溢出的情况下)。
  • >> :带符号右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。x >> n,相当于 x 除以 2 的 n 次方。
  • >>> :无符号右移,忽略符号位,空位都以 0 补齐。

由于 double,float 在二进制中的表现比较特殊,因此不能来进行移位操作。
移位操作符实际上支持的类型只有int和long,编译器在对short、byte、char类型进行移位前,都会将其转换为int类型再操作。
如果移位的位数超过数值所占有的位数会怎样?
当 int 类型左移/右移位数大于等于 32 位操作时,会先求余(%)后再进行左移/右移操作。也就是说左移/右移 32 位相当于不进行移位操作(32%32=0),左移/右移 42 位相当于左移/右移 10 位(42%32=10)。当 long 类型进行左移/右移操作时,由于 long 对应的二进制是 64 位,因此求余操作的基数也变成了 64。
也就是说:x<<42等同于x<<10,x>>42等同于x>>10,x >>>42等同于x >>> 10。
使用 >>>进行右移位运算;

注意 这是无符号右移位 这个操作会忽略高位符号位,空位补充0;

使用原因

  • 高效:现在计算硬件可以专门处理移位操作
  • 便于操作二进制数的每一位数据 包括 判断一个数的二进制的某位是否为0 或者 1,比如布隆过滤器
  • 整数在内存中 数据对齐 解决一些伪共享

伪共享

伪共享:计算机不同线程处理不同数据但是这些数据却在同一个缓存行上,需要频繁的失效,加载缓存行引起;
缓存行:一般缓存行是64字节,cpu缓存行一次必须加载一个缓存行的数据,这是缓存行的最小单位。
一般使用填充数据补充上缓存行的数据解决这个问题。

  1. 使用手动填充数据填充缓存行
class Pointer2 {
    MyLong a = new MyLong();
    MyLong b = new MyLong();
}

class MyLong {
    volatile long value;
    long p1, p2, p3, p4, p5, p6, p7;
}
  1. 使用@sun.misc.Contended 注解(java8)

@Contended 注解会增加目标实例大小,要谨慎使用。默认情况下,除了 JDK 内部的类,JVM 会忽略该注解。要应用代码支持的话,需要在jvm启动参数上设置 -XX:-RestrictContended=false,它默认为 true(意味仅限 JDK 内部的类使用)。当然,也有个 –XX: EnableContented 的配置参数,来控制开启和关闭该注解的功能,默认是 true,如果改为 false,可以减少 Thread 和 ConcurrentHashMap 类的大小.

jdk内部解决伪共享

jdk中的ConcurrentHashMap类

@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}

jdk8中LongAdder的父类Striped64
Striped64中的内部类Cell,使用@sun.misc.Contended注解,说明里面的值消除了伪共享

学习JavaGuide,记录简单笔记

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容