Java并发-ThreadPoolExecutor深度解析

ThreadPoolExecutor搞不懂?看这篇就够了

Mysql高可用架构总结

为了创建高可用数据库系统,传统的实现方式是创建一个或多个备用的数据库实例,MySQL5.7新引入了Group Replication,用于搭建更高事务一致性的高可用数据库集群系统。

Java并发-锁的应用与原理,看这一篇就够了

通过对锁原理的分析,重点分析ReentrantLock和ReentrantReadWriteLock的源码,通过锁的实现更深入的理解AQS。

Jedis集群模式经典实现

Jedis是Redis的Java客户端,本代码是Jedis应用的一个范例。

Java函数式编程总结

在阅读SpringBoot的源码时,框架运用了大量的函数式编程。可以说,业务代码你可以不使用函数式编程,但是上升到框架层面,函数式编程是基础。

linux使用jmeter进行压力测试

linux使用jmeter进行压力测试

安装

export PATH=/usr/local/jmeter/bin/:$PATH 添加到/etc/profile末尾。

1
`$ wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-3.3.tgz$ tar -xvf apache-jmeter-3.3.tgz$ mv apache-jmeter-3.3.tgz jmeter$ mv jmeter /usr/local`

Java-Provider一种写法

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 class Test {

public static void main(String[] args) {
Map<String, NodeProvider<Node>> map = new HashMap<>();
map.put("ik", TestInner::getNode);

System.out.println(map.get("ik").get("name", 1).getName());
System.out.println(map.get("ik").get("name", 200).getAge());

}
}

interface NodeProvider<T> {
T get(String name, int age);
}

class Node {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

class TestInner {
public static Node getNode(String name, int age) {
Node node = new Node();
node.setAge(age);
node.setName(name);

return node;
}
}

单例模式

单例模式

单例模式分为了饿汉式和懒汉式,总体来说懒汉式要优于饿汉式,饿汉式不管是否其他线程调用了getInstance,都在类加载阶段创建了实例。而懒汉式则只有在调用的时候,才实例化对象,更加节省系统资源。

饿汉式:

1
2
3
4
5
6
7
8
public class Singleton  {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}

public static Singleton getInstance() {
return INSTANCE;
}
}

懒汉式-双重检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 懒汉式-双重检查
*/
public class SingletonLazy {
private static SingletonLazy instance = null;

private SingletonLazy() {
}

public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}

懒汉式-内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 懒汉式-内部类
*/
public class SingletonLazy1 {


private SingletonLazy1() {
}

private static class InnerSingleton {
private final static SingletonLazy1 instance = new SingletonLazy1();
}


public static SingletonLazy1 getInstance() {

return InnerSingleton.instance;
}

public static void helloworld() {
System.out.println("hello lazy singleton.");
}
}

执行Main方法测试,从输出结果看,只有执行了SingletonLazy1.getInstance()方法,才开始加载内部类SingletonLazy1$InnerSingleton。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static void main(String[] args) throws InterruptedException{
SingletonLazy1.helloworld();
Object lock = new Object();
synchronized (lock) {
lock.wait(1000);
}
System.out.println("分割线---------------");
System.out.println(SingletonLazy1.getInstance());
}
}

输出结果:
[Loaded sun.nio.cs.US_ASCII$Decoder from /Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/jre/lib/rt.jar]
分割线---------------
[Loaded sun.misc.VMSupport from /Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded com.wsy.learn.designmodel.SingletonLazy1$InnerSingleton from file:/Users/wangsiyuan1/workspace/springtest/target/classes/]
com.wsy.learn.designmodel.SingletonLazy1@27716f4

生产者消费者模式-Java

生产者消费者模式

生产者-消费者模式在服务端编程中,是一种很常见的设计模式,比如消息队列的实现,就是这种思想。本文就是用Java语言编写一个简单的生产者消费者例子,从而引出concurrent包下的阻塞队列和ReentrantLock一些玩法。

##基础知识

首先复习下基础知识,在Java中concurrent包下并发队列分为阻塞队列非阻塞队列,ConcurrentLinkedQueue是非阻塞队列,底层实现用了CAS。阻塞队列包括LinkedBlockingQueue,LinkedBlockingDeque,LinkedTransferQueue,ArrayBlockingQueue,阻塞队列底层是靠ReentrantLock实现。Condition包括await,signal,signalAll,Condition作为条件锁

我们知道Lock的本质是AQS,AQS自己维护的队列是当前等待资源的队列,AQS会在被释放后,依次唤醒队列中从前到后的所有节点,使他们对应的线程恢复执行,直到队列为空。

