事务

Spring声明式事务管理默认对非检查型异常进行事务回滚,而对检查型异常则不进行回滚操作。所以必须捕获检查型异常,捕获后再抛出非检查型异常,这样事务方才起效。

https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html
//TODO

Java线程池

从JDK1.5开始,Java API提供了Executor框架,创建不同的线程池,有效地进行线程控制。其中包括每次处理一个任务的单线程池、数目固定的线程池、缓存线程池。
Executors线程池工厂提供了一些创建不同特性线程池ThreadPoolExecutor的方法,它们都返回ExecutorService对象:

  • Executors.newSingleThreadPool()
  • Executors.newFixedThreadPool(int size):定长线程池,可控制线程最大并发数,超出的线程在队列中等待。
  • Executors.newCachedTheadPool():适合很多生存期短的任务,空闲线程会在指定时间内被回收。
  • Executors.newScheduledThreadPool():定长线程池,支持定时及周期性任务执行

它们内部都是封装了ThreadPoolExecutor:

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 15, TimeUnit.SECONDS,new ArrayBlockingQueue(5), new ThreadPoolExecutor.CallerRunsPolicy());

线程池超载后的拒绝策略

池中的线程用光,等待队列也已排满。

扩展线程池

ThreadPoolExecutor提供了用于扩展的接口,比如要监控每个任务执行的开始和结束时间。

打印异常堆栈是排查问题的指南针,用线程池的execte()方法替换submit()方法,可以进而继承ThreadPoolExecutor来扩展出自己的TreaceThreadPoolExecutor。

Guava中扩展的线程池:

  • MoreExecutors.directExecutor() :将任务在调用线程中执行。
  • 将普通线程池转为Daemon线程池。
  • 对Future模式扩展。

估算线程池大小

threadsSize=cpu数量*cpu使用率*(1+等待时间/计算时间)

Fork/Join线程池

分而治之的思想,例如MapReduce,将大任务划分为小任务合成为最终结果。

ForkJoinPool线程池接受提交ForkJoinTask,ForkJoinTask对象可以调用fork()来分叉出小任务。

 

 

Java工具

jps:显示所有java进程

jstack:打印给定java进程的内部线程及堆栈

JMH:Java Microbenchmark Harness,Java微基准测试框架,是在OpenJDK中发布的用于性能测试框架。可以对方法的性能定量分析。

并发

基础概念

并发与并行:并发是串行交替执行,并行就是并行执行。并行只能出现在多核CPU情况下了。但它们的执行结果可能是一致的。并发概念独立于程序语言,多数语言都会涉及到并发问题。 临界区:多线程争用的共享数据。 死锁、饥饿、活锁:死锁是两方都争用;饥饿是一方争用而另一方谦让;活锁是两方都谦让。

并发级别

阻塞无饥饿:公平锁,不论线程优先级别,线程按照顺序排队。 无障碍:非阻塞调度是一种乐观的策略。都无障碍的执行,一旦检测到冲突,立即回滚。 无锁:保证必然有一个线程在有限步内完成。 无等待:要求所有线程都必须在有限步内完成。

锁机制

锁机制有两个层面:一种是代码层次上的,另外一种是数据库层次上的。锁同步更多指的是应用程序的层面,多个线程进来,只能一个一个的访问同一个数据。

数据库锁机制

  • 悲观锁(Pessimistic Locking):独占锁,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制,也只有数据库层提供的锁机制才能真正保证数据访问的排他性。select * from account where name=”Erica” for update 这条 sql 语句锁定了 account 表中所有符合检索条件( name=”Erica” )的记录。

锁优化

  • 减小持有时间
  • 减小粒度:分割数据结构,适用于局部信息调用频繁,全局信息调用不频繁。
  • 锁粗化:把一组连续请求释放锁整合。
  • 读写分离锁:分割功能点,也是减小粒度的一种情况。
  • 锁分离:操作于结构上的位置不同,如take和put操作于LinkedBlockingQueue中链表的首尾。

JVM锁优化

  • 锁偏向:同一线程连续再次申请锁时,无须同步操作。适用于锁竞争少的场景。可以使用虚拟机参数开启锁偏向机制。
  • 轻量级锁:
  • 重量级锁:
  • 自旋锁:
  • 锁消除:通过逃逸分析,去除在没有竞争时使用的锁。可以使用虚拟机参数开启锁消除。

 

数据库基础

不同的数据库中字符串连接符不同:
1、oracle数据库中的连接符为||,例如连接字符串AB、CD可以使用“AB”||“CD”;
2、SQLSERVER 数据库连接符为+,例如连接字符串AB、CD可以使用“AB”+“CD”;
3、MYSQL数据库中连接符为+,例如连接字符串AB、CD可以使用“AB”+“CD”;

