面试题总结

5、数据库的隔离级别有哪些?事务的特性有哪些?
关于隔离界别,事务,这些一定要放在一起说,因为他们就是一样东西。
面试官可能会有多种问法,事务,数据库事务,事务的隔离级别、数据库的隔离界别。
事务的操作指的就是数据库事务,事务的隔离级别就是数据库隔离级别,准确来讲应该是数据库事物的隔离级别。这中间关系来缕一缕。
首先是事务,事务从业务上面来讲,指的就是,做一件事。对数据库来讲就是对数据库进行crud多次操作,而这些操作是一个整体。要么发生,要么不发生。业务中应该有很多这样的操作,所以事务的存在是合理的。那这样的事务得有个标准,所以冒出四个特性
原子性:一个事务分割不了,要么全部成功,要么回滚失败。(内部)
隔离性:每个事务都是独立的,多个事物之间是隔开的。(外部)
持久性:指一个事务在提交完成之后,对数据的操作是永久的。(感觉很废话)
一致性:执行完事务之后,数据库状态应该从一个状态变成另一个状态。(状态的确定性)
这四个特性把事务解释的太过细致,导致我特别不想去记这些东西。但这些特性跟下面记忆又有关系。就是,事务有原子性,一致性,持久性,所以才有了隔离性,如果没有隔离性,那就出现。也是事务在并发情况下出现的问题
脏读:在事务还没有提交,就读取了事务相关的数据(因为这个事务如果回滚,那读的数据就是不对的。)
不可重复读和幻读:这两个放在一起是因为两个都是一个事物之中被读到两次的数据因别的事务修改了而不同的情况。只是前者侧重修改,后者侧重增删。
这些问题,所以隔离性上,数据库又有了四个隔离级别,来支持前三个特性。
从上往下隔离级别依次越来越高。mysql默认的事务隔离级别为repeatable-read
读未提交(read-uncommitted)都防不了
读已提交(read-committed)使用“快照读(Snapshot Read),防脏读,防不了不可重复读和幻读。
可重复读(repeatable-read)使用“快照读(Snapshot Read)和锁住被读取记录,可防脏读和不可重复读,防不了幻读,mysql默认的数据库隔离级别
串行化(serializable)啥都能防 ,就像直接加锁了,并发操作直接排队来,虽然啥都能防,但是效率极低,基本不敢用。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
从这四点隔离界别开始理解,即反向理解就能理解并记住这一长串的知识点了。首先记住隔离两个字。
4、在分布式服务中,一个生成订单的接口别一次性点击了很多次,在集群服务器中生成了很多的订单怎么办?
答:这个用redis分布式锁来解决。
这其实是一个解决幂等性(数学和计算机领域的一个概念,就是对某一个动作进行多次操作,都只能产生一种结果。例如,在http协议的访求方式中,同样的提交请求,PUT方法就是幂等性的,POST方法就是非幂等性)的问题。对于同一个用户,通过生成订单接口,我们希望一次只能生成一个订单,即使是通过集群订单微服务,即使用户能够多次点击生成订单接口。那么,这个现象我们换一种说法就是,一个身份,我要求在一个短暂的时间内只能生成一个订单,集群微服务在进行事物之前,都去检验某个相同的地方,满足这三个条件就能解决这个问题。这个身份就是用户的token,很简单,时间可以定义在三秒或四秒,放的位置就在缓存nosql数据库redis就最好。
具体可以使用redis的两个方法 getset 和 getnx(SET if Not eXists) 两个方法来解决。两个方法的特点是,前者:在设置一个(key:value)值的时候返回这个key对应的旧的value;后者:在设置一个(key:value)值的时候,如果这个key已经存在,那就设置不进去,返回0,如果可不存在则设置进去了,返回1,而且这个方法能直接设置有效期。这两个方法相比来讲,后者更加适合当作redis分布式锁。
getnx:主要代码部分

