java 多线程

java实现多线程的方式

  • 继承thred接口
  • 实现runnable接口
  • 实现Callable接口

创建线程的方式

  • 通过thread类直接创建线程
  • 利用线程池内部类创建线程

启动线程的方式

  • 调用线程的start()方法
  • 利用线程池内部创建线程 那啥是线程池呢

上下文切换

回忆起了操作系统中的pcb,tcb。操作系统在切换线程的时候也会保留下pcb和tcb的信息

cpu执行线程的任务时,会为线程分配时间片,以下几种情况会发生上下文切换。

  1. 线程的cpu时间片用完
  2. 垃圾回收
  3. 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当发生上下文切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态,jvm中有块内存地址叫程序计数器,用于记录线程执行到哪一行代码,是线程私有的。

JVM程序计数器 VS OS程序计数器

守护线程

java进程需要等待所有线程都运行结束,才会结束,有一种特殊线程叫守护线程,当所有的非守护线程都结束后,即使它没有执行完,也会强制结束。

垃圾回收线程就是典型的守护线程

线程的阻塞

join是指调用该方法的线程进入阻塞状态,等待某线程执行完成后恢复运行

线程的打断

时间片的切换

线程的状态

Untitled

线程的五种状态

93f311262656412c83a99b9c03b4815e_tplv-k3u1fbpfcp-zoom-1.png

线程通信

线程间通信可以通过共享变量+wait()&notify()来实现

  1. Thread-0先获取到对象的锁,关联到monitor的owner,同步代码块内调用了锁对象的wait()方法,调用后会进入waitSet等待,Thread-1同样如此,此时Thread-0的状态为Waitting
  2. Thread2、3、4、5同时竞争,2获取到锁后,关联了monitor的owner,3、4、5只能进入EntryList中等待,此时2线程状态为 Runnable,3、4、5状态为Blocked
  3. 2执行后,唤醒entryList中的线程,3、4、5进行竞争锁,获取到的线程即会关联monitor的owner
  4. 3、4、5线程在执行过程中,调用了锁对象的notify()或notifyAll()时,会唤醒waitSet的线程,唤醒的线程进入entryList等待重新竞争锁

注意:

  1. Blocked状态和Waitting状态都是阻塞状态
  2. Blocked线程会在owner线程释放锁时唤醒
  3. wait和notify使用场景是必须要有同步,且必须获得对象的锁才能调用,使用锁对象去调用,否则会抛异常
  • wait() 释放锁 进入 waitSet 可传入时间,如果指定时间内未被唤醒 则自动唤醒
  • notify()随机唤醒一个waitSet里的线程
  • notifyAll()唤醒waitSet中所有的线程

