青花小记

这个人很懒,只想写写代码做做饭

0%


Java线程池

本文参考自 Java 四种线程池的用法分析

new Thread的弊端

执行一个异步任务你还只是如下new Thread吗?

1
2
3
4
5
6
new Thread(new Runnable() {
@Override
public void run() {
//TODO
}
).start();

那你就out太多了,new Thread的弊端如下:

  • 每次new Thread新建对象性能差
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom
  • 缺乏更多功能,如定时执行、定期执行、线程中断

相比new Thread,Java提供的四种线程池的好处在于:

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
  • 提供定时执行、定期执行、单线程、并发数控制等功能

线程池的作用

线程池作用就是限制系统中执行线程的数量。

根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排 队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池 中有等待的工作线程,就可以开始运行了;否则进入等待队列。

优点

  1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

相关的Java类

  • ExecutorService  真正的线程池接口
  • ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
  • ThreadPoolExecutor  ExecutorService的默认实现。
  • ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

Java中四种线程池

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。 分别为:

  • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CacheThreadPoolTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
}
}

结果如下所示:

当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。

此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FixedThreadPoolTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
final int index = i;

fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("当前线程" + Thread.currentThread().getName() + ", index值为:" + index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}

输出结果:

输入图片说明

可以看出,每次最多同时运行2个线程,超出的线程等待其他线程运行完毕后再运行。其次,加入线程池的线程属于托管状态,线程的运行不受加入顺序的影响。

newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

延迟执行

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println(DateTime.now().toString("yyyy-MM-dd HH:mm:ss"));
}
}, 3, TimeUnit.SECONDS);
}
}

延迟3秒执行run方法中的内容

定期执行
1
2
3
4
5
6
7
8
9
10
11
12
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(DateTime.now().toString("yyyy-MM-dd HH:mm:ss"));
}
}, 1, 3, TimeUnit.SECONDS);
}
}

表示延迟1秒后每3秒执行一次。

结果如下:

newSingleThreadExecutor