SQL聚集函数是以一个集合(集或者多重集)为输入,返回单个值的函数。SQL提供了五个聚集函数:

  • 平均值:avg
  • 最小值:min
  • 最大值:max
  • 总和:sum
  • 计数:count

而Having是一个过滤声明,是在查询返回结果集以后对查询结果进行的过滤操作,在Having中可以使用聚合函数。

范式

  • 第一范式(1NF)
    指数据库表的每一列数据项都不可分割,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。简而言之,第一范式就是无重复的列。
  • 第二范式(2NF)
    要求数据库表中的每个实例或行必须可以被唯一地区分。要求实体的属性完全依赖于主关键字
    所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。简而言之,第二范式就是非主属性依赖于主关键字。
  • 第三范式(3NF)
    在满足第二范式的基础上,不存在传递函数依赖,那么就是第三范式。简而言之,第三范式就是属性不依赖于其它非主属性。

常用的关系数据库

  • Oracle
  • MySql
  • MariaDB
  • PostgreSQL

NoSQL

  • redis
  • mongodb
  • hbase
  • Cassandrs
  • pika

NewSQL

  • TiDB
  • OceanBase

数据库事务特性

  • A 原子性
  • C 一致性
  • I 隔离性
  • D 持久性

本地事务数据库断电的这种情况,它是怎么保证数据一致性的呢?使用SQL Server来举例,在执行事务的时候数据库首先会记录下这个事务的redo操作日志,然后才开始真正操作数据库,在操作之前首先会把日志文件写入磁盘,那么当突然断电的时候,即使操作没有完成,在重新启动数据库时候,数据库会根据当前数据的情况进行undo回滚或者是redo前滚,这样就保证了数据的强一致性。

Java枚举

在多数其它语言中,枚举只是命令整数常量的列表。在Java中,枚举实际上是类,枚举可以拥有成员变量、成员方法、构造方法。

enum Type{
    A,B,C,D;
}

枚举在编译后,会自动生成一个继承自java.lang.Enum的类。上面的枚举在编译后生成下面的代码:

class Type extends Enum{
    public static final Type A;
    public static final Type B;
    ...
}

更多参考 https://www.cnblogs.com/ldq2016/p/6627542.html

ordinal和value

ordinal()返回的值是索引,value()返回的是给枚举变量赋予的值。

public enum EnumDeviceType {
    Unknow(0),
    MasterSocket(1),
    SlaveSocket(2),
    DoorbellController(42),
    PowerControl(43);

    private int value = 0;

    private EnumDeviceType(int value) {
        this.value = value;
    }

    public int value() {
        return value;
    }
}

单例模式

单例模式有两种:“饿汉模式”和“懒汉模式”。直接使用饿汉模式,懒汉模式在任何情况下都不需要使用,因为饿汉模式先天就没有线程安全问题。

懒汉单例模式终极版

用volatile和synchronized配合双重检查锁(DCL:double check lock)机制,其中volatile用来保证多线程下的可见性:

  • 读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的跟主线程的一致。
  • 写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。

或者认为用volatile修饰singleton并不是用了volatile的可见性,而是用了java内存模型的“先行发生”(happens-before)原则的其中一条Volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”指时间上的先后顺序。这样禁止指令重排序,确保singleton对象是在初始化完成后才能被读到。

public class Singleton {
private static volatile Singleton uniqueInstance;

private Singleton(){
}

public static Singleton getInstance(){
if(uniqueInstance == null){ //#1
synchronized(Singleton.class){ //#2
if(uniqueInstance == null){ //#3
uniqueInstance = new Singleton(); //#4
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
} else {
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
}
}
}
return uniqueInstance;
}
}

饿汉单例模式

以下两种写法在编译后等效

public class Singleton {
    private static final Singleton singleton=new Singleton();
    
    public static Singleton getInstance(){
        return singleton;
    }
}
public class Singleton {
    private static Singleton singleton;
    
    static{
        singleton=new Singleton();
    }
    
    public static Singleton getInstance(){
        return singleton;
    }
}

类的加载分为5个步骤:加载、验证、准备、解析、初始化,“饿汉模式”的创建对象是在类加载的初始化阶段进行的,jvm规范规定有且只有以下7种情况下会进行类加载的初始化阶段:

  • 使用new关键字实例化对象的时候
  • 设置或读取一个类的静态字段(被final修饰,已在编译器把结果放入常量池的静态字段除外)的时候
  • 调用一个类的静态方法的时候
  • 使用java.lang.reflect包的方法对类进行反射调用的时候
  • 初始化一个类的子类(会首先初始化父类)
  • 当虚拟机启动的时候,初始化包含main方法的主类
  • 当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

