零碎的知识点-2

零碎的知识点-2

死锁及其解决方案(避免、预防、检测)

死锁产生的原因

因竞争资源发生死锁 现象:系统中供多个进程共享的资源的数目不足以满足全部进程的需要时,就会引起对诸资源的竞争而发生死锁现象。

死锁的四个必要条件:

  • 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源

  • 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放

  • 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放

  • 环路等待条件:是指进程发生死锁后,必然存在一个进程–资源之间的环形链

处理死锁的基本方法

预防死锁(破坏四个必要条件)

  • 资源一次性分配:(破坏请求和保持条件)
  • 可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)

  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

避免死锁(银行家算法)

上面的预防算法会严重地损害系统性能。而避免死锁算法是让系统在进行资源分配之前预先计算资源分配的安全性。

检测死锁

首先为每个进程和每个资源指定一个唯一的号码;

然后建立资源分配表和进程等待表。

解除死锁

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

剥夺资源

从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;

撤消进程

可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

Linux 命令&和&&的区别

在Linux控制台命令的执行过程中,

&表示该命令后台执行。

例如执行:ping 192.168.1.* &

表示后台不停的执行ping命令,即便你强制中断掉该线程,它仍然会不停的输出结果。除非你将该线程kill掉。

&&表示并行执行前后命令。

Linux—shell中$(( ))、$( )、$${ }的区别

在bash中,$( )与(反引号)都是用来作命令替换的。
命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。

redis的持久化方式RDB和AOF的区别

redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),另外一种是AOF持久化(原理是将Reids的操作日志以追加的方式写入文件)。

二者却别:

  • RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

  • AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

MYSQL innodb中MVCC的一些理解

MVCC简介

MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能

  • 读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

  • 写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

  • 表锁:操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。

  • 行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。

MVCC有下面几个特点:

  • 每行数据都存在一个版本,每次数据更新时都更新该版本
  • 修改时Copy出当前版本随意修改,各个事务之间无干扰
  • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

一句话讲,MVCC就是用 同一份数据临时保留多版本的方式 的方式,实现并发控制。

TCP三次握手和四次挥手

三次握手

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据,在上述过程中,还有一些重要的概念:

未连接队列

在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于SYN_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。

TCP握手

TCP挥手

TCP挥手

为什么TCP协议终止链接要四次?

  1. 当主机A确认发送完数据且知道B已经接受完了,想要关闭发送数据口(当然确认信号还是可以发),就会发FIN给主机B。
  2. 主机B收到A发送的FIN,表示收到了,就会发送ACK回复。
  3. 但这是B可能还在发送数据,没有想要关闭数据口的意思,所以FIN与ACK不是同时发送的,而是等到B数据发送完了,才会发送FIN给主机A。
  4. A收到B发来的FIN,知道B的数据也发送完了,回复ACK, A等待2MSL以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,A就关闭链接,B也关闭链接了。

TCP挥手时,为什么等待2MSL,从TIME_WAIT到CLOSE?

在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

C语言文件读写

C语言文件读写

Redis分布式锁

可靠性

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  • 互斥性 在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁 即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 具有容错性 只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  • 解铃还须系铃人 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

代码实现

加锁代码

public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

解锁代码

···java
public class RedisTool {

private static final Long RELEASE_SUCCESS = 1L;

/**
 * 释放分布式锁
 * @param jedis Redis客户端
 * @param lockKey 锁
 * @param requestId 请求标识
 * @return 是否释放成功
 */
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