创建一个单线程化的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SingleThreadPool {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {

@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}

结果如下:

可以看到,结果依次输出,相当于顺序执行各个任务。

对于 InnoDB 这种聚集主键类型的引擎来说,数据会按照主键进行物理排序,这对 auto_increment int 是个好消息,因为后一次插入的主键位置总是在最后。但是对 uuid 来说,这却是个坏消息,因为 uuid 是杂乱无章的,每次插入的主键位置是不确定的,可能在开头,也可能在中间,在进行主键物理排序的时候,势必会造成大量的 IO操作影响效率,因此不适合使用 UUID 做物理主键。比较适合的做法是把uuid作为逻辑主键,物理主键依然使用自增ID。

GUID

全局唯一标识符,简称GUID(Globally Unique Identifier),是一种由算法生成的唯一标识,通常表示成32个十六进制数字组成的字符串,实质上是一个128位长的二进制整数。

GUID的主要目的是产生完全唯一的数字,在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID,GUID的总量也足够大,达到了2^128个,所以随机生成两个相同的GUID的可能性是非常小的,但是并不为0;所以,用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证重复的情况不会发生。

GUID已经广泛使用于数据库表格的主键。由于主键需要用作索引,于是就产生了一个性能问题:当主键足够随机时,新的记录就必须插入到原有的索引中间,而不能仅仅排在最后

为缓解这个问题并仍然提供足够的随机程度以避免GUID的重复,人们就创造了一些新的算法来生成序列化的GUID。

2002年8月,吉米尼尔森(Jimmy Nilsson)给出了第一种方法,并称之为“COMB”(combined guid/timestamp,意思是:组合GUID/时间戳)。他将GUID中数据4的最后6字节用系统时间的最低位替换。经测试,这对随机性的影响很小,但是有一个副作用即是其创建的时间可以从GUID中轻松还原。

自从Microsoft SQL Server 2005版开始,微软在Transact-SQL中加入了一个新函数,叫做NEWSEQUENTIALID(),用来生成主键增大的GUID,但一旦服务器重新启动,其再次生成的GUID可能反而变小(但仍然保持唯一)。这在很大程度上提高了索引的性能,但并不能保证所生成的GUID已知增大。这个函数产生的GUID很简单就可以预测,因此不适合用于安全目的。

2006年,一些程序员发现,在一些平台上的Oracle软件中,SYS_GUID函数能返回序列化的GUID。但这个实际上是一个BUG导致的。

全局唯一性ID问题

https://tech.meituan.com/MT_Leaf.html

文中的思路可以借鉴,目前先只使用snowflake来处理

https://www.cnblogs.com/relucent/p/4955340.html

Queue

接口,继承自Collection,Java集合框架中的队列实现。

除了基本的Collection操作外,队列还提供其他的插入、提取和检查操作。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(null 或 false,具体取决于操作)。插入操作的后一种形式是用于专门为有容量限制的 Queue 实现设计的;在大多数实现中,插入操作不会失败。

操作/结果 抛出异常 返回特殊值
插入 add(e) offer(e)
移除 remove() poll()
检查 element() peek()

队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。不过优先级队列和 LIFO 队列(或堆栈)例外,前者根据提供的比较器或元素的自然顺序对元素进行排序,后者按 LIFO(后进先出)的方式对元素进行排序。无论使用哪种排序方式,队列的头 都是调用 remove() 或 poll() 所移除的元素。在 FIFO 队列中,所有的新元素都插入队列的末尾。其他种类的队列可能使用不同的元素放置规则。每个 Queue 实现必须指定其顺序属性。

如果可能,offer 方法可插入一个元素,否则返回 false。这与 Collection.add 方法不同,该方法只能通过抛出未经检查的异常使添加元素失败。offer 方法设计用于正常的失败情况,而不是出现异常的情况,例如在容量固定(有界)的队列中。

remove() 和 poll() 方法可移除和返回队列的头。到底从队列中移除哪个元素是队列排序策略的功能,而该策略在各种实现中是不同的。remove() 和 poll() 方法仅在队列为空时其行为有所不同:remove() 方法抛出一个异常,而 poll() 方法则返回 null。

element() 和 peek() 返回但不移除队列的头。

Queue 接口并未定义并发编程中常见的阻塞队列的方法。BlockingQueue 接口定义了那些等待元素出现或等待队列中有可用空间的方法,这些方法扩展了此接口。

Queue 实现通常不允许插入 null 元素,尽管某些实现(如 LinkedList)并不禁止插入 null。即使在允许 null 的实现中,也不应该将 null 插入到 Queue 中,因为 null 也用作 poll 方法的一个特殊返回值,表明队列不包含元素。

Queue 实现通常未定义 equals 和 hashCode 方法的基于元素的版本,而是从 Object 类继承了基于身份的版本,因为对于具有相同元素但有不同排序属性的队列而言,基于元素的相等性不总是很好定义。

方法

boolean add(E e)

将指定的元素插入此队列,如果队列没满,且可以立即插入,则返回 true。其他异常情况如下:

  • IllegalStateException - 如果由于容量的限制此时不能添加该元素
  • ClassCastException - 如果指定元素的类不允许将其添加到此队列
  • NullPointerException - 如果指定元素为 null 并且此队列不允许 null 元素
  • IllegalArgumentException - 如果此元素的某些属性不允许将其添加到此队列

boolean offer(E e)

将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时,此方法通常要优于 add(E),后者可能无法插入元素,而只是抛出一个异常

如果该元素已添加到此队列,则返回 true;否则返回 false

  • ClassCastException - 如果指定元素的类不允许将其添加到此队列
  • NullPointerException - 如果指定元素为 null 并且此队列不允许 null 元素
  • IllegalArgumentException - 如果此元素的某些属性不允许将其添加到此队列

E remove()

获取并移除此队列的头。此方法与 poll 唯一的不同在于:此队列为空时将抛出一个异常。

  • NoSuchElementException - 如果此队列为空

E poll()

获取并移除此队列的头,如果此队列为空,则返回 null。

E element()

获取但是不移除此队列的头。此方法与 peek 唯一的不同在于:此队列为空时将抛出一个异常。

  • NoSuchElementException - 如果此队列为空

E peek()

获取但不移除此队列的头;如果此队列为空,则返回 null。

AbstractQueue

抽象队列类,实现了Queue接口,继承了AbstractCollection。

此类提供某些Queue操作的骨干实现。此类中的实现适用于基本实现不允许包含null元素时。add、remove 和 element 方法分别基于 offer、poll 和 peek 方法,但是它们通过抛出异常而不是返回 false 或 null 来指示失败。

扩展此类的 Queue 实现至少必须定义一个不允许插入 null 元素的 Queue.offer(E) 方法,该方法以及 Queue.peek()、Queue.poll()、Collection.size() 和 Collection.iterator() 都支持 Iterator.remove() 方法。通常还要重写其他方法。如果无法满足这些要求,那么可以转而考虑为 AbstractCollection 创建子类。

方法

boolean add(E e)

1
2
3
4
5
6
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}

基于offer实现,将指定的元素插入到此队列中(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用空间,则抛出 IllegalStateException。

E remove()

1
2
3
4
5
6
7
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}

基于poll实现,获取并移除此队列的头。此方法与 poll 唯一的不同在于:此队列为空时将抛出一个异常NoSuchElementException。

E element()

1
2
3
4
5
6
7
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}

基于peek实现,获取但不移除此队列的头。此方法与 peek 唯一的不同在于:此队列为空时将抛出一个异常NoSuchElementException。

void clear()

1
2
3
4
public void clear() {
while (poll() != null)
;
}

移除此队列中的所有元素。此调用返回后,队列将为空。

此实现重复调用 poll,直到它返回 null 为止。

boolean addAll(Collection<? extends E> c)

1
2
3
4
5
6
7
8
9
10
11
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}

调用add方法将指定 collection 中的所有元素都添加到此队列中。如果试图将某一队列 addAll 到该队列本身中,则会导致 IllegalArgumentException。此外,如果正在进行此操作时修改指定的 collection,则此操作的行为是不确定的。

此实现在指定的 collection 上进行迭代,并依次将迭代器返回的每一个元素添加到此队列中。在试图添加某一元素(尤其是 null 元素)时如果遇到了运行时异常,则可能导致在抛出相关异常时只成功地添加了某些元素。

  • ClassCastException - 如果指定 collection 元素的类不允许将该元素添加到此队列中
  • NullPointerException - 如果指定 collection 包含一个 null 元素并且此队列不允许 null 元素,或者指定 collection 为 null
  • IllegalArgumentException - 如果指定 collection 元素的某些属性不允许将该元素添加到此队列中,或者指定 collection 是此队列
  • IllegalStateException - 如果此时由于插入限制无法添加所有元素

