小知行的博客


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

未命名

发表于 2021-05-16 | 0

SpringBean 的生命周期

JVM概念一

发表于 2020-06-18 | 分类于 java基础 |

JVM概念一

  • 虚拟机:
    • 系统虚拟机
      • 对物理计算机的仿真: Visual Box, Vmware
    • 程序虚拟机
      • 专门为执行单个计算机程序而设计: Java虚拟机,执行Java字节码指令
  • Java虚拟机:
    • 跨平台(一次编译到处运行);
    • 优秀的垃圾回收(自动垃圾回收);
    • 可靠的即时编译;
    • 自动内存管理
阅读全文 »

JVM架构模型

发表于 2020-06-18 | 分类于 java基础 |

JVM架构模型

指令集架构分类:

  • 基于栈的指令集架构(JVM) 

    • 设计简单,对系统资源要求不高(只有进栈出栈操作)

    • 使用零地址指令方式分配,避开了寄存器的分配难题

    • 由于使用零地址指令,其执行过程依赖于操作栈。指令集更小(但会花费更多的指令去完成一项操作,8字节对齐)

    • 不需要硬件支持,可移植性更好,更好实现跨平台。  

  • 基于寄存器的指令集架构(X86: PC,Android的Davlik虚拟机)

    • 指令集架构完全依赖硬件,可移植性差,但也因此具有更好的性能和更高的执行效率

    • 大部情况下以一地址指令,二地址指令,三地址指令为主;指令集更大16字节对齐,但完成一项操作会花费更少的指令

总结: 由于跨平台的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。

优点: 跨平台,指令集小,编译器容易实现;缺点: 性能下降,实现同样的功能需要更多的指令。

tips:反编译命令: javap -v xxx.class

Java多线程基础

发表于 2020-05-10 | 分类于 java基础 |

Java多线程基础

线程是一个轻量级的“进程”, 是程序最小的执行单位。了解多线程,我们从线程的生命周期、基本操作、线程安全等几个方面来入手。

生命周期

在Java中一个线程的生命周期状态有以下六种(都在Thread.State枚举中):

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

public enum State {
// 刚创建线程,尚未执行
NEW,
// 线程执行中
RUNNABLE,
// 线程阻塞(遇到了synchronized同步块)
BLOCKED,
// 线程等待(wait(),join()),直到被notify()唤醒或等待线程结束
WAITING,
// 线程等待,有时限等待
TIMED_WAITING,
// 线程结束
TERMINATED;
}

可以简单画一张图来描述Java线程的生命周期:

线程状态图

基本操作

其实在上面的线程状态图中已经包含了大部分线程的基本操作了:

new 一个线程对象,用Thread#start()启动线程。

Thread#start()会让这个线程执行Thread#run(),但Thread#run 默认的实现为空,故在new一个线程对象时需要重写(@Override)run()方法,实现自己的任务逻辑。

tips: start()会调用native方法start0(),继而由 JVM 来实现多线程的控制,因为需要系统调用来控制时间分片

我们可以通过继承Thread类来重写run方法,实现自定义任务。
Java是单继承(只能继承一个类)多实现(可以实现多个接口),由于单继承的局限性,更推荐通过实现Runnable接口的run()方法来实现自定义任务(实际上该接口也只有这一个方法).

Thread类有一个重要的构造方法: public Thread(Runnable target) ,看到target我们自然而然就回想到代理模式。实际上Thread和Runnable就是一种静态代理的实现,参考以下代码(JDK8部分源码)

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

// 接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}

// Thread 实现了Runnable接口 作为代理
public class Thread implements Runnable {

// 构造方法之一
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}

@Override
public void run() {
if (target != null) {
target.run();
}
}
}

// 自己实现Runnable 接口
public class MyThread implements Runnable {
public int count = 10;
@Override
public synchronized void run(){
while(count>0){
count--;
}
}
}

// 创建线程实例
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
// start()会调用native方法start0(),继而由 JVM 来实现多线程的控制,因为需要系统调用来控制时间分片
thread.start();
}
}

线程终止与中断

线程终止的情况:

  1. 正常执行结束
  2. 调用Thread#stop()方法暴力终止。由于其会把执行一半的线程强行终止,可能还会引起数据不一致问题,目前已被废弃(@Deprecated)

stop

stop已被废弃,JDK提供了一个更为强大的支持,线程中断:

1
2
3
4
5
6
7
8
9

public class Thread implements Runnable {
// 中断线程
pubulic void interrupt();
// 判断是否被中断
public boolean isInterrupted();
// 判断是否被中断,并清除当前中断状态
poublic static boolean interrupted();
}

线程中断并不会使线程立马终止,而是给线程发送一个通知,告知线程目标需要终止,线程具体在什么时候退出,由目标线程自行决定(通过isInterrupted判断状态,决定怎么退出以及退出逻辑)。

