-
Java 学习笔记
您对计算机(电脑)有了解吗?
-
计算机组成
-
硬件
- 键盘、鼠标、主机箱内的CPU 、硬盘、内存条等,但是这些硬件需要在软件的驱动下才会正常的工作,才有意义
-
软件
而软件又分为
系统软件
和应用软件
-
系统软件
直接和硬件交互的软件叫系统软件,例如 win7、win8、Linux、mac等操作系统
-
应用软件
通常在运行在系统软件上,也就是在系统软件上开发的软件,例如 QQ 、微信等,我们一般说的开发一般是应用软件,因为系统软件需要更高的技术,而应用软件开发是需要掌握
编程语言
,使用编程语言规则开
发出应用软件
-
-
-
为什么会提问对计算机是否了解?
- 因为在以后的开发应用软件编程中会遇到问题,例如性能调优等,这就需要对计算机了解,定位问题,是硬件问题还是程序问题,硬件问题是哪些硬件约束性能还是程序编写的问题,这个很关键。这些都是自行实践体会,每个人都有自己不同的经验心得
常用的 DOS 命令
如何打开 DOS 命令行和关闭 DOS 命令行,自行百度了解,不难,如果不会搜索,那可以参考搜索 "如何打开 DOS 命令行和关闭 DOS 命令行"
以下的命令在小米笔记本电脑
和win10 操作系统
中执行
-
打开 DOS 命令行
键盘左下角 Fn 和 Alt 之间的 win 键 + R ,打开一个运行界面,输入cmd ,回车就可以打开 DOS 命令行,或者直接在电脑左下角搜索 cmd ,回车就可以打开
-
关闭 DOS 命令行 exit
输入
exit
命令退出当前 DOS 命令行窗口 -
清屏 cls
输入
cls
命令清楚屏幕内容,达到干净效果 -
复制
鼠标左键选中内容为复制,默认配置
-
粘贴
鼠标右键点一下则粘贴,默认配置
-
列出当前所有的目录和文件
输入
dir
,列出当前目录和文件默认一开始打开 DOS 窗口的目录 :C:\Users\18816> ,跟 Linux 的有点类似
C:\Users\18816>dir 驱动器 C 中的卷是 Windows 卷的序列号是 3266-86B9 C:\Users\18816 的目录 2020/07/21 20:45 <DIR> AppData 2020/03/12 08:43 <DIR> Contacts 2021/01/25 21:46 <DIR> Desktop 2021/01/01 11:21 <DIR> Documents 2020/12/13 11:17 <DIR> Downloads 2020/03/12 08:43 <DIR> Favorites 2020/03/12 08:43 <DIR> Links 2020/08/26 23:48 <DIR> Music 2021/01/31 09:32 <DIR> OneDrive 2020/07/21 22:57 <DIR> Oracle 2020/12/20 01:10 <DIR> Pictures 2020/04/19 22:16 <DIR> Postman 2020/03/12 08:43 <DIR> Saved Games 2020/03/12 08:43 <DIR> Searches 2020/01/05 22:28 <DIR> UIDowner 2020/10/01 08:50 <DIR> Videos 2020/08/15 18:13 <DIR> VirtualBox VMs 2020/05/17 18:56 <DIR> vue 9 个文件 18,569 字节 39 个目录 148,148,510,720 可用字节
-
切换目录
输入
cd 目录路径
切换到该目录下我要切换到 Music 目录下,当前我在 C:\Users\18816 位置,直接输入 cd Music 就可以切换进入 Music
C:\Users\18816>cd Music C:\Users\18816\Music>
-
路径问题
-
相对路径
相对于当前位置路径来说
.
表示当前路径..
表示上一级目录像前面说的 cd 切换目录命令,cd Music 是相对于当前目录 C:\Users\18816,而 cd . 还是切换到当前目录 ,C:\Users\18816\Music>cd ..
C:\Users\18816> 表示切换到上一级目录了
C:\Users\18816\Music>cd .. C:\Users\18816>cd . C:\Users\18816>
-
绝对路径
一般会有盘符 C:\Users\18816 这个是绝对路径,如果切换到 Music 目录,跟上面有什么不一样,一般要带上盘符
C:\Users\18816>cd C:\Users\18816\Music C:\Users\18816\Music>
-
-
盘符切换
直接盘符 :就可以切换到对应的盘目录,下面切换到 d 盘
C:\Users\18816\Music>d: D:\>
Java 语言特性
免费、开源 、纯面向对象、跨平台
-
简单性
相对于 C++ 而言简单,
C++ 有指针, Java 屏蔽指针,个人理解 C++ 的指针跟 Java 的变量或者引用有点类似(但不完全),指向内存的一块空间
C++ 多继承,Java 单继承
Java 底层是 C++ 实现,不是 C 语言
-
面向对象
纯面向对象,符合人的思维
-
可移植性
跨平台,一次编译到处运行
这里需要讲一下,可移植性的根本原因在于 Java 虚拟机
Java 虚拟机有不同版本 Windows、Linux 版本等,但是不同版本的虚拟机都能识别 .class 文件,将它转成二进制文件,操作系统才识别运行,所以引出 JDK 、JRE、JVM 关系
JRE 包含 JVM
JDK 包含 JRE
所以 JDK>JRE>JVM
当编译好的 .class 文件(平常看到的 .jar 就是 .class 文件压缩包)不管放到哪个操作系统都能被识别运行
-
多线程
提高效率,类似于多人搬砖比一人搬砖快
-
健壮性
Java 自动回收垃圾 ,GC 机制,开出一块内存,不用担心内存占用,GC 机制会回收
-
安全性
Java 程序的编译与运行
Java 程序执行包括编译和运行两个阶段
这里的编译运行都是在完整的类名下(相对路径)包名+类名
-
编译
-
源文件 .java ,源文件的代码叫源码
-
源文件 .java 变成 .class(字节码文件) 的过程叫编译,.class 不是纯粹的二进制文件,无法直接在操作系统运行
-
编译的任务是 : 检查 Java 源程序是否符合 Java 的语法,如果符合就会生成对应的 .class 文件(class 类才会有),否则无法生成 .class 文件,编译报错。
-
而在 Java 中,编译源文件依赖 jdk 自带的 javac.exe 工具,在 jdk1.8.0_111\bin 目录下,在 DOS 窗口 执行 javac xxx.java 就会得到对应的 xxx.class(符合 Java 语法规则,只有一个 class 类下)
-
一个 .java 文件可能生成多个 .class文件 , .java 文件名随意
当一个 .java 文件如下所示,A 类,B类 ,C类则会生成 A.class ,B.class , C.class
class A {} class B {} class C {}
当类中使用 public 修饰时,源文件 .java 的名字只能是 A.java
A 类,B类 ,C类则会生成 A.class ,B.class , C.class
public class A {} class B {} class C {}
-
-
运行
-
加载执行字节码文件的过程
-
在 Java 中,运行依赖 jdk 自带的 java.exe 工具,在 jdk1.8.0_111\bin 目录下
-
例如 A类的字节码文件为 A.class 运行 A 类则在 DOS 命令行下输入 java A 就可以,无需输入 java A.class(错误)
-
程序的入口是 main 方法,如果运行的程序不含 main 方法,则会运行失败
D:\data\jdkStudy>javac A.java D:\data\jdkStudy>java A 错误: 在类 A 中找不到 main 方法, 请将 main 方法定义为: public static void main(String[] args) 否则 JavaFX 应用程序类必须扩展javafx.application.Application D:\data\jdkStudy>
-
运行过程
输入 java A ,java.exe 命令启动一个 Java 虚拟机(JVM),JVM 会启动一个类加载器 ClassLoader(加载字节码文件),
ClassLoader 会在硬盘上搜索一个叫 A.class 的文件,找到 A.class 文件后则加载该字节码文件到 JVM 中,JVM 将字节码文件解释成二进制文件,然后操作系统执行二进制和底层硬件平台交互
-
引用: C 语言的速度比 Java 快的原因
根据上面 Java 运行过程,对比于 C 语言,直接编译成二进制文件,无需经过编译成字节码文件这一个过程,这也说明其他高级语言也是变成二进制才能被操作系统识别执行与硬件交互
-
DOS 命令找不到 java 命令的根本原因
-
从当前目录下搜索,找不到就会去 path 配置下搜索,没有则报错
配置之前
D:\data\jdkStudy>javac 'javac' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 D:\data\jdkStudy>
path = xxxx;C:\Program Files\Java\jdk1.8.0_111\bin
配置之后
D:\data\jdkStudy>javac 用法: javac <options> <source files> 其中, 可能的选项包括: -g 生成所有调试信息 -g:none 不生成任何调试信息 -g:{lines,vars,source} 只生成某些调试信息 -nowarn 不生成任何警告 -verbose 输出有关编译器正在执行的操作的消息 -deprecation 输出使用已过时的 API 的源位置 -classpath <路径> 指定查找用户类文件和注释处理程序的位置 -cp <路径> 指定查找用户类文件和注释处理程序的位置 -sourcepath <路径> 指定查找输入源文件的位置 -bootclasspath <路径> 覆盖引导类文件的位置 -extdirs <目录> 覆盖所安装扩展的位置 -endorseddirs <目录> 覆盖签名的标准路径的位置 -proc:{none,only} 控制是否执行注释处理和/或编译。 -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程 -processorpath <路径> 指定查找注释处理程序的位置 -parameters 生成元数据以用于方法参数的反射 -d <目录> 指定放置生成的类文件的位置 -s <目录> 指定放置生成的源文件的位置 -h <目录> 指定放置生成的本机标头文件的位置 -implicit:{none,class} 指定是否为隐式引用文件生成类文件 -encoding <编码> 指定源文件使用的字符编码 -source <发行版> 提供与指定发行版的源兼容性 -target <发行版> 生成特定 VM 版本的类文件 -profile <配置文件> 请确保使用的 API 在指定的配置文件中可用 -version 版本信息 -help 输出标准选项的提要 -A关键字[=值] 传递给注释处理程序的选项 -X 输出非标准选项的提要 -J<标记> 直接将 <标记> 传递给运行时系统 -Werror 出现警告时终止编译 @<文件名> 从文件读取选项和文件名 D:\data\jdkStudy>
标识符
命名规则规范
-
规则
- 不遵守规则,就会编译失败
-
合法的标识符由数字、字母、下划线
_
、美元符$
(只能包含这四种,web@ 是错的 ,web_ 是对的) - 不能以数字开头(1234A 是错的 ,A1234 是对的)
- 严格区分大小写(A 与 a 是可以同时存在的)
- 不能使用关键字作为标识符
-
规范
-
不遵守,虽然编译成功,但是给人不好阅读体验
-
见名知意
-
类名、接口名首字母大写,后面每个单词首字母大写
-
变量名、方法名首字母小写,后面每个单词首字母大写
-
常量名全部大写,每个单词之间使用下划线 _ 连接
-
驼峰写法 :UserService
-
关键字
- 关键字多写多看就熟悉了 :public 、class 、void 等
变量
-
字符和字符串区别
字符使用单引号表示,而且单引号内只能存放一个字符(内存决定),例如 'A'
字符串使用双引号 "" 表示,字符串由多个字符组成,例如 "ABC"
public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
-
变量定义
变量本质上来说是内存中的一块空间,这块空间有数据类型,有名字,有字面值
- 数据类型 :决定在使用时开辟对应的内存,比如 int 4字节 ,long 8 字节,不同数据类型,初始化时分配的内存空间大小不一样
- 名字 :变量名
- 字面值 :也就是这块内存存储的值
- 字面值与数据类型不一样,编译报错
-
通常访问一个变量的两种访问方式
- 读取变量中保存的具体数据 get
- 修改变量中保存的具体数据 set
-
变量声明赋值才可以访问
-
赋值是修改变量对内存的指向
int i =100 ,变量i指向 100 这块内存空间
i = 200 ,把 i 指向 100 这块内存修改为指向 200 这块内存
-
-
变量作用域
有效范围 : {} 大括号范围内有效
根据变量声明的位置分类
-
成员变量(方法体外,类体内声明的变量)
- 静态成员变量
- 普通成员变量
- 局部变量(方法内声明的变量)
-
成员变量(方法体外,类体内声明的变量)
数据类型
数据类型 : 指导 JVM 在运行程序的时候给该数据分配多大的内存空间
-
分类
-
基本数据类型
-
引用数据类型
-
类
-
接口
-
数组
...
-
-
-
关于基本数据类型
-
整数型
- byte 、short 、int 、long
-
浮点型
- float 、double
-
布尔型
- boolean
-
字符型
- char
-
整数型
-
字符串不属于基本数据类型,字符属于基本数据类型
-
八种基本数据类型各自占用内存空间大小
数据类型 | 占用空间(单位:字节) | 默认值 | 取值范围 |
---|---|---|---|
byte | 1 | 0 | -128~127 |
short | 2 | 0 | -32768 ~32767 |
int | 4 | 0 | |
long | 8 | 0 | |
float | 4 | 0.0f | |
long | 8 | 0.0d | |
boolean | 1 | false | true/false |
char | 2 | '\u0000' | 0-65535 |
-
进制问题需要弄清楚,一般就是 二进制 、十进制 、十六进制使用的比较多,相互转换原理都是需要了解
进制 : 数据表示的一种方式,二进制满2进1,十进制满10进1 ,十六进制满16进1
-
字节 (byte)
1byte = 8 bit ,一个比特位(bit)表示一个二进制位 1 或者 0
1KB = 1024Byte
IMB = 1024KB
1GB = 1024MB
1TB = 1024GB
数据类型 char
-
字符由 '' 表示
-
一个字符两个字节,可以表示一个占用两字节的中文
数据类型转义字符
- \ 表示转义字符
- char a = 'n', a 的值为 n ,而 char a = '\n' ,a 的值为换行符,\具有转义功能 ,跟 n 一起变成换行符
- \t 制表符 与 空格不一样的 ASCII 码
- \显示反斜杠字符
整数型
Java 中的整数型字面值,例如 123,324等默认是 int 类型的数据,当出现赋值给其他类型的变量时,需要考虑类型转换
- 十进制表示 10 ----> 输出 10 进制的 10
- 八进制表示,前面有一个 0 ----> 010 输出10 进制的 8
- 十六进制表示,前面有一个 0x ----> 0x10 输出十进制的 16
long i = 123 ,这句话解释:int 类型的字面 123 赋值给 long 类型的变量 i ,long 类型占用 8 字节 ,int 占用 4 字节,int 类型转换成 long 类型属于小容量转大容量,类型自动提升,是允许的
long x = 2147483648 编译错误,计算机将 2147483648 首先当作 int 类型处理,而 2147483648 超过了 int 类型取值范围
long y = 100L , int a = y , 编译报错 : 100L 字面值为 long 类型,a 变量为 int 类型,long 类型占用 8 字节 ,int 占用 4 字节,大容量不能直接转成小容量,需要强制转换,但是会损失精度
int b = (int)y 这样才能正确编译
强转原理
原始数据 :00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100
强转后的数据:00000000 00000000 00000000 01100100
将左边的二进制数据砍掉
但是有一个特例 : byte a = 50 ,byte b =127(编译成功) ,byte c = 128 (编译失败),不超出 byte 的取值范围是可以成功编译的
注意理解原码补码反码?
byte ,short,char 类型的字面值不超过取值范围,直接是可以赋值,不会报错,但是超过需要强转
浮点型
单精度 float,双精度 double,默认 double
float f = 1.1;编译报错,需要强转 float f = (float)1.1 或者 float f = 1.1f||float f = 1.1F
字符编码
计算机二进制跟文字等的对应关系
ISO-8859-1 ----> latin-1 (不支持中文)
GB2312(仅支持简体)< GBK < GB18030(容量排序)
大五码(big5)支持繁体中文
Unicode --> UTF-8、UTF-16等支持所有文字,容量大
需要记住的以下几个字符
- 'a' ---> 97 ---> 01100001
- 'A' ---> 65 --->
- '0' ---> 48 --->
运算符
算术运算符
+(加)、-(减)、*(乘)、/(除,取商值)、%(求余,取模,结果为余数)、++(自加1)、--(自减1)
int a =100
a++ 与 ++a 没差别,但是
int b = a++ 和 int b = ++a 是有差别的
前者 b = 100,a =101,
后者 b = 101,a=101
当有赋值表达式时 ,++ 或者 -- 在前面表示先进行 ++ 或者 -- 运算再赋值,++ 或者 -- 在后面表示先赋值后进行运算
int a = 100;
System.out.println(a++); // 结果为 100,但后来 a 的值变为 101,自加 1 ,因为 a++ 相当于传参 int x = a++
System.out.println(a--); // 结果为 101,但后来 a 的值变为 100,自减 1 ,因为 a-- 相当于传参 int x = a--
System.out.println(++a); // 结果为 101,但后来 a 的值变为 101,自加 1 ,因为 ++a 相当于传参 int x = ++a
System.out.println(--a); // 结果为 100,但后来 a 的值变为 100,自减 1 ,因为 --a 相当于传参 int x = --a
关系运算符
关系运算符结果一定是布尔类型 true/false
>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、==(等于)、!=(不等于)
原理:
int a = 10;
int b = 10;
a>b 比较的是 a 中保存的 10 这个值和 b 中保存的 10 这个值大小
赋值运算符
= (赋值号)
逻辑运算符
&(逻辑与)、|(逻辑或)、!(逻辑非)、^(逻辑异或)、&&(短路与)、||(短路或)
& : 两边表达式为 true ,结果才为 true
| : 两边表达式其中一个 true,结果为 true
! : !false 为 true ,!true为false
^ : 两边表达式结果不一样为 true
字符串连接
"+"
- 当 + 两边都是数字时,做加法运算
- 当 + 两边其中一个是字符串时,做连接
三元运算/三目运算
布尔表达式 ?表达式1:表达式2(布尔表达式位 true ,结果为表达式1,false 为表达式2)
多线程
-
进程
- 进程是一个应用程序(一个进程是一个软件)
-
线程
-
线程是一个进程中的一个执行单元,一个进程可以启动多个线程
-
以 java HelloWorld 为例
-
java HelloWorld 会先启动 JVM ,JVM 就是一个进程,JVM 再启动一个主线程调用 main 方法,同时再启动一个垃圾回收线程负责守护,回收垃圾。最起码,现在的 Java 程序中至少有两个线程并发,一个是垃圾回收线程,另一个是 main 方法的主线程
-
Java 中多个线程共享堆内存和方法区内存,但是线程之间的栈内存不共享,一个线程一个栈
- 假设启动10 个线程,会有 10 个栈空间,每个栈和每个栈之间互不干扰,相互独立,各自执行各自的,这就是多线程并发
- 例如火车站是一个进程,售票口是一个线程,我在售票窗口1 买票,你在售票窗口2买票,我不等你,你不等我,互不干扰,这样多线程并发提高效率
-
思考问题 : 根据以上知识,当 main 方法结束后,是不是有可能程序还没结束?
-
是的,main 方法只是 JVM 进程中的一个线程,线程之间互不影响,而且同时 JVM 也启动了一个垃圾回收看护线程(或者还有其他线程),当 main 方法结束时,只是 main 方法这个主线程结束,其他线程还没结束,程序还没结束,除非是 关闭 JVM ,进程结束,程序也结束。
-
思考问题 :对于单核CPU ,真的可以做到真正多线程并发吗?
-
单核的 CPU 表示只有一个大脑
-
不能够做到真正的多线程并发,但是可以做到给人一种"多线程并发"的感觉
-
在某个时间点上只能处理一个事情,但是由于 CPU 的处理速度极快,多个线程之间频繁切换执行,给人一种感觉,多个事情同时在做:
线程A :播放音乐
线程B :运行游戏
CPU 在 线程 A 和线程 B 之间切换很快,对我们来说就好像同时在播放音乐和运行游戏,但实际上对 cpu 来说某个时间要么播放音乐,要么运行游戏
-
-
对于多核的 CPU 电脑来说,可以做到真正的多线程并发
-
例如 4 核的 CPU 表示同一个时间点上,可以真正有四个进程并发执行
-
-
Java 中实现线程的两种方式
- 编写一个类,直接继承 java.lang.Thread ,重写 run 方法
package com.yuanhy;
/**
* @author yuanhy
* @date 2021-03-01 22:46
*/
/**
* 实现线程的第一种方式,直接继承 Thread ,必须重写 run
*
* 启动线程,调用 start() 方法
*/
public class ThreadTest01 {
public static void main(String[] args) {
//这里是 main 方法,这里的代码属于主线程,在主栈中运行
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
/**
* start 方法作用 :启动一个分支线程,在 JVM 中,开辟一个新的栈空间,这段代码开辟新的栈空间后就会瞬间结束,
* 这时候线程就启动成功了,启动成功后的线程就会自动调用 run 方法,并且 run 方法在分支栈的栈底部(压栈,先进后出),
* main 方法在主线程底部,run 和 main 是平级的
*/
myThread.start();
//这里的代码运行在主线程中
/**
* myThread.start() 和 myThread.run() 区别
*
* myThread.run() 不会开辟新的栈空间,还是在主线程中主栈中,按照代码从上而下执行顺序,先执行 myThread.run() 再往下执行
*/
//myThread.run();
for (int i = 0; i < 50; i++) {
System.out.println("主线程 :"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//编写程序,这段程序运行在分支线程中(分支栈)
for (int i = 0; i < 50; i++) {
System.out.println("分支栈(分支线程) :"+i);
}
}
}
-
线程生命周期
- 新建状态 (new Thread 实例化一个线程对象)
- 就绪(调用 start 方法后进入就绪状态,说明当前线程具有抢夺 cpu 时间片权利,CPU 时间片就是执行权,抢到执行权就可以运行 run 方法,start 方法前面说了开辟新的栈空间)
- 运行(运行 run 方法)
- 阻塞(但 run 方法被阻住运行时,进入阻塞状态,放弃执行权,没有执行就会进入就绪状态,重新抢夺 cpu 时间片)
- 结束(run 方法正常运行结束)
-
获取当前线程名
-
默认 Thread-0 开始,第二个 Thread-1,
-
Thread thread = new Thread()
threaf.getName() // 获取线程名
thread.setName() //设置线程名
-
-
获取当前线程对象
-
Thread.currentThread();
-
sleep 面试题
- 阻塞的是当前线程(记住是当前线程)
- sleep 是静态方法,让当前线程 main 进入睡眠状态,对 myThread 没影响,虽然是 myThread.sleep(1000*5)
public class ThreadTest01 {
public static void main(String[] args) {
//这里是 main 方法,这里的代码属于主线程,在主栈中运行
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
/**
* start 方法作用 :启动一个分支线程,在 JVM 中,开辟一个新的栈空间,这段代码开辟新的栈空间后就会瞬间结束,
* 这时候线程就启动成功了,启动成功后的线程就会自动调用 run 方法,并且 run 方法在分支栈的栈底部(压栈,先进后出),
* main 方法在主线程底部,run 和 main 是平级的
*/
myThread.start();
//这里的代码运行在主线程中
/**
* myThread.start() 和 myThread.run() 区别
*
* myThread.run() 不会开辟新的栈空间,还是在主线程中主栈中,按照代码从上而下执行顺序,先执行 myThread.run() 再往下执行
*/
//myThread.run();
// sleep 是静态方法,让当前线程 main 进入睡眠状态,对 myThread 没影响,虽然是 myThread.sleep(1000*5)
myThread.sleep(1000*5);
////5秒之后才执行
for (int i = 0; i < 50; i++) {
System.out.println("主线程 :"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//编写程序,这段程序运行在分支线程中(分支栈)
for (int i = 0; i < 50; i++) {
System.out.println("分支栈(分支线程) :"+i);
}
}
}
-
run 方法没有抛异常,只能 try ... catch
-
run 方法在父类没有抛出任何异常,因此子类不能比父类抛出更多异常
-
终止线程的睡眠
-
不是中断线程执行
-
Thread thread = new Thread()
thread.interrupt();
interrupt 中断此线程,哪个线程对象调用,就中断哪个线程,这种中断方法依靠 java 异常机制
-
-
强行终止线程执行
- 调用 stop (已过期,不建议使用)
- 缺点 : 直接终止线程,数据容易丢失,不建议使用,类似突然断电
-
优雅的终止线程执行
- 设置 boolean 类型标志 boolean run = true; ,改变 run 状态
package com.yuanhy;
/**
* @author yuanhy
* @date 2021-03-07 11:24
*/
public class ThreadTest01 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.setName("t");
thread.start();
//模拟 5 秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myRunnable.run = false;
}
}
class MyRunnable implements Runnable{
boolean run = true;
public void run() {
for (int i = 0; i < 10; i++) {
if(run) {
System.out.println(Thread.currentThread().getName() + " ----> " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
return;
}
}
}
}
-
线程的调度
-
常见的线程调度模型
-
抢占式调度模型
哪个线程的优先级比较高,抢到 CPU 时间片概率比较高一些,java 就是采用抢占式调度模型(例子 :vip 的线程优先级级高一点,所以抢到的概率比较高)
-
均分式调度模型
平均分配 CPU 时间片,每个线程占有的 CPU 时间片时间长度一样
-
-
哪些方法和线程调度有关系?
-
设置线程优先级,实例方法
public final void setPriority(int newPriority)
//最小优先级
public final static int MIN_PRIORITY = 1;
//默认优先级
public final static int NORM_PRIORITY = 5;
//最高优先级
public final static int MAX_PRIORITY = 10;
-
获取线程优先级,实力方法
public final int getPriority() {
return priority;
} -
让位方法
暂停当前正在执行的线程对象,并执行其他线程
public static native void yield() 静态方法
不是阻塞 ,让线程从运行状态返回到就绪状态,还可能再抢到执行权
-
join 方法
等待该线程死亡,直到调用 join 方法的线程对象执行完毕结束后,才开始执行其他线程
在主线程中,加入了myThread.join(); 把谁加入了就要等谁。异步
同时join还有join(millis)方法,能够加入等待时间,效果上相似sleep,可是仍是有实际区别的。
join底层是wait方法,因此它是会释放对象锁的,而sleep在同步的方法中是不释放对象锁的,只有同步方法执行完毕,其余线程才能够执行。
public final void join() throws InterruptedException
用于合并线程
-
-
-
多线程并发环境下,数据安全问题
-
重点
-
什么时候数据在多线程并发的环境下会存在安全问题?
三个条件
- 多线程并发
- 有共享数据
- 共享数据有修改行为
-
怎么解决多线程并发安全问题?
线程同步机制
线程排队执行,一个一个来(不能并发),用排队执行解决线程安全问题,这种机制称为线程同步机制
会牺牲一些效率,但是安全性优先
-
线程同步模块涉及两个专业术语
-
异步编程模型
线程 t1 和线程 t2 各自执行,t1 不管 t2 , t2 不管t1,谁也不需要等谁,属于多线程并发
-
同步编程模型
线程 t1 和线程 t2,在 t1 线程执行的时候, t2 必须等待 t1 执行完成的时候再执行或者 t1 必须等待 t2 执行完成的时候再执行,线程之间发生等待关系,属于线程同步
-
-
线程同步机制
-
语法
同步代码块,小括号里传的值很关键,是多线程并发共享的数据对象
synchronized(){
}
假设有 t1,t2,t3,t4,t5 5个线程,只希望 t1,t2,t3 线程排队执行 , t4,t5 线程不需要排队执行,怎么做?只需要在小括号中写 t1,t2,t3 共享的对象,而这个对象对 t4,t5 是不共享的,这个对象就是所谓的锁
Java 语言中,每个对象对应一把锁, 100 个对象对应 100把锁,一个对象一把锁
-
原理
1、假设 t1 线程 和 t2 线程并发,开始执行以下同步代码块的时候,会有一先一后的情况
synchronized(){
}
2、假设 t1 先执行,遇到了 synchronized ,这个时候自动找小括号里面的
需要排队的线程的共享对象
的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行的过程中一直都是占有这把锁的,直到同步代码块程序执行结束,才会释放这把锁3、假设 t1 已经占有这把锁,此时的 t2 也遇到 synchronized ,也会去自动找小括号里面的
需要排队的线程的共享对象
的对象锁,结果这把锁被 t1 占有,t2 只能在同步代码块外面等待 t1 执行结束,并且归还这把锁,此时 t2 才会占有这把锁,直到同步代码块程序执行结束这样就达到了线程排队
这里需要注意的是 : 这个共享对象一定要选好,一定是需要排队执行的线程所共享的对象
-
-