BlockingQueue

接口,继承自Queue,支持两个附加操作的Queue,这两个操作是:

  • 获取元素时等待队列变为非空
  • 存储元素时等待空间变得可用

BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操作),第三种是在操作成功前,无限期地阻塞当前线程,第四种是在给定的最大时间限制内阻塞,超时失败。下表中总结了这些方法:

操作/结果 抛出异常 返回特殊值 阻塞等待 超时等待
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll() take() poll(time, unit)
检索 element() peek() 不可用 不可用

BlockingQueue 不接受 null 元素,试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。

BlockingQueue可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。

BlockingQueue实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。

BlockingQueue实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。

BlockingQueue 实质上不 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。

以下是基于典型的生产者-使用者场景的一个用例。注意,BlockingQueue 可以安全地与多个生产者和多个使用者一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Producer implements Runnable {
private final BlockingQueue queue;
Producer(BlockingQueue q) { queue = q; }
public void run() {
try {
while(true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Object produce() { ... }
}

class Consumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) { queue = q; }
public void run() {
try {
while(true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Object x) { ... }
}

class Setup {
void main() {
BlockingQueue q = new SomeQueueImplementation();
Producer p = new Producer(q);
Consumer c1 = new Consumer(q);
Consumer c2 = new Consumer(q);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}

内存一致性效果:当存在其他并发 collection 时,将对象放入 BlockingQueue 之前的线程中的操作 happen-before 随后通过另一线程从 BlockingQueue 中访问或移除该元素的操作。

方法

boolean add(E e)

将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException当使用有容量限制的队列时,通常首选 offer

boolean offer(E e)

将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false。当使用有容量限制的队列时,此方法通常要优于 add(E),后者可能无法插入元素,而只是抛出一个异常。

void put(E e) throws InterruptedException

将指定元素插入此队列中,将等待可用的空间(如果有必要)。

  • InterruptedException - 如果在等待时被中断
  • ClassCastException - 如果指定元素的类不允许将其添加到此队列
  • NullPointerException - 如果指定元素为 null
  • IllegalArgumentException - 如果指定元素的某些属性不允许将其添加到此队列

boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException

将指定元素插入此队列中,在到达指定的等待时间前等待可用的空间(如果有必要)。

如果成功,则返回 true;如果在空间可用前超过了指定的等待时间,则返回 false。

  • InterruptedException - 如果在等待时被中断
  • ClassCastException - 如果指定元素的类不允许将其添加到此队列
  • NullPointerException - 如果指定元素为 null
  • IllegalArgumentException - 如果指定元素的某些属性不允许将其添加到此队列

E take() throws InterruptedException

获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。

E poll(long timeout, TimeUnit unit) throws InterruptedException;

获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。

int remainingCapacity();

返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的附加元素数量,即剩余容量;如果没有内部限制,则返回 Integer.MAX_VALUE。

注意,不能 总是通过检查 remainingCapacity 来判断尝试插入元素是否成功,因为可能出现这样的情况:其他线程即将插入或移除一个元素。

boolean remove(Object o)

从此队列中移除指定元素的单个实例(如果存在)。更确切地讲,如果此队列包含一个或多个满足 o.equals(e) 的元素 e,则移除该元素。如果此队列包含指定元素(或者此队列由于调用而发生更改),则返回 true。

  • ClassCastException - 如果指定元素的类与此队列不兼容(可选)
  • NullPointerException - 如果指定元素为 null(可选)

boolean contains(Object o)

如果此队列包含指定元素,则返回 true。更确切地讲,当且仅当此队列至少包含一个满足 o.equals(e) 的元素 e时,返回 true。

int drainTo(Collection<? super E> c)

移除此队列中所有可用的元素,并将它们添加到给定 Collection 中。此操作可能比反复轮询此队列更有效。在试图向 Collection c 中添加元素没有成功时,可能导致在抛出相关异常时,元素会同时在两个 Collection 中出现,或者在其中一个 Collection 中出现,也可能在两个 Collection 中都不出现。如果试图将一个队列放入自身队列中,则会导致 IllegalArgumentException 异常。此外,如果正在进行此操作时修改指定的 Collection,则此操作行为是不确定的。

  • UnsupportedOperationException - 如果指定 Collection 不支持添加元素
  • ClassCastException - 如果此队列元素的类不允许将其添加到指定 Collection
  • NullPointerException - 如果指定 Collection 为 null
  • IllegalArgumentException - 如果指定 Collection 是此队列,或者此队列元素的某些属性不允许将其添加到指定 Collection

int drainTo(Collection<? super E> c, int maxElements);

  • maxElements - 传输元素的最大数量

最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 Collection 中。在试图向 Collection c 中添加元素没有成功时,可能导致在抛出相关异常时,元素会同时在两个 Collection 中出现,或者在其中一个 Collection 中出现,也可能在两个 Collection 中都不出现。如果试图将一个队列放入自身队列中,则会导致 IllegalArgumentException 异常。此外,如果正在进行此操作时修改指定的 Collection,则此操作行为是不确定的。

ArrayBlockingQueue

一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。

这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞

此类支持对等待的生产者线程和消费者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。

该队列不允许插入null元素

源码分析可以参考**http://www.cnblogs.com/leesf456/p/5533770.html**

PriorityQueue

基于优先级堆(二叉堆)的无界优先级队列,优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的Comparator进行排序,具体取决于所使用的构造方法。

优先级队列不允许使用null元素,依靠自然顺序的优先级队列还不允许插入不可比较的对象(避免可能导致的ClassCastException)。

此队列的头* 是按指定排序方式确定的*最小 元素**。如果多个元素都是最小值,则头是其中一个元素——选择方法是任意的。队列获取操作 pollremovepeek 和 element 访问处于队列头的元素。

优先级队列是无界的,但是有一个内部容量,控制着用于存储队列元素的数组大小。它通常至少等于队列的大小。随着不断向优先级队列添加元素,其容量会自动增加。无需指定容量增加策略的细节。

此类及其迭代器实现了 Collection 和 Iterator 接口的所有可选 方法。方法 iterator() 中提供的迭代器 保证以任何特定的顺序遍历优先级队列中的元素。如果需要按顺序遍历,请考虑用 Arrays.sort(pq.toArray())

注意,此实现不是同步的。如果多个线程中的任意线程修改了队列,则这些线程不应同时访问 PriorityQueue 实例。相反,请使用线程安全的 PriorityBlockingQueue 类。

PriorityBlockingQueue

无界阻塞队列,线程安全

ArrayList Java 中的动态数组实现,默认容量为10,最大容量为Integer.MAX_VALUE

基于数组实现,所以拥有数组的特点:数据连续,随机访问速度快(实现了RandomAccess标记接口,表明了随机读取的能力);同样的,因为Java中的数组是不可变的,所以每次的增删数组都涉及到了数组的扩容和拷贝,速度上会较慢(ArrayList如果不考虑扩容的情况,效率和LinkedList不会有太大差别);

扩容

在添加元素时,考虑到容量的因素,需要按情况进行扩容,默认的扩容方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public boolean add(E e) {
ensureCapacityInternal(size + 1); //扩容判断 size+1即为minCapacity,可满足add操作的最小容量
elementData[size++] = e;//在末尾追加元素
return true;
}

/**
* 可分配的最大数组长度
* 一些虚拟机会在数组中保留头文字
* 去分配一个更大长度的数组可能会导致内存溢出的错误:请求的数组大小超过虚拟机的限制
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) { //如果列表为空(没有元素)
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//比较设定的最小容量和默认容量,取其中大最大值为最小容量;即minCapacity不满10,按10处理
}
ensureExplicitCapacity(minCapacity);//扩容判断
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;//修改次数加1,这个字段继承自AbstractList,用于在add set等操作时进行ConcurrentModificationException异常判断

if (minCapacity - elementData.length > 0)//判断最小容量和当前列表的大小,注意是elementData.length而不是size
grow(minCapacity);//当前容量不足,扩容
}

private void grow(int minCapacity) {
int oldCapacity = elementData.length;//原有的容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//新容量 原有的容量的1.5倍左右 >> 移位操作 即 1111右移1位变为0111 15->7
// int newCapacity = (oldCapacity * 3)/2 + 1; //JDK1.6的源码
if (newCapacity - minCapacity < 0) //如果扩容后的容量比所需的最小容量还小 溢出??
newCapacity = minCapacity; //设置最小容量为新的容量
if (newCapacity - MAX_ARRAY_SIZE > 0) //再判断新容量是否超出所允许的最大长度 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 为什么是这个数??? 没搞清楚
newCapacity = hugeCapacity(minCapacity); //如果新容量超出最大长度,重新扩容
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//数组拷贝,新数组长度为新容量的大小,扩容完毕
}

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();//溢出 抛异常 感觉不会出现这种情况
return (minCapacity > MAX_ARRAY_SIZE) ? //如果最小容量超出所允许的最大长度,新容量就用Integer.MAX_VALUE,否则的话就用MAX_ARRAY_SIZE
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

在扩容的过程中涉及到了modCount字段,ArrayList的修改次数,Java中的fail-fast机制,每次去检查修改次数,如果和期望的修改次数不一致,会出现ConcurrentModificationException异常

fail-fast,快速失败,Java集合中的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModificationException异常,从而产生fail-fast机制

除了在添加元素时,自动的扩容之外,ArrayList也可以显式的进行扩容,直接指定ArrayList的最小容量

1
2
3
4
5
6
7
8
9
10
11
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != EMPTY_ELEMENTDATA)
// 如果elementData不是空数组,则证明数组已有数据,minExpand为0
? 0
// 等于空数组的话,则默认大小为DEFAULT_CAPACITY 10
: DEFAULT_CAPACITY;

if (minCapacity > minExpand) {//如果需要的最小容量大于minExpand
ensureExplicitCapacity(minCapacity);//对数组进行扩容
}
}

构造器

ArrayList三个构造器,其中两个是根据Collection实现类的标准定义的,另一个public ArrayList(int initialCapacity)用来指定初始化的容量,这个方法是为了在可以预估大概数据量的情况下,指定ArrayList的默认长度,避免扩容和数组拷贝,可以提高插入效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  /**
* 构造器
* @param 数组的大小
* 如果传入的参数小于0的话,抛出IllegalArgumentException异常
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity]; //初始化一个Object数组
}

/**
* 无参构造器,初始化一个空数组
*/
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}

/**
*
* 构造器
* @param c 集合类,该集合中的元素必须是ArrayList的泛型的本类或子类
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray(); //转换为Object数组
size = elementData.length; //设置size
// c.toArray可能不返回Object数组 详见http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652
if (elementData.getClass() != Object[].class)
//如果elementData中的元素不是Object类型的,则将其复制到Object数组中,并重新赋值给elementData
elementData = Arrays.copyOf(elementData, size, Object[].class);
}

ArrayList是Collection的实现类,所有通用的Collection实现类(通常通过它的一个子接口间接实现Collection,如List、Queue和Set)应该提供两个“标准”的构造方法:
1.无参构造方法,用于创建空的collection
2.带有Collection类型单参数的构造方法,用于创建一个具有与其参数相同元素的新collection,如:public ArrayList(Collection<? extends Ec)
这两个标准不强制必须遵守,但是目前JDK中所有集合实现类都遵守了这两个标准

其他方法

去除无用空间

1
2
3
4
5
6
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = Arrays.copyOf(elementData, size);
}
}

列表中是否包含某个元素

其实就是 o==null ? e==null : o.equals(e);

1
2
3
public boolean contains(Object o) {
return indexOf(o) >= 0;
}

获取某个元素的索引值

返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1

1
2
3
4
5
6
7
8
9
10
11
12
public int indexOf(Object o) {
if (o == null) {//如果传入的参数是null,则循环遍历数组,找到第一个为null的值,返回它的索引
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {//否则,遍历数组,查看两个对象是否相等;比对时需要注意equals方法有没有被重写
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;//如果数组中找不到该值,则返回-1
}

返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1

1
2
3
4
5
6
7
8
9
10
11
12
public int lastIndexOf(Object o) {//倒叙遍历数组
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

clone

重写Clone方法,虽然实现了Cloneable接口,但是Cloneable中并没有clone方法;如果不重写clone()方法,当ArrayList对象调用clone()方法时,将抛出CloneNotSupportedException异常

1
2
3
4
5
6
7
8
9
10
11
12
13

public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
//因为ArrayList已经实现了Cloneable,所以这个异常不会发生,
//如果发生了,就抛出InternalError错误:Java虚拟机中发生了意外的内部错误
throw new InternalError(e);
}
}

List转数组

1
2
3
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

转化为对象数组,数组中元素顺序和在列表中时一致;重写分配了内存空间,修改该数组不影响原列表;

需要注意的是,这里仍为浅拷贝,修改元素的内容,会影响原列表中元素的内容

1
2
3
4
5
6
7
8
9
10
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组

获取指定位置的元素

1
2
3
4
5
6
7
8
9
10
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}

public E get(int index) {
rangeCheck(index);//检查传入的索引参数是否超出列表的最大长度,如果超出,抛出越界异常IndexOutOfBoundsException

return elementData(index);
}

设值

1
2
3
4
5
6
7
public E set(int index, E element) {
rangeCheck(index);

E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

设置某个位置的元素为element,该方法返回列表中被替换的值,即原值

添加元素

1
2
3
4
5
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 保证容量,可能会触发扩容操作
elementData[size++] = e;
return true;
}

列表末尾添加元素,此方法可能会涉及到列表扩容和数组拷贝。

1
2
3
4
5
6
7
8
9
public void add(int index, E element) {
rangeCheckForAdd(index);

ensureCapacityInternal(size + 1); // Increments modCount!!
//从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}

在列表中指定位置插入元素,列表中该位置前的元素不动,后面的元素向后移动一位

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
public E remove(int index) {
rangeCheck(index);

modCount++;
E oldValue = elementData(index);

int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // 将数组中最后一个位置的元素置空,使垃圾回收起效,此处可做参考

return oldValue;
}

从列表中指定位置移除某个元素,列表的capacity容量不变,但是size减1

该方法返回移除的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

移除列表中第一个对象o,成功返回true

1
2
3
4
5
6
7
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
}

私有删除方法,跳过边界检查,并且不返回删除的值。可参考remove(int index)的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);

// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}

移除列表中索引在fromIndex、toIndex之间的元素

清空列表

1
2
3
4
5
6
7
8
9
public void clear() {
modCount++;

// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;

size = 0;
}

清空列表元素,执行该方法后,容量不变,但是size变为0

添加集合到列表中

1
2
3
4
5
6
7
8
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}

将集合中的元素都添加到列表中,需要注意的是,集合元素的类型必须是列表中元素类型或者其子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);

Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount

int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);

System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

把集合中的元素都存入到列表中的指定位置,起始位置由index决定

边界检查

1
2
3
4
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

检查传入的索引参数是否超出列表的最大长度,如果超出,抛出越界异常

该方法不检查index是否为负数,所以虽然get(i)在传入负值时仍会抛出IndexOutOfBoundsException异常,但是该异常信息是elementData[index]时数组越界产生的,不是ArrayList的rangeCheck方法抛出的,这点需要注意

ArrayList的IndexOutOfBoundsException会调用outOfBoundsMsg方法,显示【”Index: “+index+”, Size: “+size】

1
2
3
4
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

在add和addAll方法中使用,避免index出现越界问题

1
2
3
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}

List越界异常触发时,异常信息详情

交集、差集

1
2
3
4
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}

从列表中移除集合中的所有元素,相当于数学中的差集

如果集合中元素类型和列表中元素类型不匹配,则会抛出ClassCastException

如果列表中包含null值,但是集合中不允许null,则会抛出NullPointerException

1
2
3
4
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}

列表取列表和集合的交集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement) //该方法取交集或差集
elementData[w++] = elementData[r];
} finally {
// 保留与AbstractCollection的行为兼容性,即使c.contains()抛出异常也会进行部分交集/差集操作。
if (r != size) {//列表没有遍历完就抛出异常
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;//取交集/差集后,列表应有的长度
}
if (w != size) {//多余的索引位置置空,快速GC
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;//列表长度发生了改变,则证明进行了交集/差集操作,返回true,代表成功,哪怕抛出了异常
}
}
return modified;
}

删除/保留列表中的元素(在集合中存在的元素)

complement是boolean类型的,为 true 则保留,即取交集,为false 删除,即取差集

序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();

// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);

// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}

if (modCount != expectedModCount) { //防止在ArrayList对象序列化期间修改了ArrayList
throw new ConcurrentModificationException();
}
}

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;

// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in capacity
s.readInt(); // ignored

if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);

Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}

在MyBatis Plus的代码自动生成器基础上做了一定的修改和优化,配置更加清晰、易懂

相关jar包引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>${velocity.version}</version>
</dependency>

MP默认使用的是velocity模板引擎,所以还是要引入这个包的,如果是使用其他的模板引擎,如FreeMarker,则可以引入FreeMarker的包

引入了mybatis-plus-boot-starter和mybatis-plus的包后,不要再引入mybatis-spring-boot-starter相关的包,以免引起jar包冲突

配置数据库连接信息

在配置文件中配置数据库连接的信息,代码生成器中直接读取配置文件的信息,不需要重复配置

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
@Component
public class PostgreSQLGenerator implements EnvironmentAware {

@Autowired
private Environment env;

private String driverClassName;
private String username;
private String password;
private String url;

public PostgreSQLGenerator() {

}

private void init() {
this.driverClassName = env.getProperty("spring.datasource.driverClassName");
this.username = env.getProperty("spring.datasource.username");
this.password = env.getProperty("spring.datasource.password");
this.url = env.getProperty("spring.datasource.url");
}

public void generator(String[] tables, Integer result) {
AutoGenerator generator = new AutoGenerator();
generator.setGlobalConfig(globalConfig());
generator.setDataSource(dataSourceConfig());
generator.setStrategy(strategyConfig(tables));
generator.setPackageInfo(packageConfig());
//generator.setCfg(injectionConfig(result));
// if (1 == result) {
// generator.setTemplateEngine(new FreemarkerTemplateEngine());
// }
generator.execute();
// 打印注入设置
//System.err.println(generator.getCfg().getMap().get("abc"));
}

/**
* 全局配置
* @return
*/
private GlobalConfig globalConfig() {
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir("D://mybatis//");
gc.setFileOverride(true);
gc.setActiveRecord(true);// 开启 activeRecord 模式
gc.setEnableCache(false);// XML 二级缓存
gc.setBaseResultMap(true);// XML ResultMap
gc.setBaseColumnList(false);// XML columList
//gc.setKotlin(true) 是否生成 kotlin 代码
gc.setAuthor("wangxinglei");
// 自定义文件命名,注意 %s 会自动填充表实体属性!
// gc.setEntityName("%sEntity");
// gc.setMapperName("%sDao");
// gc.setXmlName("%sDao");
// gc.setServiceName("MP%sService");
// gc.setServiceImplName("%sServiceDiy");
// gc.setControllerName("%sAction");
return gc;
}

/**
* 模板引擎配置
* @return
*/
private TemplateConfig templateConfig() {
return null;
}

/**
* 数据源配置,从配置文件获取
* @return
*/
private DataSourceConfig dataSourceConfig() {
init();

DataSourceConfig dsc = new DataSourceConfig();
dsc.setSchemaname("public");
dsc.setDbType(DbType.POSTGRE_SQL);
dsc.setTypeConvert(new PostgreSqlTypeConvert());
dsc.setDbQuery(new PostgreSqlQuery());
dsc.setDriverName(driverClassName);
dsc.setUsername(username);
dsc.setPassword(password);
dsc.setUrl(url);
return dsc;
}



/**
* 策略配置
* @return
*/
private StrategyConfig strategyConfig(String[] tables) {
// 策略配置
StrategyConfig strategy = new StrategyConfig();
// strategy.setCapitalMode(true);// 全局大写命名
// strategy.setDbColumnUnderline(true);//全局下划线命名
strategy.setTablePrefix(new String[]{"tbl_"});// 此处可以修改为您的表前缀
//strategy.setFieldPrefix(new String[]{"A_"});
strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);// 允许字段策略独立设置,默认为 naming 策略
strategy.setInclude(tables); // 需要生成的表
// strategy.setExclude(new String[]{"test"}); // 排除生成的表
// 自定义实体父类
// strategy.setSuperEntityClass("com.baomidou.demo.TestEntity");
// 自定义实体,公共字段
// strategy.setSuperEntityColumns(new String[] { "test_id", "age" });
// 自定义 mapper 父类
strategy.setSuperMapperClass("com.baomidou.mybatisplus.mapper.BaseMapper");
// 自定义 service 父类
strategy.setSuperServiceClass("com.baomidou.mybatisplus.service.IService");
// 自定义 service 实现类父类
strategy.setSuperServiceImplClass("com.baomidou.springwind.service.impl.ServiceImpl");
// 自定义 controller 父类
// strategy.setSuperControllerClass("com.baomidou.demo.TestController");
// 【实体】是否生成字段常量(默认 false)
// public static final String ID = "test_id";
// strategy.setEntityColumnConstant(true);
// 【实体】是否为构建者模型(默认 false)
// public User setName(String name) {this.name = name; return this;}
// strategy.setEntityBuliderModel(true);
return strategy;
}

/**
* 包配置
* @return
*/
private PackageConfig packageConfig() {
PackageConfig pc = new PackageConfig();
//pc.setModuleName("test");
pc.setParent("com.lemon.rabbit");// 自定义包路径
pc.setController("controller");// 这里是控制器包名,默认 web
pc.setEntity("model");
pc.setMapper("mapper");
pc.setXml("mapping");
pc.setService("service");
pc.setServiceImpl("service.impl");
return pc;
}

/**
* 自定义配置
* @param result
* @return
*/
private InjectionConfig injectionConfig(Integer result) {
// 注入自定义配置,可以在 VM 中使用 cfg.abc 设置的值
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
Map<String, Object> map = new HashMap<>();
map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp");
this.setMap(map);
}
};
List<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig("/templates/dto.java" + ((1 == result) ? ".ftl" : ".vm")) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输入文件名称
return "D://test/my_" + tableInfo.getEntityName() + ".java";
}
});
cfg.setFileOutConfigList(focList);
return cfg;
}