基本来说就是只有当以某种方式调用了这个类的时候,它才会进行初始化,而不是说jvm启动的时候就初始化。所以说假如单例类里只有一个getInstance()方法,那相当于当从其他类调用getInstance()方法的时候才会进行初始化,这和“懒汉模式”一样的效果。而jvm本身会确保类的初始化只执行一次。

当单例类里除了getInstance()方法还有一些其他静态方法,这样当调用其他静态方法的时候,也会提前初始化实例了。解决这个问题只要加个内部类就行了,这种模式叫holder pattern,如下,只有当调用getInstance()方法的时候,才会初始化内部类SingletonHolder:

public class Singleton {
    private static class SingletonHolder{
        private static final Singleton instance=new Singleton();
    }
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

单元素enum来实现单例

public enum Singleton
{
    INSTANCE;

    public void dosomething()
    {
        System.out.println(this + " is speaking!");
    }
}

Java集合

并发集合

Java5介绍了并发集合,大部分位于java.util.concurrent并发包中。

  • ConcurrentHashMap
  • CopyOnWriteArrayList:目的是高效并发读取。很多应用场景中,要求读操作尽可能快,写操作可以相对慢。CopyOnWriteArrayList中利用CopyOnWrite机制优化到写入不会阻塞读取
  • ConcurrentLinkedQueue:通过CAS操作和锁分离,是高并发环境中性能最好的队列。
  • BlockingQueue:线程间数据共享阻塞队列接口,它有不同的实现。BlockingQueue让从队列中获取消息的服务线程在队列空时等待,当有消息后再将线程唤醒。主要操作它的put和take方法。
  • ConcurrentSkipListMap:随机数据结构,跳表。跳表的内部维护了分层的多个链表,使用锁分离机制,查询时间复杂度是O(lgn)。

ConcurrentHashMap

ConcurrentHashMap,不仅提供线程安全,还用锁分离和内部分区等现代技术提高了可扩展性。把实际map分割(segmentation)成若干部分,默认值为16,使的多线程减小争用。但其size()等方法可能需要全局锁而取得所有段的锁,性能反而比HashMap低。

(低并发)线程安全容器:

  • Collections.synchronizedMap()
  • Collections.synchronizedList()
  • Vector
  • Hashtable:大小增加到一定的时候,性能会急剧下降,因为迭代时整个map需要被锁定很长的时间。

BlockingQueue

ArrayBlockingQueue

LinkedBlockingQueue:通过takeLock、putLock两把锁实现了读数据和写数据的分离,实现了对独占锁分离。

 

PriorityQueue 保证最高或者最低优先级的的元素总是在队列头部,当遍历一个 PriorityQueue 时,没有任何顺序保证。
LinkedHashMap 维持的顺序是元素插入的顺序。LinkedHashMap 课保证遍历顺序是元素插入的顺序。
集合的排序:可以使用有序集合,如 TreeSet 或 TreeMap,或者使用有顺序的的集合,如List,然后通过 Collections.sort() 来排序。
打印数组:数组没有实现 toString() 方法,可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来转为字符串再打印数组。
Java 中的 LinkedList 是单向链表还是双向链表。在 Eclipse,可以使用快捷键 Ctrl + T,直接在编辑器中打开该类检查源码。
Java 中的 TreeMap 是使用红黑树实现的。
在 Java 7 中,ArrayList 的默认大小是 10 个元素。Java 7 中 ArrayList 和 HashMap 类的代码片段:

// from ArrayList.java JDK 1.7
private static final int DEFAULT_CAPACITY = 10;

//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

遍历 ArrayList 时移除一个元素,不是使用ArrayList 的 remove() ,而是使用Iterator 的 remove()方法,则不会出现 ConcurrentModificationException 异常。
想使用 Java 中增强的for-each 循环来遍历,只需要实现 Iterable 接口。或者实现 Collection 接口,默认就具有该属性。
Vector、Hashtable等是同步集合类,用同步方法来实现了线程安全, 而ArrayList、HashMap等不是线程安全的。

Java集合继承结构
集合继承结构

HashMap

由数组加链表实现

默认大小是16个元素(必须是2的幂),因为可以通过按位与计算余数,比求模更快。

Hashtable 与 HashMap

a) Hashtable 是 JDK 1 遗留下来的类,而 HashMap 是后来增加的。
b)Hashtable 是同步的,比较慢,但 HashMap 没有同步策略,所以会更快。
c)Hashtable 不允许有个空的 key,但是 HashMap 允许出现一个 null key。
大多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap。HashMap的是无序的,允许将null用作键和值,非线程安全。HashMap不能保证元素的顺序,HashMap能够将键设为null,也可以将值设为null。
Hashtable,(注意大小写:不是HashTable),Hashtable不能将键和值设为null,否则运行时会报空指针异常错误,Hashtable是线程安全的。
HashSet 的内部采用 HashMap来实现。HashSet 允许有一个null key。

