ConcurrentHashMap
在 Java 7 之前是 section 分段锁,Java 8 之后引入了 Node 数组 + 链表 + 红黑树,使用 CAS + synchronized 来保证并发。由于 Java 8 之后只锁住头节点,锁的粒度更细,所以性能也更高。在链表长度超过 8 时由链表变成红黑树,防止时间复杂度退化到O(N).
创建线程的几种方式
继承 Thread 类
这种是最简单的方法,但是扩展性比较差,其实很少用。示例代码在下面
class MyThread extends Thread { @Override public void run() { System.out.println("Thread: " + Thread.currentThread().getName()); } } new MyThread().start();实现 Runnable 接口
这种方法比较常用,可以搭配线程池来使用,缺点是无法获取线程的执行结果,无法抛出异常。
Runnable task = () -> System.out.println("Runnable: " + Thread.currentThread().getName()); new Thread(task).start();实现 Callable + FutureTask
这种方法可以实现抛出异常的接受,缺点是代码比较繁琐,而且使用 get 方法去得到线程执行结果时是一个阻塞的操作,会耗费时间,一般要搭配超时限制。
Callable<String> callable = () -> { TimeUnit.SECONDS.sleep(1); return "Result from Callable"; }; FutureTask<String> future = new FutureTask<>(callable); new Thread(future).start(); String result = future.get(); // 阻塞等待结果使用线程池 ThreadPoolExecutor
使用线程池可以复用线程,避免频繁创建/销毁的开销,同时还可以控制并发数,避免内存超载。要注意执行失败时的拒绝策略,这里有4种:1.默认丢弃 2.由提交的线程自己执行失败任务 3.报异常,需要捕获异常 4.丢弃当前队列中最老的任务,重新加入队列。
数据库三大范式
第一范式 原子性
要求字段不能再拆分,必须有主键,不能有重复的行。举个例子就是一个表格里面不能既放住址又放电话,只能放一个。
第二范式 消除部分依赖
这一条是针对联合主键,非主键字段必须依赖于完整的主键,不能只依赖与一个主键。如果只有一个主键,那么自动满足第二范式。这样做可以消除因数据更新导致的异常。
第三范式 消除传递依赖
所有非主键字段必须直接依赖于主键,不能非主键字段 A 依赖与 非主键字段 B,然后 B 再依赖于主键 C。这样可以进一步消除冗余,确保每个非主键字段都只与主键有直接关系,避免修改一处需要同步修改多处的问题。