Untitled

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
public class Threaded_communication {
static final Objectlock= new Object();
public static void main(String ...arg){
new Thread(()->{
synchronized (lock){
System.out.println("开始执行1");
try{
lock.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("继续执行核心逻辑1");
}
},"t1").start();

new Thread(()->{
synchronized (lock){
System.out.println("开始执行2");
try{
lock.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("继续执行核心逻辑2");
}
},"t2").start();

try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("开始唤醒");
synchronized (lock){
lock.notifyAll();
}
}
}

wait 和 sleep的区别

wait是Object的方法,sleep是Thread的方法

wait会立即释放锁,sleep不会释放锁

生产者消费者模型

包含一点java内部类的概念

https://cloud.tencent.com/developer/article/1675402

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
import java.util.LinkedList;

public class Productor_consummer {
public static void main(String... args) throws InterruptedException {
MessageQueue queue = new MessageQueue(2);

for (int i = 0; i < 3; i++) {
int id = i;
new Thread(
() -> {
queue.put(new Message(id, "值" + id));
},
"生产者" + i)
.start();
}

new Thread(
() -> {
while (true) {
queue.take();
}
},
"消费者")
.start();
}

// 消息队列被生产者和消费者持有
static class MessageQueue {
private LinkedList<Message> list = new LinkedList<>();

// 容量
private int capacity;

public MessageQueue(int capacity) {
this.capacity = capacity;
}
// 生产
public void put(Message message) {
synchronized (list) {
while (list.size() == capacity) {
System.out.println("队列已满");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.addLast(message);
System.out.println("生产消息:{}");
list.notifyAll();
}
}

public Message take() {
synchronized (list) {
while (list.isEmpty()) {
System.out.println("队列空了等待消费者");
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = list.removeFirst();
System.out.println("消费者消息:{}");
// 消费后通知生产者
list.notifyAll();
return message;
}
}
}

static class Message {
private int id;
private Object value;

public Message(int id, Object value) {
this.id = id;
this.value = value;
}
}
}

ReentrantLock 可重入锁

实例:

一个线程输出a,一个线程输出b,一个线程输出c,abc按照顺序输出,连续输出5次

可重入锁的原理:

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

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
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class demo_reentrantLock_2 {
public static void main(String... arg) {
AwaitSignal awaitSignal = new AwaitSignal(5);
// 构建三个条件变量
Condition a = awaitSignal.newCondition();
Condition b = awaitSignal.newCondition();
Condition c = awaitSignal.newCondition();

// 开启三个线程
new Thread(
() -> {
awaitSignal.print("a", a, b);
},
"a")
.start();

new Thread(
() -> {
awaitSignal.print("b", b, c);
},
"b")
.start();

new Thread(
() -> {
awaitSignal.print("c", c, a);
},
"c")
.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
awaitSignal.lock();
try {
a.signal();
} finally {
awaitSignal.unlock();
}
}

static class AwaitSignal extends ReentrantLock {
private int loopNumber;

public AwaitSignal(int loopNumber) {
this.loopNumber = loopNumber;
}

/**
*@paramprint输出的字符
*@paramcurrent当前条件变量
*@paramnext下一个条件变量
*/
public void print(String print, Condition current, Condition next) {
for (int i = 0; i < loopNumber; i++) {
lock();
try {
try {
current.await();
System.out.println(print);
} catch (InterruptedException e) {
System.out.println(e);
}
next.signal();
} finally {
unlock();
}
}
}
}
}

condition

死锁

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
public class DeadLock {
public static void main(String... arg) {
Beer beer = new Beer();
Story story = new Story();
new Thread(
() -> {
synchronized (beer) {
System.out.println("我有酒,给我故事");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (story) {
System.out.println("小王开始喝酒讲故事");
}
}
})
.start();
new Thread(
() -> {
synchronized (story) {
System.out.println("我有故事,给我酒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e);
}
synchronized (beer) {
System.out.println("老王开始喝酒讲故事");
}
}
})
.start();
}

static class Beer {}

static class Story {}
}

java内存模型(JMM)

volital 关键字

线程池

线程的资源很宝贵,不可能无限的创建,必须要有管理线程的工具,线程池就是一种管理线程的工具,java开发中经常有池化的思想,如 数据库连接池、Redis连接池等。

预先创建好一些线程,任务提交时直接执行,既可以节约创建线程的时间,又可以控制线程的数量。

操作系统课堂笔记

清华大学os网课笔记

操作系统的概述

  • 操作系统是一个控制程序

  • 管理应用程序,

  • 为应用程序提供服务

  • 杀死程序

  • 资源管理:管理外设,分配资源

  • 作为抽象层在物理层之上

  • 作为系统软件为应用软件提供支持

  • 面向应用程序的接口:shell

  • 面向计算机内部的接口:kernal

  • 硬件三大套:cpu 内存 磁盘

  • os kernel的特征:

  • *并发**:一段时间内多个程序同时运行

  • 并行*: 一个时间点内多个程序同时运行(多个cpu)

  • *共享**:同时访问,互斥共享

  • *虚拟**:cpu->进程,磁盘->文件,内存->地址空间。多道文件设计,让用户觉得自己独享整个计算机。

  • *异步**:程序的执行是走走停停的,向前推进的速度不可预知。到那时只要环境相同,运行的结果也要相同。

操作系统历史

  • 移动终端上linux应用广泛
  • cpu性能越越强
  • 多道程序设计
  • 分时调度:在于时钟会定时产生中断,每千分之一秒
  • 未来的发展趋势:多核多处理,分布式操作系统

操作系统结构

  • 微内核设计:尽然把内核服务放到用户空间。中断处理,消息传递 放到内核中。
    文件系统,网络协议放到用户空间,以服务的方式松耦合存在,而非函数调用的方式。
    代价是性能。

操作系统启动

  • bios->bootloader->os
  • disk:存放os
  • bios:基本io处理系统,开机检验外设
  • bootloader:加载os,让os从硬盘到内存中去
  • bios从一个特定的地址执行,cs:ip(cs:段寄存器,ip:指令寄存器)
  • bootloader一般放在disk的第一个主引导扇区(512字节)

操作系统中的事件:中断,异常和系统调用

  • 系统调用异步或同步,来源于应用程序,应用程序主动向os发出服务请求
  • 异常同步,来源于不良的应用程序,非法指令(内存出错)
  • 中断异步,来源于外设(鼠标的移动,键盘的输入),不同的硬件设备的计时器和网络的中断
  • 应用程序不能直接访问外设。内核是被信任的,内核统一的向程序暴露接口。

计算机体系结构/内存分层

  • 抽象:逻辑地址空间
  • 保护:独立地址空间
  • 共享:访问相同内容
  • 虚拟化:更多的地址空间
  • 主存:物理内存,接电从硬盘读数据到主存

地址空间

  • 物理地址空间—内存条代表的贮存,硬盘代表的磁盘
  • 逻辑地址空间—一个运行的程序所拥有的内存范围 (0,max)
  • cpu—mmu查找逻辑地址对应的物理地址,如果没找到就到内存中去找
    操作系统需要建立逻辑地址和物理地址的关系。
  • 操作系统的地址安全检测

连续内存分配

  • 空闲内存不能被利用
  • 外部碎片
  • 内部碎片

分区的动态分配

  • 首次适配(first fit):找到第一个满足 程序需求的连续空闲块
  • 优点*:简单
  • 缺点*:容易产生不合适的空闲块
  • 最佳匹配(best fit):使用最小的满足要求的空闲块
  • 优点*:简单
  • 缺点*:容易产生不合适的空闲块
  • 最差分配原则(worst fit):使用最大的满足要求的空闲快
  • *优点**:分配中等尺寸效果最好
  • *劣势**:

连续的内存分配算法

  • 压缩式
  • 交换式

非连续内存分配:分段

  • 难点在于逻辑地址到物理地址的映射
  • 操作系统维护段表

非连续内存的分配:分页

  • 划分物理内存大小—帧frame
    • 一个物理地址是一个二元数组(f,o)
    • f—帧号(F位,一共有2的F次方个帧)
    • o—帧内偏移位(s位,每帧有s的s次方字节)
    • 物理地址 2^s*f + o
  • 划分逻辑地址—页page
    • 一个程序的逻辑地址空间被划分为大小相等的页
    • 页内偏移的大小
  • 页表
    • 页表
  • 建立方案 转换逻辑地址为物理地址page to frames
  • cpu根据page number和 offeset在page table内寻址,之后根据找到的frame number 和offset去物理地址寻址
  • 快表TLB使用关联内存实现
  • 页表太大的问题:使用多级页表
    • 多级页表的最后一层才是真正的寻找物理地址
  • 反向页表
    *基于hash的方向页表:

虚拟内存

  • 覆盖技术:使用分时的方式用同一块内存空间,需要确定各个模块的覆盖关系。
  • 交换技术:操作系统管理程序,swap out吧一个进程的整个地址空间的内容包窜到外存中,
    而将外村中的某个进程的地址空间读入到内存中(swap in)。
    • 交换时机的确定:只有当内存空间不够或者有不够的危险时换出
    • 交换区的大小
    • 程序换入的重定位:换入换出在物理地址上不用在同一个地方,只需要更改页表,使得逻辑地址映射到同一个物理地址。

      虚存技术

  • 不是替换整个进程的内存空间,而是部分替换。

java中的重写与重载

重写

子类重写父类的方法时需要保持参数和返回值的一致性,否则编译器就会报错。
下面这个例子我们发现在circle类是属于Shape类对象的一个实例,在编译阶段只会检查Shape类中是否有testPoly这个方法。
而在运行阶段jvm则会访问circle实例上的testPoly方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class test{
public static void main(String args[]){
Shape circle = new Circle();
circle.testPoly();
}
}
class Shape{
public void testPoly(){
System.out.println("shape");
}
}
class Circle extends Shape{
//重写父类的方法 保持参数和返回值的一致性
public void testPoly(){
System.out.println("circle");
}
}

重写的规则(摘自互联网)

  • 参数列表与被重写方法的参数列表必须完全相同。

  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

  • 父类的成员方法只能被它的子类重写。

  • 声明为 final 的方法不能被重写。

  • 声明为 static 的方法不能被重写,但是能够被再次声明。

  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

  • 构造方法不能被重写。

  • 如果不能继承一个类,则不能重写该类的方法。

重载

重载的时候必须在类内部,其中 函数的参数列表必须不一样,不然会报错

1
2
3
4
5
6
7
public void test1(){
System.out.println("test1");
}
public int test1(int param){
System.out.println("test2");
return 2;
}

总结

类的重写是多态在父类与子类中的一种体现,
而类的重载是多态在一个类内部的一种体现

git 撤销修改的方法

git基本概念

  • 工作区: 工作目录
  • 暂存区: (stage).git文件夹下的index文件,也叫索引
  • 版本库: .git文件夹

撤销修改的方法

工作区,暂存区,版本库之间的关系

  • 当对工作区修改(或新增)的文件执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。

  • 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。

  • 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。

  • 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。

  • 当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。

  • 当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。

重拾java: jav中的集合(collection)

集合

Java的中的 集合框架包含两种类型的容器: 集合(collection),
图(map)。

###集合(collection)
collection有三种子类型:List,Set,Queue。 再是一些抽象是实现类。
最后是具体实现类。常用的有:ArrayList,LinkedList,HashSet等等、

###图(map)
遍历map的四种方法

  1. iterator 遍历
    collection接口要求所有的子类需要实现iterator()方法
    返回iterator,注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
    1
    2
    3
    4
    5
    6
    7
    8
    //通过 Iterator 遍历实现
    List<Integer> list = new ArrayList<Integer>();
    list.add(1);
    list.add(2);
    list.add(3);
    for(Iterator itr = list.listIterator();itr.hasNext();){
    System.out.println(itr.next());
    }

JVM程序计数器 VS OS程序计数器

学完了操作系统后在学到jvm时看到了程序计数器,那他们有什么关系呢?

jvm内存模型中的PC程序计数器 与 OS操作系统程序计数器 的关系

JVM程序计数器:

程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。- - 摘自《深入理解Java虚拟机》

OS程序计数器

为了保证程序(在操作系统中理解为进程)能够连续地执行下去,CPU必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作用,所以通常又称为指令计数器。在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的地址。当执行指令时,CPU将自动修改PC的内容,即每执行一条指令PC增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对PC加1。 当程序转移时,转移指令执行的最终结果就是要改变PC的值,此PC值就是转去的地址,以此实现转移。有些机器中也称PC为指令指针IP(Instruction Pointer)

总滴来说JVM中的程序计数器是对cpu的程序计数器的一种抽象他只是JVM内存空间中的一小块内存,并不是真正的物理寄存器

学习资源

Linux学习笔记(强悍值得一看)
链接:https://pan.baidu.com/s/1j_-dd0RSUejJFxFTfW6TgQ 提取码:fnsh

4份优质算法刷题笔记
链接: https://pan.baidu.com/s/1yYpZavbtpHGRJbv5zRtWyA 密码: qmi3

Vim从入门到精通 & Vim中文用户手册
链接:https://pan.baidu.com/s/1eUsJCQb41yG9QVsXVJ6DQQ 提取码:5f02

Redis6.x 入门到精通实战教程
链接:https://pan.baidu.com/share/init?surl=fpaHY1CwBG5KTHlWKv6ycg 提取码:c0ox

Git教程 中文PDF+视频
链接:https://pan.baidu.com/share/init?surl=HBInqhh0R6bd_tUlhDpA4Q 提取码:fh7i

C语言基础资料
链接: https://pan.baidu.com/s/1-f4d0DnJbny3-DMrs6smJA 提取码: 49e4

程序员英语词汇1700词
链接:https://pan.baidu.com/s/1L8nR-ci8iqkZLF61H7PbJw 提取码:gdhj

计算机专业必看书籍及视频教程
https://www.zhihu.com/question/438642229/answer/1841323980

MySQL基础入门到精通视频教程(内含MySQL34道面试题)
https://www.bilibili.com/video/BV1fx411X7BD

Java零基础教程视频(适合Java 0基础,Java初学入门)
https://www.bilibili.com/video/BV1Rx411876f

一二线城市知名 IT互联网公司名单(最新整理版)出炉!
https://www.lxlinux.net/7856.html

GitHub加速插件
链接:https://pan.baidu.com/s/1xukH6MrIuUKijrq0b9NV7A 提取码:7ag0

名校计算机专业学习资料
北大:https://github.com/lib-pku/libpku

清华:https://github.com/PKUanonym/REKCARC-TSC-UHT

浙大:https://github.com/QSCTech/zju-icicles

计算机基础知识学习笔记PDF版
链接: https://pan.baidu.com/s/1cf1k9FFVYudf-e-UATjO_A 提取码: quxu

操作系统学习笔记PDF版
链接: https://pan.baidu.com/s/1Kn36FLovgZA5vilJftUczg 提取码: gppe

图解网络(小林)
链接:https://pan.baidu.com/s/1doCAerxNO-3M1vqn820WNg 提取码:5l38

VMWare安装包及序列号:
链接: https://pan.baidu.com/s/1J34PLr0i2F7Nt3_aScdlrA 提取码: kydn

Linux内核及各发行版镜像:
链接:https://pan.baidu.com/s/1kyD2dcMOdDAYf5tmBEtn9w 提取码:2h9v

MobaXterm安装包:
链接:https://pan.baidu.com/s/1Wsv1BRQiNlk3Yl0bphpeCw 提取码:jut6

IDEA永久激活:
链接:https://pan.baidu.com/s/1Yfc5ft7E2t7IQWIGejFydw 提取码:aabb

Source Insight破解版:
链接:https://pan.baidu.com/s/1UdpMdFK_mVD1a7y5tak1sA 提取码:vkk5

java学习路线指南:
链接:https://github.com/liyupi/code-roadmap/blob/main/docs/roadmap/Java%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF.md

mall商城实战项目文档:
https://macrozheng.github.io/mall-learning/#/foreword/mall_foreword_02