@Override
public void setEnvironment(Environment environment) {
this.env = environment;
}
}

使用

在单元测试中直接调用postgreSQLGenerator的generator方法即可,第一个参数填入对应的表名,可以写一个,也可以写多个,然后执行用例即可生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RunWith(SpringRunner.class)
@SpringBootTest
public class DatabaseGeneratorTest {

@Autowired
private PostgreSQLGenerator postgreSQLGenerator;

@Before
public void setup() {

}

@Test
public void contextLoads() throws Exception{
String[] tables = new String[]{"tbl_city"};
Integer result = 1;
postgreSQLGenerator.generator(tables, 1);
}

}

使用阿里云 OSS SDK 创建 Bucket 时,出现如下错误:

1
2
3
4
5
6
7
8
9
10
11
12
com.aliyun.oss.OSSException: Put bucket request is not resource owner.
[ErrorCode]: AccessDenied
[RequestId]: 5E821469BB409736367FAEA5
[HostId]: qinghuazs-test.oss-cn-hangzhou.aliyuncs.com
[ResponseError]:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Put bucket request is not resource owner.</Message>
<RequestId>5E821469BB409736367FAEA5</RequestId>
<HostId>qinghuazs-test.oss-cn-hangzhou.aliyuncs.com</HostId>
</Error>