// 返回1表示锁获取成功,返回0表示锁取失败。lockKey是通过token得来的,
//例如String lockKey = MD5Util.md5Of32(token).append("_lock").toString(),它是key值
//nowTime是此刻时间,它是value值,expire值的意义是,在这个时间内本应该生成订单的,如果超过这个时间这个键值对还在的花,后面的也可以替换掉它。
		String result = jedisCommands.set(lockKey, nowTime, "NX", "PX", expire);
		if ("1".equals(result)) {
			return true; //返回true说明拿到了这个锁,可以进行事物(例如生单)操作
		} else {
			String oldTime = redisTemplate.opsForValue().get(lockKey);
			if (null != oldTime) {
				// 检查锁lockKey的值是不是超过了设定的时间,如2秒钟,如果超过了则继续尝试获取锁,
				// 直到获取到锁,或者数据未超期时退出,循环判断可以解决死锁的问题
				if (now - Long.parseLong(oldTime) >= expire) {// 数据已经过期了
					con = true;
				}
			}
getset:主要代码部分
	long now = System.currentTimeMillis();
	// Redis的GetSet返回的值必须是字符串,否则会抛异常,因而将其转换为字符串
	String nowTime = String.valueOf(now);
	String oldTime = null;
	// 判断用于锁定的key是否已经被设置了值,如果被设置了值,则用于控制后续的处理逻辑不再进行
	if ((oldTime = redisTemplate.opsForValue().getAndSet(lockKey, nowTime)) != null) {
		// 检查锁lockKey的值是不是超过了设定的时间,如2秒钟,没有超过则返回,不继续处理后续的任务;
		// 针对正常的业务请求这个是可以约定的,针对非正常的请求,被拦截也很正常,所以这个问题不是问题。
		if (now - Long.parseLong(oldTime) < timeout) {
			return false;
		}
		return true;
	}
	return true;

最终:redis分布式锁,其实就是借用redis的缓存一用,我做事物的时候,给个标记,相同就别进来了,事物做完了,我自己删除那个标记。如果,我没做完的过程中死掉了,没删掉这个标记,那个标记的过期时间就是我的死亡时间。

3、Synchronized的原理是什么?“锁”到底是什么?怎么确定锁?可重入锁和不可重入锁是什么概念?
1、其实被加了所的代码块在编译成字节码之后会看到,在代码块的前面又一个monitorenter指令,代码块末尾有个moniterexit指令,这些指令都是有个Reference类型参数,其实这个参数就是锁。Synchronized的原理就是,当一个线程通过monitorenter时,当前的对象没有被锁定,或者当前线程已经拥有了这个对象的锁,会给当前的对象锁的计数器+1,到monitorexit时,该锁的及计数器-1。只有当对象的计数器为0的时候,锁才会被释放。当锁计数器不为0的时候,别的线程拿到该对象的锁,则进入阻塞等待状态,知道别线程释放这个锁
2、锁就是Reference类型的对象,它是monitorenter和monitorexit指令的参数。当synchronized有传入对象做参数的时候,锁就是这个参数,没有传入对象的时候,那就分被修饰的方法是静态还是非静态。非静态时,锁就是调用该方法的当前对象,就是this;静态的时候,那锁就是该静态方法所属的静态类了。
3、可重入锁就是在已经持有一个对象锁的情况下还可以再次进入该对象锁的情况,反之就是不可重入锁。进入一个持有对象锁的代码块,该锁计数器+1,在代码块里面再进入还是此锁的代码块,那该所计数器再+1,就成了2了,以此类推,只要是可重入锁,就继续往上加,只要到最后释放一次次-1就行。Synchronized和ReentrantLock是可重入锁。

2、用过ThreadLocal吗?它是什么?在什么场景下使用
ThreadLocal其实是一个带有泛型的变量,它是用来存放不同线程所需要的不同成员变量的,就是多个线程公用一个成员变量,还希望它存储的东西不同,那就用ThreadLocal来存储。因为这个类型的底层是一个map,这个类里面有Thread.currentThread()获取当前的线程,没存入一个对象,都把线程和对象以键值对的形式存放在map以供后面get提取调用。例如在订单模块,购物车模块,那些明确需要用户信息的模块,一般在一个拦截器里面,拦截器里面一个静态get方法提取ThreadLocal里面的对象,后面直接可以用拦截器.方法得到同一个线程的对象。

1、一个类,重写equals方法后一定要重写hashCode方法吗?
是的。
如果一个类重写了equals方法但是没有重写hashCode方法,那么该类无法结合所有基于散列的集合(HashMap,HashSet)一起正常运作。解释一下就是,重写了equals方法,说明程序对该对象有自己的等值比较方法,但是如果想往在hashMap等散列集合中存入该对象当作key值得话,它是先比较hashCode值的,所以也要重写hashCode方法。
例如:一个Person类的对象,只要他们名字一样就代表这两个对象是一样的,要这样重写

    static class Person {
        private String name;
        public Person(String name) {
            this.name = name;
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Person) {
                Person person = (Person) obj;
                return name.equals(person.name);
            }
            return false;
        }
        @Override
        public int hashCode() {
            return name.hashCode();
        }
    }

直接将name的hashCode的值返回,当成对象的hashCode的值。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值