    if (RELEASE_SUCCESS.equals(result)) {
        return true;
    }
    return false;

}

}
“`

总结

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的。

Java类加载机制

双亲委派加载机制

双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

  1. 遇见一个类需要加载的类,它会优先让父加载器去加载。层层传递。
  2. 每个类加载器都有自己的加载区域,它也只能在自己的加载区域里面寻找。
  3. 自定义类加载器也必须实现这样一个双亲委派模型。
  4. 双亲委派机制是隔离的关键, 如String.class:
    1. 一个JVM里面只能有一个String.class。
    2. 用户没法自定义个String.class出来。
    3. 每个Classloader都有自己的加载区域,需要注意部分配置文件的存放地点。

类加载的隔离机制

类加载的隔离机制
通过不同的 完整类名 和 classloader, 可以区分两个类。好处为内存隔离(最常见的就是静态变量)。

  • 类名不一致一定不是同一个类
  • 类名一致类加载器不一致也不是同一个类(eaquels false)
  • 类名一致类加载器一致但是类加载器实例不一致也不是同一个类。

Tomcat提供了一个Child优先的类加载机制:首先由子类去加载, 加载不到再由父类加载。就很好的规避了Tomcat lib里面的包与webapps中用户的包互相冲突。WEB-INF/lib 目录下的类的加载优先级是优于Tomcat lib的。(配置文件在server.xml里面的 default false)上。

针对Tomcat, 做一个加载路径的介绍:

  • Tomcat起始于catalina.sh里面的命令 java org.apache.catalina.startup.Bootstrap start
  • 因为显式的指定了java命令,因此
    • BootstrapClassLoader负责加载${JAVA_HOME}/jre/lib部分jar包
    • ExtClassLoader加载${JAVA_HOME}/jre/lib/ext下面的jar包
    • AppClassLoader加载bootstrap.jartomcat-juli.jar(只显示的指定了这两个jar包)
    • 之后Tomcat通过初始化了三个URLClassLoader, 并指定加载路径 (见catalina.properties#common.loader配置)
    • 除了common外, server和shardLoader的加载路径一般都没有显示的指定, 因此这三个Loader实际上都是URLClassLoader。
    • 同时,它顺便指定了当前线程的contextClassLoader。
    • Tomcat对于WEB应用的启动都是依赖于web.xml的, 里面配置的Filter、Listener、Servlet根据Tomcat的定义都是由WebappClassLoaderBase来加载的。
    • 毕竟FilterListenerServlet等入口都是被WebappClassLoaderBase加载的,而一般开发者不会主动指定ClassLoader。那么除非指定了ClassLoader,所有的webapp都是它加载的(刚好它的加载空间包含了这些类)
    • 在需要Spring的时候已经由App自身加载得到, 就不会再去寻找Tomcat lib里面的Spring。
    • 自此,Tomcat的类加载区分完毕。 通过 “子优先” 这个机制,可以保证多个 Tomcat App 之间做到良好的隔离。

类加载的顺序

当需要用一个类的时候, 必须先加载它。

  1. 装载:查找和导入Class文件;
  2. 链接:把类的二进制数据合并到JRE中;
  3. 校验:检查载入Class文件数据的正确性;
  4. 准备:给类的静态变量分配存储空间;
  5. 解析:将符号引用转成直接引用;
  6. 初始化:对类的静态变量,静态代码块执行初始化操作

类加载的触发点

  • 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
    • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
    • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

类的实例化

类只有在加载进入JVM之后才能被使用, 但是一般情况下还需要把类做实例化操作后来用。 一般区分为显式的实例化和隐式的示例化。

类的实例化的目的是为了得到一个类的对象。

  • new 方法为隐式的实例化。方便可用性高。
  • newInstance()为显式的实例化。必须要一个无参构造器。
  • 两种实例化方式都需要ClassLoader的参与。显式的实例化往往指定,隐式的实例化则默认是调用的这个类的类加载器。
  • 实例化的目的都是为了得到类的对象。而类在实例化之前已经初始化完毕。
  • 实例化的时候会为域变量赋值,并执行代码块的方法。

为什么静态元素和静态代码块在一个虚拟机里面只会执行一次 ?
1. 默认习惯都是不会指定ClassLoader的,所属类也就只有一次初始化过程。
2. 赋值静态域,或者执行静态代码块,是在类加载的流程中执行的。而这样的操作只会有一次。
3. 赋值域,或者执行代码块,是在类实例化的流程中执行的,这样的操作根据程序需求可能有多次。

JVM的符号引用和直接引用

在JVM中类加载过程中,在解析阶段,Java虚拟机会把类的二级制数据中的符号引用替换为直接引用。

  1. 符号引用(Symbolic References): 符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。

  2. 直接引用:
    直接引用可以是
    (1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
    (2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
    (3)一个能间接定位到目标的句柄

直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。

MySQL主从复制、半同步复制和主主复制概述

MySQL数据库支持同步复制、单向、异步复制,在复制的过程中一个服务器充当主服务,而一个或多个服务器充当从服务器。主服务器将更新写入二进制日志文件,并维护文件的一个索引以跟踪日志循环。这些日志可以记录发送到从服务器的更新。当一个从服务器连接主服务器时,它通知主服务器从服务器在日志中读取的最后一次成功更新的位置。从服务器接收从那时起发生的任何更新,然后封锁并等待主服务器通知新的更新。

主从复制的原理:

  1. 数据库有个bin-log二进制文件,记录了所有sql语句。
  2. 我们的目标就是把主数据库的bin-log文件的sql语句复制过来。
  3. 让其在从数据的relay-log重做日志文件中再执行一次这些sql语句即可。
  4. 下面的主从配置就是围绕这个原理配置

主从复制

原理

  1. 具体需要三个线程来操作:

    1. binlog输出线程:每当有从库连接到主库的时候,主库都会创建一个线程然后发送binlog内容到从库。

    在从库里,当复制开始的时候,从库就会创建两个线程进行处理:

    1. 从库I/O线程:当START SLAVE语句在从库开始执行之后,从库创建一个I/O线程,该线程连接到主库并请求主库发送binlog里面的更新记录到从库上。从库I/O线程读取主库的binlog输出线程发送的更新并拷贝这些更新到本地文件,其中包括relay log文件。

    2. 从库的SQL线程:从库创建一个SQL线程,这个线程读取从库I/O线程写到relay log的更新事件并执行。

可以知道,对于每一个主从复制的连接,都有三个线程。拥有多个从库的主库为每一个连接到主库的从库创建一个binlog输出线程,每一个从库都有它自己的I/O线程和SQL线程。

猜你喜欢

转载自blog.csdn.net/weixin_35040169/article/details/81457189