权限不足,用户不具备创建 Bucket 的权限,检查后发现虽然创建了用户,但是用户并未加入到用户组中,所以会创建失败。

解决方案

1)为用户添加AliyunOSSFullAccess(管理对象存储服务(OSS)权限)的权限

2)将用户添加到用户组下

SpringBoot 下使用 Junit 进行单元测试时,报错:java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

具体报错信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
信息: Could not detect default configuration classes for test class [com.qinghuazs.starlight.oss.AliOSSBucketClientTest]: AliOSSBucketClientTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.

java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

at org.springframework.util.Assert.state(Assert.java:73)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.getOrFindConfigurationClasses(SpringBootTestContextBootstrapper.java:240)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.processMergedContextConfiguration(SpringBootTestContextBootstrapper.java:153)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:395)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildDefaultMergedContextConfiguration(AbstractTestContextBootstrapper.java:312)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:265)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildTestContext(AbstractTestContextBootstrapper.java:108)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.buildTestContext(SpringBootTestContextBootstrapper.java:97)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:139)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:124)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:151)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:142)
at org.springframework.test.context.junit4.SpringRunner.<init>(SpringRunner.java:49)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:33)
at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:36)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:49)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

根据报错提示,可以得知 SpringBoot 应用下缺失 @SpringBootConfiguration 类,即 SpringBoot 启动类(@SpringBootApplication 是一个复合注解),增加一个 SpringBoot 启动类即可。