而Condition自己也维护了一个队列,该队列的作用是维护一个等待signal信号的队列。

但是,两个队列的作用不同的,事实上,每个线程也仅仅会同时存在以上两个队列中的一个,流程是这样的:

  • 1、线程1调用reentrantLock.lock时,尝试获取锁。如果成功,则返回,从AQS的队列中移除线程;否则阻塞,保持在AQS的等待队列中。

  • 2、线程1调用await方法被调用时,对应操作是被加入到Condition的等待队列中,等待signal信号;同时释放锁。

  • 3、锁被释放后,会唤醒AQS队列中的头结点,所以线程2会获取到锁。
  • 4、线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。注意,这个时候,线程1 并没有被唤醒,只是被加入AQS等待队列。
  • 5、signal方法执行完毕,线程2调用unLock()方法,释放锁。这个时候因为AQS中只有线程1,于是,线程1被唤醒,线程1恢复执行。

所以,发送signal信号只是将Condition队列中的线程加到AQS的等待队列中。只有到发送signal信号的线程调用reentrantLock.unlock()释放锁后,这些线程才会被唤醒。可以看到,整个协作过程是靠结点在AQS的等待队列和Condition的等待队列中来回移动实现的,Condition作为一个条件类,很好的自己维护了一个等待信号的队列,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作。 signal就是唤醒Condition队列中的第一个非CANCELLED节点线程,而signalAll就是唤醒所有非CANCELLED节点线程,本质是将节点从Condition队列中取出来一个还是所有节点放到AQS的等待队列。尽管所有Node可能都被唤醒,但是要知道的是仍然只有一个线程能够拿到锁,其它没有拿到锁的线程仍然需要自旋等待,就上上面提到的第4步(acquireQueued)。

##生产者-消费者代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 用阻塞队列实现生产-消费者模式
*/
public class Productor {
static LinkedBlockingQueue<Integer> blockQueue = new LinkedBlockingQueue<>(10);

void provide() throws InterruptedException {
for (int i = 0; i < 10; i++) {
blockQueue.offer(i);
System.out.println("生产:" + i);
Thread.sleep(100);
}
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 基于阻塞队列消费者
*/
public class Consumer {
void cusume() throws InterruptedException {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("消费:" + Productor.blockQueue.take());
}
}
}
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
static void test0() {
//创建生产者
Productor productor = new Productor();
//创建消费者
Consumer consumer = new Consumer();
Thread cusumerThread = new Thread(() -> {
try {
consumer.cusume();
} catch (Exception e) {

}

});
cusumerThread.start();

try {
productor.provide();

cusumerThread.interrupt();
} catch (Exception e) {
e.printStackTrace();
}
}

输出:
生产:0
消费:0
生产:1
消费:1
生产:2
消费:2
生产:3
消费:3
生产:4
消费:4
生产:5
消费:5
生产:6
消费:6
生产:7
消费:7
消费:8
生产:8
生产:9
消费:9
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
/**
* 自己实现的简单的阻塞队列
*/
public class MyBlockQueue<E> {
private LinkedList<E> queue = new LinkedList<>();
//第一种实现方式,采用了Object wait和notify的方式
private Object lock = new Object();
//第二种实现方法,采用了ReentrantLock获取Condition,通过condition await和signal方式实现
private ReentrantLock reentrantLock = new ReentrantLock();
Condition con = reentrantLock.newCondition();

void offer(E e) {
queue.offer(e);
synchronized (lock) {
lock.notifyAll();
}
}

E take() throws InterruptedException {
if (queue.size() == 0) {
synchronized (lock) {
lock.wait();
}
}
return queue.poll();
}

void offer1(E e) throws InterruptedException {

try {
reentrantLock.lockInterruptibly();
queue.offer(e);
con.signalAll();

} finally {
reentrantLock.unlock();
}
}

E take1() throws InterruptedException {
if (queue.size() == 0) {
try {
reentrantLock.lockInterruptibly();
con.await();

} finally {
reentrantLock.unlock();
}
}
return queue.poll();
}
}

Java线程基础知识

Java线程基础

如下图所示,Java中线程可分为NEW,RUNABLE,RUNING,BLOCKED,WAITING,TIMED_WAITING,TERMINATED 共七个状态,一个状态是如何过渡到另一个状态图中标识的很清楚。

img

  • 初始状态(NEW)
    实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

  • 就绪状态(RUNNABLE)
    就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。

    • 调用线程的start()方法,此线程进入就绪状态。
    • 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
    • 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
    • 锁池里的线程拿到对象锁后,进入就绪状态。
  • 运行中状态(RUNNING)
    线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