LinkedHashMap

LinkedHashMap增加了时间和空间上的开销,通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序。该迭代顺序可以是插入顺序或者是访问顺序。LinkedHashMap是有序、Key和Value都允许空、非线程安全的。LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。LinkedList每次访问一个元素(get或put),被访问的元素都会被提到最后面。
LinkedHashMap可以应用来实现LRUCache,LRU即Least Recently Used 最近最少使用。当缓存满了,优先淘汰最不常访问的数据。

LinkedHashMap相关问题:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx。
rpc远程调用在底层还是使用的HTTPClient,所以在传递参数的时候,必定要有个顺序,不然服务层在接的时候就出问题了,所以它才会转为LinkedHashMap。spring 有一个类叫ModelMap,继承了linkedhashMap,即 public class ModelMap extends LinkedHashMap,所以一个接口返回的结果就可以直接用ModelMap来接。注意ModelMap是没有泛型的,不管返回的结果是什么类型的map,泛型是多复杂的map,都可以直接new一个Modelmap,用它来接返回的结果。

Java 中使用 Collections 的最佳实践

a)使用正确的集合类。如果不需要同步列表,使用 ArrayList 而不是 Vector。
b)优先使用并发集合。不是对集合进行同步并,发集合提供更好的可扩展性。
c)使用接口表示和访问集合。如使用List存储 ArrayList,使用 Map 存储 HashMap 。
d)使用集合的时候使用泛型。
e)使用迭代器来循环集合。

线程安全的集合

喂,SHE
喂(V,Vector)
S(Stack)
H(Hashtable)
E(Enumeration)

生产者消费者模型

在现实中许多线程问题都属于生产者消费者模型。较低级的解决方式是用wait和notify,更好的办法是用Semaphore 或者 BlockingQueue。

生产者消费者模型,准确地说应该是“生产者-消费者-仓储”模型,有以下几点需要明确:
1、生产者仅仅在仓储未满时候生产,仓满则停止生产。
2、消费者仅仅在仓储有产品时候才能消费,仓空则等待。
3、当消费者发现仓储没产品可消费时候会通知生产者生产。
4、生产者在生产出可消费产品时候,应该通知等待的消费者去消费。

生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。解决生产者/消费者问题的常用方法是采用某种机制保护生产者和消费者之间的同步,常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。

如下代码示例,使用了BlockingQueue阻塞队列来线程间共享数据:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * java多线程模拟生产者消费者问题
 * 
 * ProducerConsumerApplication是主类,Producer生产者,Consumer消费者,Product产品,Storage仓库
 */
public class ProducerConsumerApplication {
    public static void main(String[] args) {
        ProducerConsumerApplication pc = new ProducerConsumerApplication();
        Storage s = pc.new Storage();
        Producer p1 = pc.new Producer("生产者1", s);
        Producer p2 = pc.new Producer("生产者2", s);
        Consumer c1 = pc.new Consumer("消费者1", s);
        Consumer c2 = pc.new Consumer("消费者2", s);
        Consumer c3 = pc.new Consumer("消费者3", s);

        ExecutorService service = Executors.newCachedThreadPool();
        service.submit(p1);
        service.submit(p2);
        service.submit(c1);
        service.submit(c2);
        service.submit(c3);
    }

    /**
     * 消费者
     */
    class Consumer implements Runnable {
        private String name;
        private Storage s = null;

        public Consumer(String name, Storage s) {
            this.name = name;
            this.s = s;
        }

        public void run() {
            try {
                while (true) {
                    System.out.println(name + "准备消费产品.");
                    Product product = s.pop();
                    System.out.println(name + "已消费(" + product.toString() + ").");
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 生产者
     */
    class Producer implements Runnable {
        private String name;
        private Storage s = null;

        public Producer(String name, Storage s) {
            this.name = name;
            this.s = s;
        }

        public void run() {
            try {
                while (true) {
                    Product product = new Product((int) (Math.random() * 10000)); // 产生0~9999随机整数
                    System.out.println(name + "准备生产(" + product.toString() + ").");
                    s.push(product);
                    System.out.println(name + "已生产(" + product.toString() + ").");
                    Thread.sleep(500);
                }
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 仓库,用来存放产品
     */
    public class Storage {
        BlockingQueue queues = new LinkedBlockingQueue(10);

        /**
         * 生产
         */
        public void push(Product p) throws InterruptedException {
            queues.put(p);
        }

        /**
         * 消费
         */
        public Product pop() throws InterruptedException {
            return queues.take();
        }
    }

    /**
     * 产品
     */
    public class Product {
        private int id;

        public Product(int id) {
            this.id = id;
        }

        public String toString() {
            // 重写toString方法
            return "产品:" + this.id;
        }
    }
}