1
2
3
4
5
6
7
@SpringBootApplication
public class OSSApplication {

public static void main(String[] args) {
SpringApplication.run(OSSApplication.class, args);
}
}

可以用于从Java主方法引导并启动Spring应用程序的类。默认情况下,类将执行以下步骤启动应用程序:

  • 创建适当的ApplicationContext实例(取决于类路径)
  • 注册一个CommandLinePropertySource,以Spring属性的形式暴露命令行参数
  • 刷新应用程序上下文,加载所有的单例bean
  • 触发所有的CommandLineRunner bean

在大多数情况下,静态方法run(Class<?>[] primarySources, String[] args)可以直接从主方法调用,从而引导应用程序:

1
2
3
4
5
6
7
8
9
10
@Configuration
@EnableAutoConfiguration
public class MyApplication {

// ... Bean definitions

public static void main(String[] args) throws Exception {
SpringApplication.run(MyApplication.class, args);
}
}

对于更高级的配置,可以在运行之前创建和定制SpringApplication实例:

1
2
3
4
5
public static void main(String[] args) throws Exception {
SpringApplication application = new SpringApplication(MyApplication.class);
// ... customize application settings here
application.run(args)
}

springapplication可以从各种不同的来源读取bean。通常建议使用一个@Configuration类来引导应用程序,但是,您也可以从以下设置源:

  • 由AnnotatedBeanDefinitionReader加载的完全限定类名
  • XmlBeanDefinitionReader装载的XML资源的位置,或者GroovyBeanDefinitionReader装载的groovy脚本
  • ClassPathBeanDefinitionScanner要扫描的软件包的名称