  • 阻塞状态(BLOCKED)
    阻塞状态是线程阻塞在进入synchronized关键字(当然也包括ReentrantLock)修饰的方法或代码块(获取锁)时的状态。

  • 等待(WAITING)
    处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

  • 超时等待(TIMED_WAITING)
    处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

  • 终止状态(TERMINATED)
    当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

Java线程常用API

img

其中常用的方法为start(),yield(),sleep(),interrupt(),interrupted(),isInterrupted(),isAlive(),join(),

setDaemon(),setName(),setPriority(),其中stop方法和destroy方法,都是被废弃的方法在日常使用中不建议用。除了Thread类下的API,Object类下的wait(),notify(),notifyAll(),这三个方法也经常在多线程场景中出现。本文的目的,主要讲解的就是这些方法的使用和内部原理。

方法 方法说明 Exception
Thread.start 开启执行线程,由虚拟机负责调用run方法。(Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.) IllegalThreadStateException if the thread was already started.
Thread.yield 让出CPU,但是仅仅对同线程级别(Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilise a CPU)
Thread.sleep 使得正在执行的当前线程睡眠。敲黑板!但是不会让出任何锁的所有权( The thread does not lose ownership of any monitors.)这个特性很重要,也就是说在同步块中使用Thread.sleep要谨慎。 当线程中断会抛出InterruptedException异常,并同时清空中断标志位
Thread.interrupt 中断线程,但只是设置了中断标志位,此刻调用isInterrupted返回true。例子请参考下面的示例代码testInterrupt0方法。只会打印到0-9循环跳出 SecurityException if the current thread cannot modify this thread 请教这个异常什么时候会发生呢?
Thread.isInterrupted 查看线程是否处于中断状态.true为中断。调用之后不清除中断标志位。
Thread.interrupted 查看线程是否处于中断状态.true为中断。调用之后清除中断标志位。心细的同学已经发现和isInterrupted的区别了吧。
Thread.isAlive 线程是否存活,A thread is alive if it has been started and has not yet died.
Thread.join 等待线程死亡之后再执行。(Waits for this thread to die) 当线程中断会抛出InterruptedException异常,并同时清空中断标志位
Thread.setDaemon 设置为守护线程。任何非守护线程还在运行,守护线程就不会终止,最典型的守护线程是垃圾回收器的回收线程。 IllegalThreadStateException 当线程状态是alive的时候不能调用setDaemon
Thread.setName 设置线程的name
Thread.setPriority 设置线程的优先级。MIN_PRIORITY为1,MAX_PRIORITY为10,NORM_PRIORITY为5。
Object.wait 如果不指定timeout,则一直阻塞直到其他线程调用notify或者notifyAll。敲黑板!调用wait之后当前线程不在持有对象锁。
Object.notify 随机唤醒同一个对象monitor下的某个线程
Object.notifyAll 唤醒同一个对象monitor下的所有线程。查看testNotify示例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void testInterrupt0()  Exception {
int i = 0;
while (!Thread.currentThread().isInterrupted()) {
System.out.println("loop" + i++);
if(i == 10) {
Thread.currentThread().interrupt();
}
}
//echo true
System.out.println(Thread.currentThread().isInterrupted());
//echo true
System.out.println(Thread.currentThread().interrupted());
//echo false
System.out.println(Thread.currentThread().isInterrupted());
}
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
public class MyThread extends Thread {
private Object lock;
private String name;

public MyThread(Object lock, String name) {
this.lock = lock;
this.name = name;
}

@Override
public void run() {
synchronized (lock) {
try {
System.out.println(name + " get lock,interrupt =" + Thread.currentThread().isInterrupted());

lock.wait();

//Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(name + " is interrupt. notify all interrupt =" + Thread.currentThread().isInterrupted());
lock.notifyAll();
}
System.out.println(name + ":notified...");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void testNotify() throws Exception {
MyThread t1 = new MyThread(lock, "t1");
MyThread t2 = new MyThread(lock, "t2");

Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);

thread1.start();
thread2.start();

Thread.sleep(1000);
long startTime = System.currentTimeMillis();
synchronized (lock) {
System.out.println("main get lock");
lock.notifyAll();
}

thread1.join();
thread2.join();
long endTime = System.currentTimeMillis();

System.out.println("notify lock.time =" + (endTime - startTime));
}

testNotify执行结果

t1 get lock,interrupt =false
t2 get lock,interrupt =false
main get lock
t2:notified…
t1:notified…