关于中断还有中断响应这个概念,所有抛出InterruptedException的方法都可以响应中断,例如:

1
2
3
4
5
6
7
8

// Object#wait()
public final void wait() throws InterruptedException {
wait(0);
}

// Thread#sleep(long)
public static native void sleep(long millis) throws InterruptedException;

tips: 对抛出的InterruptedException进行捕获会清除中断标记,如果后续还需要判断中断状态,需要再次设置中断标记Thread.currentThread.interrupt()

wait()和notify()。

wait() 和 notify()是Object类中的两个方法。用于对多线程协作进行支持。

obj.wait()并不是随便可以调用的,它 必须包含在对应synchronized(obj) 语句中 ,因为无论是wait()还是notify()都需要先获得一个目标对象的监视器 (源码中wait()的注释This method should only be called by a thread that is the owner of this object's monitor.) 。notify() 也一样。

obj.wait()使持有obj对象锁的线程进入等待,并释放资源 让其他等待obj对象的线程可以正常执行,obj.notify()会 随机 唤醒一个等待中的线程, 但不会立即释放资源 需要等待当前线程的synchronized语句执行完毕才会释放,还有一个obj.notifyAll()会唤醒所有等待的线程.

tips: 实际上, hotspot JVM 中notify()是顺序唤醒的。参考文章

tips: sleep()方法也会让线程等待若干时间,但并不释放锁资源,也不能被通知唤醒。 但wait() 和 sleep() 都可响应中断。sleep()会使线程进入TIMED_WAITING状态

tips: 使用jstack命令可以查看当前系统的线程信息。配合jps使用

tips: 推荐wait()和notify()配合使用来替换挂起suspend()和继续执行resume()这两个已被废弃的方法。线程挂起suspend()并不会释放资源,若使用不当,resume()方法在suspend()之前执行了,还会导致死锁。被挂起的线程状态还是Runnable

join() 和 yield()

join() 字面意思是加入,但其在Java中其实也是一种等待,其核心代码如下,调用了wait(0) 方法.但join()不用notify通知唤醒,它会在被阻塞线程执行完毕后自动释放资源。

适用场景: 线程A的输入依赖与线程B的输出,则就需要使用B.join(),等待B执行完毕,A再继续执行

1
2
3
4
5
6
7
8

public final void join() throws InterruptedException; // 调用join(0)

public final synchronized void join(long millis) throws InterruptedException;

while (isAlive()) {
wait(0); // join(long)则调用wait(long)
}

tips: 由于join() 实际调用wait() 故在开发中不用在Thread对象上使用wait() , notify(),因为这样可能会和系统api相互影响产生预期之外的错误。

yield()是一个静态native方法,使用yield() 会使当前线程让出CPU,之后和其他线程再次竞争CPU资源。

使用场景:一个线程不怎么重要,或者优先级很低,怎可以在适当的时候调用yield() 让出资源,给其他线程更多机会。

其他操作

  1. 设置后台守护进程(Daemon) ,如垃圾回收线程,JIT线程
1
2
3
4
5
6

Thread thread = new Thread();
// 此方法必须在start之前执行,否则该线程还会继续正常执行,
// 但不会当做守护线程执行,而是被当做用户线程
thread.setDaemon(true);
thread.start();
  1. 设置线程优先级(Priority), 优先级从1到10,数字越大优先级越高,高的优先级并不表示一定会最先执行.Thread类默认给了三个优先级 1, 5, 10.
1
2
3
4
5
6
7
8
9
10

// Thread默认的三个优先级别
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

//设置优先级
Thread thread = new Thread();
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();

线程安全概念

使用多线程可以提升效率但是也不能牺牲正确性,多线程操作临界区数据时若没有进行恰当的处理,则很有可能会产生冲突。最简单的处理办法就是使用synchronized关键字来实现线程间的同步,更加强大的控制则需要对JUC并发包进行学习。

synchronized关键字除了用于同步,确保线程安全外还可以保证线程间的可见性和有序性。可见性完全可以替代volatile关键字,只是没有volatite使用方便;有序性,被synchronized修饰的代码块内部是串行执行的,必然可以保证有序性。

tips: 并发下的ArrayList可能会抛出下标越界的异常,这是由于ArrayList在扩容过程中,内部一致性遭到破坏,由于没有锁的保护其他线程访问到了不一致的内部状态,导致下标越界。 或者没有异常信息但是最终结果不一致,这是由于多线程访问冲突,使得保存容器大小的变量被不正常访问,同时有多个线程对ArrayList的同一位置进行赋值,并发下的HashMap也可能会出现这个问题。

并发与JMM(2)——指令重排与Happen-Before规则

发表于 2020-05-05 | 分类于 java基础 |

指令重排

在多线程的有序性里边我们已经初步了解了指令重排,但是需要注意的是,在单个线程里指令执行的顺序一定是一致的,也就是说指令重排必须保证串行语义的一致性,不能因为指令重排导致串行语义的逻辑发生问题。

tips: 指令重排可以保证串行语义的一致,但无法保证多线程间的语义一致

为什么要进行指令重排? 无他,为了性能。

在Cpu执行一条指令时,简单来说可以分为如下几步:

  1. 取指令 IF
  2. 译码和取寄存器操作数 ID
  3. 执行或者有效地址计算 EX
  4. 存储器访问 MEM
  5. 写回 WB

CPU在实际工作中执行一组指令,为了性能,并不是等前一条指令执行完毕再依次执行下一条指令,而是采用流水线的工作原理:
指令流水线

在实际执行一个程序时,流水线之间的衔接不可能总是完美的,如 A = B + C 这个操作:

A=B+C

图中的红叉就表示流水线中断,CPU进入等待,一旦发生中断,这个时间点执行的所有步骤都会向后延(最后的SW操作执行到ID也进入等待)极大影响效率。
为了避免这种情况发生就需要进行指令重排,在不影响串行语义的情况下对指令进行重新排序,尽可能提高效率。

Happen-Before规则

Java虚拟机和执行系统再进行指令重排时需要遵守Happen-Before规则。

Happen-Before规则是用来阐述操作之间的内存可见性。用来保证正确同步的多线程程序的执行结果不被改变

望文生义,Happen-Before很容易理解为前一个操作执行完毕再执行后一个操作,即两个遵守happen-before规则的操作是按顺序先后执行的,这样理解对,但不完全对。
站在开发者的角度来说,这样理解好像没什么毛病,毕竟系统执行程序的结果是符合预期的(语义未改变);但是对于Java虚拟机来说,只要保证第一个操作的执行结果对第二个操作可见即可,如果指令重排后的执行结果和happen-before规则执行的结果一致则允许进行重排。

一张图理解happen-before:
一张图理解happen-before

Happen-Before规则的基本原则:

  1. 程序顺序原则: 单个线程内保证语义的串行性。
  2. volatile规则: volatile变量的写,先发生与读,保证volatile变量的可见性。
  3. 锁规则: 解锁(unlock)必须发生再随后的加锁(lock)前。
  4. 传递性: A先于B, B先于C ,那么A必然先于C。
  5. 线程的start()方法先于它的每一个动作。
  6. 线程的所有操作优先于线程的终结(Thread.join())
  7. 线程的中断(interrupt()) 先于被中断线程的代码。
  8. 对象的构造函数执行、结束优先于finalize() 方法。

并发与JMM(1)——原子性,可见性,有序性

发表于 2020-05-05 | 分类于 java基础 |

并发与JMM

java多线程技术可以大幅提升Java应用性能,但同时也会带来一些问题,比如处理开发并发程序会必串行程序复杂的多,并发程序会遇到一些串行程序没有的问题,如数据访问的一致性和安全性。那么 java程序并发下数据访问的一致性和安全性该如何保障?

JMM(Java内存模型)定义了一种规则,用来保证多个线程间可以有效地、正确地协同工作。

在了解JMM之前我们先熟悉一下多线程的原子性、可见性、有序性,这些概念对学习JMM很重要。

阅读全文 »

Java中的泛型

发表于 2020-04-05 | 分类于 java基础 |

Java中的泛型

Java是在JDK1.5之后引入泛型,为Java开发带来诸多好处,具体有两点:

  • 简单安全。加入泛型后在编译时有类型检查,提前发现问题,减少不必要的bug
  • 提升性能。加入泛型之前,需要强转以获得期望的值;使用泛型对容器操作则不行用进行类型转换,提到代码可读性和运行性能。

    从以下几个方面来了解泛型

  • 泛型分类
  • 泛型的界限
  • 泛型擦除
  • 使用
阅读全文 »

在docker中安装部署gitlab

发表于 2019-09-07 | 分类于 docker |

在docker中安装部署gitlab

  • 方法: docker-compse

阅读全文 »

docker的安装及换源

发表于 2018-09-05 | 分类于 docker |

docker的安装及换源

  • 系统: centos 8
  • 安装方式:yum
  • 参考: docker官方文档

  • 卸载旧版本
  • 安装
  • 更换到国内源
阅读全文 »

mysql修改root密码

发表于 2018-03-23 | 分类于 环境搭建 , 数据库 , MySQL |
如何解决MySQL5.7忘记密码(修改密码)的问题

阅读全文 »
lyf

lyf

乘风破浪会有时

10 日志
5 分类
11 标签
© 2021 lyf | 本站总访问量4081次 本站访客数3403人次
由 Hexo 强力驱动
|
主题 — NexT.Muse v6.0.6