配置属性也绑定到SpringApplication。这使得可以动态地设置SpringApplication属性,比如额外的源(“spring.main”。来源“-一个CSV列表)标志以表示一个web环境(“spring.main.web-application-type=none”)或标志以关闭横幅(“spring.main.bannermode =off”)。

启动时调用main方法,主要执行***SpringApplication.run()***方法

1
2
3
public static void main(String[] args) {
SpringApplication.run(RabbitApplication.class, args);
}

SpringApplication的run方法实现

1
2
3
4
5
6
7
8
9
10
11
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}

其中primarySource即为com.lemon.rabbit.RabbitApplication

SpringApplication中定义了一些静态的成员属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

//在非web环境中默认使用的应用程序上下文的类名。
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";

//应用程序上下文的类名,默认用于web环境。
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

//
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };

public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler";

private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";

//banner的默认存放位置
public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;

//旗帜位置属性的关键。
public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

分区的具体好处是:

某些类型的查询性能可以得到极大提升

更新的性能也可以得到提升,因为表的每块的索引要比在整个数据集上的索引要小。如果索引不能全部放在内存里,那么在索引上的读和写都会产生更多的磁盘访问

批量删除可以用简单的删除某个分区来实现

可以将很少用的数据移动到便宜的、转速慢的存储介质上

阅读全文 »

单机部署

下载安装包

1
wget http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.12/zookeeper-3.4.12.tar.gz

解压

1
tar -zxvf zookeeper-3.4.12.tar.gz 

检查Java环境

需要JDK1.6以上的环境

使用java -version检查是否安装JDK

1
java -version

输出结果

1
2
3
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

配置环境变量

1
2
3
[root@iZbp19q9mzfovzwh6hk6rpZ zookeeper-3.4.12]# pwd
/usr/local/soft/zookeeper/zookeeper-3.4.12
[root@iZbp19q9mzfovzwh6hk6rpZ zookeeper-3.4.12]# vim /etc/profile

添加环境变量

1
2
3
ZK_HOME=/usr/local/soft/zookeeper/zookeeper-3.4.12
PATH=$PATH:$ZK_HOME/bin
export PATH ZK_HOME

使配置马上生效

1
[root@iZbp19q9mzfovzwh6hk6rpZ zookeeper-3.4.12]# source /etc/profile

配置zk

1
2
3
4
5
6
7
8
9
[root@iZbp19q9mzfovzwh6hk6rpZ zookeeper-3.4.12]# mkdir data
[root@iZbp19q9mzfovzwh6hk6rpZ zookeeper-3.4.12]# mkdir logs
[root@iZbp19q9mzfovzwh6hk6rpZ zookeeper-3.4.12]# cd conf/
[root@iZbp19q9mzfovzwh6hk6rpZ conf]# ll
total 12
-rw-rw-r-- 1 1000 1000 535 Mar 27 12:32 configuration.xsl
-rw-rw-r-- 1 1000 1000 2161 Mar 27 12:32 log4j.properties
-rw-rw-r-- 1 1000 1000 922 Mar 27 12:32 zoo_sample.cfg
[root@iZbp19q9mzfovzwh6hk6rpZ conf]# cp zoo_sample.cfg zoo.cfg

修改zoo.cfg中的配置,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=20
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/usr/local/soft/zookeeper/zookeeper-3.4.12/data
dataLogDir=/usr/local/soft/zookeeper/zookeeper-3.4.12/logs
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
server.1=127.0.0.1:2888:3888

其中:
2888端口号是zookeeper服务之间通信的端口
3888是zookeeper与其他应用程序通信的端口

启动与停止

切换到zookeeper的bin目录下

启动
1
./zkServer.sh start
查看进程
1
jps 

其中QuorumPeerMain是zookeeper进程,启动正常

查看状态
1
./zkServer.sh status
停止
1
./zkServer.sh stop

设置开机启动

切换到/etc/rc.d/init.d/目录下

1
cd /etc/rc.d/init.d

创建zookeeper文件

1
touch zookeeper 

更新权限

1
chmod +x zookeeper 

编辑文件,写入一下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
#chkconfig:2345 20 90
#description:zookeeper
#processname:zookeeper
export JAVA_HOME=/usr/local/soft/jdk/jdk1.8.0_171
export PATH=$JAVA_HOME/bin:$PATH
ZK_HOME=/usr/local/soft/zookeeper/zookeeper-3.4.12
case $1 in
start)su root $ZK_HOME/bin/zkServer.sh start;;
stop)su root $ZK_HOME/bin/zkServer.sh stop;;
status)su root $ZK_HOME/bin/zkServer.sh status;;
restart)su root $ZK_HOME/bin/zkServer.sh restart;;
*) echo "require start|stop|status|restart" ;;
esac

这样就可以用service zookeeper start/stop来启动停止zookeeper服务了

使用命令把zookeeper添加到开机启动里面

1
2
chkconfig zookeeper on  
chkconfig --add zookeeper

然后通过chkconfig –list查看是否已成功添加到开机启动项

1
zookeeper      	0:off	1:off	2:on	3:on	4:on	5:on	6:off