一、复习
1.1 哈希表原理
1、哈希表(HashMap)的底层是由数组 + 单链表 + 红黑树 组成
2、哈希表中使用数组的目的:根据下标访问操作的效率高,时间复杂度是O(1)
。这里的数组是散列存储,根据 key 的哈希值来计算索引位置。index = hash(key)
& (table.length-1)
3、万一两个或多个不同的(key,value)被映射到同一个 index 位置,称为哈希冲突。当哈希冲突的多个(key,value)要在同一个 table[index]位置存储的话,必须使用单链表或红黑树。
4、当 table[index]下的(key,value)的数量在 8 以下,使用 单链表。当 table[index]下的(key,value)的数量 达到 8 时,还要看 table.length 是否达到 64,如果达到 64,就会把 table[index]下单链表转为红黑树。
5、哈希表中的数组长度一定是 2 的 n 次方,每次扩容也是 2 倍扩容,保证一直是 2 的 n 次方。默认的初始长度为 16。数组的扩容有 2 种情况:
- 正常情况:size >= threshold(size 是哈希表中所有键值对数量,不是某一个 table[index]桶的数量) 。threshold = table.length * loadfactor(loadfactor 的默认值是 0.75)。
- 特殊情况:当 table[index]下的(key,value)的数量 达到 8,table.length 未达到 64,现在要往 table[index]位置继续添加(key,value),此时哈希表会扩容,试图分散 table[index] 的 键值对。
6、当 table[index]下的树的结点数量 小于等于 6 ,此时继续 put,触发扩容,会进行反树化。当 table[index]下的树的结点数量 比较少,此时 remove 时,遇到根结点、根的左结点、根的右结点、根的左左结点之一出现 null,会进行反树化。
7、key 的类型通常需要重写 hashCode 和 equals 方法,它们一起重写时要保证:
- 两个“相等的 equals 为 true”的 key 对象,hashCode 一定是相同的
- 两个“不相等的 equals 为 false”的 key 对象,hashCode 可能相同,可能不同
- 两个 hashCode 不同的 key 对象,此时它们 equals 结果一定是 false
在哈希表结构中,先看 key 的 hashCode,如果 hashCode 不同,可能散列到不同的位置,也可能散列到相同的位置,但是此时无论它们在不在一个位置,都不需要调用 equals 方法了。如果 hashCode 相同,再次调用 equals 确定 key 是否重复。
1.2 Lambda 表达式
1、Lambda 表达式的作用
Lambda 表达式是一种语法糖,用于简化 匿名内部类实现函数式接口的代码。函数式接口是有且只有 1 个抽象方法必须重写。函数式接口通常有@FunctionalInterface 注解标记。
2、函数式接口的代表们
(1)Comparator<T>
,抽象方法:int compare(T t1, T t2)
(2)Consumer<T>
,抽象方法: void accept(T t)
有参无返回值
(3)Predicate<T>
,抽象方法:boolean test(T t)
有参有返回值,返回值类型是 boolean
(4)Function<T,R>
,抽象方法: R apply(T t)
有参有返回值
- Operator 结尾,T
apply(T t)
,参数类型与返回值类型相同 - 包含 Unary,参数只有 1 个,且参数类型与返回值类型相同
- 包含 Binary 或 Bi,参数有 2 个
(5)Supplier<T>
,抽象方法:T get()
无参有返回值
.....
3、Lambda 表达式的语法格式
(形参列表) -> {方法体;}
- 当形参的类型可以通过泛型自动推断的时候,类型可以省略
- 当参数只有 1 个,且类型省略的情况下,()可以省略
- 当{ 方法体} 只有 1 个语句时, {} 和这个语句的; 也可以省略,如果这个语句还是 return 语句,那么 return 也得省略。
4、方法引用
方法引用的作用是简化部分 Lambda 表达式。语法格式:
- 类名::方法名
- 对象名::方法名
- 类名::new
当 Lambda 表达式的{方法体}只有 1 个语句,且是调用一个类的静态方法或一个对象的实例方法或在 new 一个对象,且调用方法用到的对象或参数全部来自于 Lambda 表达式的(形参列表),没有额外数据参与,那么就可以使用方法引用。
1.3 StreamAPI
第一步:创建 Stream
- 针对数组:
Arrays.stream(数组)
- 针对 Collection 系列集合:集合.
stream()
Stream.of(元素列表)
Stream.generate (...)
或Stream.iterate(...)
第二步:加工处理。这些方法的返回值类型一定 Stream
- 过滤:filter
- 去重:distinct
- 跳过:skip
- 限制几个:limit
- 排序:sort
- 查看:peek
- 映射:map 和 flatMap
第三步:终结。这些方法的返回值类型一定不是 Stream
- 匹配:allMatch、anyMatch、noneMatch
- 最大值最小值:max、min
- 统计数量:count
- 遍历:forEach
- 收集:collect 配合 Collectors 工具类
二、多线程
2.1 多线程的概念
程序(Program):为了实现某个功能,或者为了完成某个任务,采用一种编程语言(可能 Java、可能是 C 等)编写的一段代码,然后被编译器或解释器处理为一组指令,这组指令可以被 CPU 执行。这个程序如果还未启动,它只占用硬盘的存储空间,不会占用内存等其他资源。
进程(Process):应用程序一旦启动,操作系统 OS 就会给它创建一个进程,因为需要管理这个程序占用的各种资源,例如:内存资源,IO 资源,网络资源等。每一个进程有自己唯一的 ID。每一个进程是互相独立的。进程是操作系统分配和管理资源的最小单位。同一个程序被启动 2 次,会有 2 个进程。
线程(Thread):一个应用程序至少有一条执行路径,即至少有 1 个线程。很多应用程序会同时拥有多条同时执行的执行路径,就称为多线程程序。
串行(serial):一个任务完成再完成下一个
并行(parallel):多个任务同时在多个 CPU 中一起执行
并发(concurrent):一个 CPU 快速的在多个任务之间切换
2.2 如何编写多线程的程序
共有 4 种方式:
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口(后期讲解)
- 使用线程池(后期讲解)
2.2.1 继承 Thread 类
步骤:
编写一个类(有名或匿名)继承 Thread 类
必须重写 public void
run()
方法,重写的快捷键 Ctrl + o- 你想让这个线程干什么,就在 run 方法里面写什么
创建这个线程类的对象
调用线程类对象的 start 方法,千万不要直接调用 run 方法。
package com.atguigu.thread;
public class MyThread extends Thread{
@Override
public void run() {
//例如:我想让一个线程,帮我打印1-100的偶数
for(int i=2; i<=100; i+=2){
try {
Thread.sleep(10);//10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("偶数even:" + i);
}
}
}
package com.atguigu.thread;
public class TestMyThread {
//测试多线程的代码,不要用JUnit,否则会有很多问题
//main方法也是一条执行路径,是主要的执行路径,称为主线程
public static void main(String[] args) {
MyThread my = new MyThread();
// my.run();//错误,这个无法实现多线程并发执行
my.start();
//例如:我想让主线程,帮我打印1-100的奇数
for(int i=1; i<=100; i+=2){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("奇数odd:" + i);
}
}
}
2.2.2 实现 Runnable 接口
步骤:
编写一个类(有名或匿名)实现 Runnable 接口
必须重写 public void
run()
方法,重写的快捷键 Ctrl + I- 你想让这个线程干什么,就在 run 方法里面写什么
创建这个线程类的对象,例如:my
创建了一个 Thread 类的对象,例如:thread,把 my 作为参数传给 thread 的 target 属性
调用 thread 的 start 方法
package com.atguigu.thread;
public class MyRunnable implements Runnable{
@Override
public void run() {
//例如:我想让一个线程,帮我打印1-100的偶数
for(int i=2; i<=100; i+=2){
try {
Thread.sleep(10);//10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("偶数even:" + i);
}
}
}
package com.atguigu.thread;
public class TestMyRunnable {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
// my.start();//错误,因为MyRunnable,及其父类、父接口都没有start方法
Thread thread = new Thread(my);
thread.start();
/*
thread在这里就充当了代理的角色,或者担保人。
new Thread(my) 这句代码,为thread对象的target属性赋值, target就相当于被代理者目标
其实这里启动的是thread线程,线程调度器会去调用 thread的run方法。
但是thread的run方法代码如下:
public void run() {
if (target != null) {
target.run();
}
}
因为thread的target属性现在不为null,它target其实就是my。在thread的run方法中帮我们执行了target即my的run
*/
//例如:我想让主线程,帮我打印1-100的奇数
for(int i=1; i<=100; i+=2){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("奇数odd:" + i);
}
}
}
2.2.3 匿名内部类实现多线程
package com.atguigu.thread;
public class TestAnonymous {
public static void main(String[] args) {
//1、匿名内部类继承Thread类
/* new Thread(){
@Override
public void run() {
//例如:我想让一个线程,帮我打印1-100的偶数
for(int i=2; i<=100; i+=2){
try {
Thread.sleep(10);//10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("偶数even:" + i);
}
}
}.start();*/
//类是匿名的,对象也是匿名的
Thread t = new Thread(){
@Override
public void run() {
//例如:我想让一个线程,帮我打印1-100的偶数
for(int i=2; i<=100; i+=2){
try {
Thread.sleep(10);//10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("偶数even:" + i);
}
}
};
t.start();
//类是匿名的,对象是有名字的,是t
//2、匿名内部类实现Runnable接口
/*Runnable r = new Runnable(){
@Override
public void run() {
//例如:我想让一个线程,帮我打印1-100的奇数
for(int i=1; i<=100; i+=2){
try {
Thread.sleep(10);//10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("奇数odd:" + i);
}
}
};
Thread t2 = new Thread(r);
t2.start();*/
/*new Thread(new Runnable(){
@Override
public void run() {
//例如:我想让一个线程,帮我打印1-100的奇数
for(int i=1; i<=100; i+=2){
try {
Thread.sleep(10);//10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("奇数odd:" + i);
}
}
}).start();*/
new Thread(() ->{
//例如:我想让一个线程,帮我打印1-100的奇数
for(int i=1; i<=100; i+=2){
try {
Thread.sleep(10);//10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("奇数odd:" + i);
}
}
).start();//Lambda表达式写法
}
}
2.3 Thread 类的部分方法
线程名称的 get/set 方法
Thread 类的静态方法
currentThread()
:获取当前线程对象。当前线程是指正在执行这句语句的线程对象。Thread 类的静态方法
sleep(时间)
:线程休眠Thread 类的非静态方法
join(时间)
和join()
:线程阻塞,加塞Thread 类的非静态方法
stop()
:线程停止- 不建议直接使用 stop 方法
- 而是用变量或其他方法来控制线程的结束条件
Thread 类的非静态方法:
setDaemon(true或false)
,设置当前线程为守护线程或非守护线程。- 什么是守护线程?守护线程是一种特殊的线程,它不会独立存在于 JVM 中,如果其他非守护线程结束了,守护线程自动结束。通常守护线程都是为其他线程服务的,例如:GC 线程、异常检测线程等
示例代码 1
package com.atguigu.thread;
/*
线程调度器只会调用 run方法,如果我的方法是别的,它不会帮我们自动调用,
我们必须手动调用。
*/
public class SubThread extends Thread{
@Override
public void run() {
//打印我是xx线程
System.out.println("我是" + getName() + "线程");
//getName()从Thread继承的
}
}
package com.atguigu.thread;
public class SubRunnable implements Runnable{
@Override
public void run() {
//打印我是xx线程
// System.out.println("我是" + getName() + "线程");
//Object父类没有getName()
//Thread类才有这个方法。
Thread thread = Thread.currentThread();
System.out.println("我是" + thread.getName() + "线程'");
}
}
package com.atguigu.thread;
public class TestMethods {
public static void main(String[] args) {
SubThread sub1 = new SubThread();
SubThread sub2 = new SubThread();
SubThread sub3 = new SubThread();
SubThread sub4 = new SubThread();
sub1.setName("老大");
sub1.start();
sub2.start();
sub3.start();
sub4.start();
//有5个线程
/*
线程的默认名称是 Thread-编号,编号从0开始
*/
Thread thread = Thread.currentThread();
System.out.println("主线程的名字:" + thread.getName());
SubRunnable sub5 = new SubRunnable();
Thread t = new Thread(sub5);
t.start();
}
}
示例代码 2
package com.atguigu.thread;
public class TestSleep {
public static void main(String[] args) {
for(int i=1; i<=10; i++){
System.out.println(i);
try {
Thread.sleep(1000);//休眠1秒 = 1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
示例代码 3
package com.atguigu.thread;
public class TestStop {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
for(int i=1; i<=10; i++){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
//主线程等5秒,让t1线程停下来
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.stop();//已过时,最好别用
}
}
package com.atguigu.thread;
public class TestStop2 {
private static boolean flag = true;
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
for(int i=1; i<=10; i++){
System.out.println(i);
if(!flag) {
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
//主线程等5秒,让t1线程停下来
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
}
}
示例代码 4
package com.atguigu.thread;
public class TestJoin {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
for(int i=2; i<=100; i+=2){
System.out.println("偶数:" + i);
}
}
};
t1.start();
Thread t2 = new Thread(){
@Override
public void run() {
for(int i=1; i<=100; i+=2){
System.out.println("奇数:" + i);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t2.start();
try {
t1.join();//main线程要等t1执行完才能继续,如果t1不完事,main不继续
//此时与t2线程无关
//t1插队到main前面
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// t2.join();//main线程要等t2执行完才能继续,如果t2不完事,main不继续
//此时与t1线程无关
//t2插队到main前面
//t1和t2还是竞争关系
t2.join(30000);//t2只阻塞main线程30秒,main恢复自由
} catch (InterruptedException e) {
e.printStackTrace();
}
//等偶数和奇数都打印完,接着打印26个字母
for(char letter = 'a'; letter<='z'; letter++){
System.out.println(letter);
}
}
}
示例代码 5
package com.atguigu.thread;
public class TestDaemon {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
while(true){
System.out.println("我爱尚硅谷");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.setDaemon(true);//让t称为守护线程
t.start();
for(int i=1; i<=10; i++){
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
以下是错误示例:
package com.atguigu.thread;
import org.junit.jupiter.api.Test;
public class TestJUnit {
@Test
public void test1(){
Thread t = new Thread(){
@Override
public void run() {
while(true){
System.out.println("我爱尚硅谷");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
//在JUnit单元测试方法中t线程默认是守护线程
/*for(int i=1; i<=10; i++){
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
}
}
2.4 线程安全
像 ArrayList 等集合,或第 1 代第 2 代的日期时间类等,StringBuilder 等可变字符串等,它们都是线程不安全的,当多个线程同时操作它们的同一个对象时,就会出现数据不一致等问题,就是线程不安全的现象。
面试题:哪几个集合是线程安全的?
List:Vector、Stack
Map:Hashtable、Properties、ConcurrentHashMap
2.4.1 如何解决线程安全问题
案例:卖票问题
现在有 100 张票,分为多个渠道(多个线程)同时卖票。
1、如何判断自己的代码有没有线程安全问题
- 是不是存在多个线程
- 多个线程有没有“共享”数据
- 多个线程对共享数据是否有修改或写操作
只要上面的 3 个条件同时存在,那么你的代码一定有线程安全问题的隐患。
2、如何解决?
需要给 某段操作共享数据的代码 加锁。这段代码同一个时间只能被 1 个线程执行,不能被多个线程同时执行。
Java基础阶段
,加锁的方式只有 1 种,同步锁 synchronized 。
3、synchronized 的原理
synchronized 比喻:厕所所长
synchronized 会选择一个 对象,这个对象就是厕所所长,它的职责是记录现在哪个线程 进入了 加锁的代码(比喻进入卫生间上厕所)。
只要有一个线程进入执行了这段代码,其他线程只能等待,等待这个线程出来,厕所所长就会擦除它的名字,下一个线程再进去。
4、如何使用 synchronized
(1)同步代码块的语法格式:
synchronized(所长对象){
//需要加锁的代码
}
对于同步代码块来说,厕所所长(又称为监视器对象)是由编写同步代码块的程序员选择的。
(2)同步方法的语法格式:
【其他修饰符】 synchronized 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//整个方法体都需要加锁
}
对于同步方法来说,厕所所长是默认的,不可更改的:
- 静态方法:所长是当前类的 Class 对象。每一个类被类加载器加载到 JVM 中之后,都会有唯一的 Class 对象来表示它。有 10 个类,就有 10 个 Class 对象,每一个类都有自己独立的 Class 对象,只要类相同,Class 对象就相同,类不同 Class 对象就不同。关于 Class 对象的更多细节,请看反射章节。
- 非静态方法:所长是 this 对象
5、锁的代码范围如何确定?
单次线程任务代码,这一次任务代码是一个整体,具有不可拆分的原子性的特征。
6、选谁当厕所所长
原则:是不是多个线程 同时承认的同一个锁对象。至于锁对象的类型不重要。
2.4.2 示例代码
示例 1:有安全问题(ArrayList)
package com.atguigu.unsafe;
import java.util.ArrayList;
/*
需求:有一个ArrayList集合,现在有2个线程同时往集合中添加元素,
每一个线程都添加1000个元素,查看最后集合中有多少个元素?
*/
public class TestArrayList {
private static ArrayList<Integer> list = new ArrayList<>();
// private static Vector<Integer> list = new Vector<>();
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
list.add(i);
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 1001; i <= 2000; i++) {
list.add(i);
}
}
};
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list的元素总个数:" + list.size());
}
}
示例 2:没有安全问题(解决示例 1 问题)
package com.atguigu.unsafe;
import java.util.ArrayList;
/*
需求:有一个ArrayList集合,现在有2个线程同时往集合中添加元素,
每一个线程都添加1000个元素,查看最后集合中有多少个元素?
*/
public class TestArrayList {
private static ArrayList<Integer> list = new ArrayList<>();
// private static Vector<Integer> list = new Vector<>();
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
synchronized (list) {
list.add(i);
}
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 1001; i <= 2000; i++) {
synchronized (list) {
list.add(i);
}
}
}
};
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list的元素总个数:" + list.size());
}
}
示例 3:有安全问题(SimpleDateFormat)
package com.atguigu.unsafe;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSimpleDateFormat{
private static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
long start = System.currentTimeMillis();
for(int i=1; i<=5; i++){
start += 1000;
Date date = new Date(start);
String str = sf.format(date);
System.out.println(str);
}
}
};
t1.start();
Thread t2 = new Thread(){
@Override
public void run() {
Date d = new Date(1000, 0,1);//过时的方法 2900-1-1
long start = d.getTime();
for(int i=1; i<=5; i++){
start += 1000;
Date date = new Date(start);
String str = sf.format(date);
System.out.println("\t\t" + str);
}
}
};
t2.start();
}
}
示例 4:没有安全问题(解决示例 3 问题)
package com.atguigu.unsafe;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSimpleDateFormat{
private static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
long start = System.currentTimeMillis();
for(int i=1; i<=5; i++){
start += 1000;
Date date = new Date(start);
synchronized (sf) {
String str = sf.format(date);
System.out.println(str);
}
}
}
};
t1.start();
Thread t2 = new Thread(){
@Override
public void run() {
Date d = new Date(1000, 0,1);//过时的方法 2900-1-1
long start = d.getTime();
for(int i=1; i<=5; i++){
start += 1000;
Date date = new Date(start);
synchronized (sf) {
String str = sf.format(date);
System.out.println("\t\t" + str);
}
}
}
};
t2.start();
}
}
示例 5:有安全问题(卖票之继承 Thread)
package com.atguigu.unsafe;
public class TicketTwo extends Thread{
// int total = 10;//成员变量的实例变量。 每一个对象不共享。因为测试类中有多个TicketTwo的对象,所以实例不被多个线程共享
private static int total = 10;
@Override
public void run() {
while(total>0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(getName() +"卖出一张票,剩余:" + total);
}
}
}
package com.atguigu.unsafe;
public class TestTicketTwo {
public static void main(String[] args) {
TicketTwo t1 =new TicketTwo();
TicketTwo t2 =new TicketTwo();
TicketTwo t3 =new TicketTwo();
t1.start();
t2.start();
t3.start();
}
}
示例 6:没有安全问题(同步代码块)
package com.atguigu.safe.block;
public class TicketTwo extends Thread {
// int total = 10;//成员变量的实例变量。 每一个对象不共享。因为测试类中有多个TicketTwo的对象,所以实例不被多个线程共享
private static int total = 1000;
private static Object lock = new Object();
@Override
public void run() {
while (true) {
// synchronized ("李刚") {//"李刚“在内存中只有1份,可以给共享
// synchronized (this) {//错误,因为此时有3个TicketTwo的对象
synchronized (lock) {//同步代码块
if(total>0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(getName() + "卖出一张票,剩余:" + total);
}else{
break;
}
}
}
}
}
package com.atguigu.safe.block;
public class TestTicketTwo {
public static void main(String[] args) {
TicketTwo t1 =new TicketTwo();
TicketTwo t2 =new TicketTwo();
TicketTwo t3 =new TicketTwo();
t1.start();
t2.start();
t3.start();
}
}
示例 7:没有安全问题(同步方法)
package com.atguigu.safe.method;
/*
public class TicketTwo extends Thread{
// int total = 10;//成员变量的实例变量。 每一个对象不共享。因为测试类中有多个TicketTwo的对象,所以实例不被多个线程共享
private static int total = 10;
// (1)所长是谁?
// run()是非静态方法,所长是this。这里this不合适
// (2)锁的代码范围对不对?
// 不对
@Override
public synchronized void run() {
while(total>0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(getName() +"卖出一张票,剩余:" + total);
}
}
}
*/
public class TicketTwo extends Thread{
// int total = 10;//成员变量的实例变量。 每一个对象不共享。因为测试类中有多个TicketTwo的对象,所以实例不被多个线程共享
private static int total = 1000;
@Override
public void run() {
while(total>0){
saleOneTicket();
}
}
// (1)所长是谁?
// saleOneTicket()是静态方法,所长是当前类的Class对象,TicketTwo.class
// (2)锁的代码范围对不对?
// 对
public static synchronized void saleOneTicket(){
if(total>0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() +"卖出一张票,剩余:" + total);
}
}
}
package com.atguigu.safe.method;
public class TestTicketTwo {
public static void main(String[] args) {
TicketTwo t1 =new TicketTwo();
TicketTwo t2 =new TicketTwo();
TicketTwo t3 =new TicketTwo();
t1.start();
t2.start();
t3.start();
}
}
示例 8:有安全问题(卖票之实现 Runnable)
package com.atguigu.unsafe;
public class TicketThree implements Runnable{
int total = 10;//成员变量的实例变量。 每一个对象不共享。 如果多个线程使用同一个对象,那么可以共享实例变量。
@Override
public void run() {
while(total>0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() +"卖出一张票,剩余:" + total);
}
}
}
package com.atguigu.unsafe;
public class TestTicketThree {
public static void main(String[] args) {
TicketThree three = new TicketThree();
Thread t1 = new Thread(three);
Thread t2 = new Thread(three);
Thread t3 = new Thread(three);
t1.start();
t2.start();
t3.start();
}
}
示例 9:没有安全问题(同步代码块)
package com.atguigu.safe.block;
public class TicketThree implements Runnable{
int total = 1000;//成员变量的实例变量。 每一个对象不共享。 如果多个线程使用同一个对象,那么可以共享实例变量。
@Override
public void run() {
while(total>0){
//同步代码块
synchronized (this) {//这里this可以,因为TicketThree的对象现在是1个
if (total > 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余:" + total);
}
}
}
}
}
package com.atguigu.safe.block;
public class TestTicketThree {
public static void main(String[] args) {
TicketThree three = new TicketThree();
Thread t1 = new Thread(three);
Thread t2 = new Thread(three);
Thread t3 = new Thread(three);
t1.start();
t2.start();
t3.start();
}
}
示例 10:没有安全问题(同步方法)
package com.atguigu.safe.method;
/*public class TicketThree implements Runnable{
int total = 10;//成员变量的实例变量。 每一个对象不共享。 如果多个线程使用同一个对象,那么可以共享实例变量。
//(1)所长是谁? run()非静态,是this,这里this合适
//(2)范围合适吗?不合适
@Override
public synchronized void run() {
while(total>0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() +"卖出一张票,剩余:" + total);
}
}
}*/
public class TicketThree implements Runnable{
int total = 1000;//成员变量的实例变量。 每一个对象不共享。 如果多个线程使用同一个对象,那么可以共享实例变量。
@Override
public void run() {
while(total>0){
saleOneTicket();
}
}
//(1)所长是谁? run()非静态,是this,这里this合适
//(2)范围合适吗?合适
public synchronized void saleOneTicket(){
if(total>0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() +"卖出一张票,剩余:" + total);
}
}
}
package com.atguigu.safe.method;
public class TestTicketThree {
public static void main(String[] args) {
TicketThree three = new TicketThree();
Thread t1 = new Thread(three);
Thread t2 = new Thread(three);
Thread t3 = new Thread(three);
t1.start();
t2.start();
t3.start();
}
}
2.5 单例设计模式
单例:唯一的实例对象。某个类它的对象只有 1 个。
编写单例类的套路,模板,成为单例设计模式。
所谓设计模式,解决一类问题,大家总结出来的一套经验,套路。大约有 23 种设计模式,被各种编程语言公认是比较好的代码模板。
单例设计模式是 23 种当中最简单的,也是笔试题中考的最多。
问题 1:怎么限制这个类的对象只有 1 个?
(1)构造器必须私有化。==>注定只能在类的内部创建它唯一的对象
(2)给外界提供渠道能获取到这个类的 唯一对象
问题 2:在哪里,什么时候创建这个类的唯一对象?
饿(恶)汉式单例设计模式:
- 饿:饥不择食,着急
- 恶:一言不合就开干
- 急:无论这个类的对象你现在用不用,我上来(类加载时)就把这个对象创建好了。 总结:在类初始化时就创建它唯一的对象
- 饿汉式是在类初始化时创建对象,而类初始化过程是一定线程安全的,因为底层已经加锁保证一个类只会被初始化一次。
懒汉式单例设计模式
- 懒:不到万不得已不会动
- 慢:直到你明确来拿这个对象了,我再创建,不会提前创建
- 懒汉式是在方法中创建对象的,所以要考虑线程安全问题。
示例代码 1:饿汉式
package com.atguigu.single;
public class SingleOne {
public static final SingleOne INSTANCE = new SingleOne();
private SingleOne(){
System.out.println("SingleOne的唯一对象被创建了");
}
public static void method(){
System.out.println("这是一个普通的静态方法");
}
}
package com.atguigu.single;
public class TestSingleOne {
public static void main(String[] args) {
/*SingleOne s1 = SingleOne.INSTANCE;
SingleOne s2 = SingleOne.INSTANCE;
System.out.println(s1 == s2);//true比较地址值*/
SingleOne.method();
}
}
示例代码 2:饿汉式
package com.atguigu.single;
public enum SingleTwo {
INSTANCE;
SingleTwo(){
System.out.println("SingTwo的对象被创建了");
}
public static void method(){
System.out.println("这是一个普通的静态方法");
}
}
package com.atguigu.single;
public class TestSingleTwo {
public static void main(String[] args) {
/* SingleTwo s1 = SingleTwo.INSTANCE;
SingleTwo s2 = SingleTwo.INSTANCE;
System.out.println(s1 == s2);//true*/
SingleTwo.method();
}
}
示例代码 3:饿汉式
package com.atguigu.single;
public class SingleThree {
private static final SingleThree INSTANCE = new SingleThree();
private SingleThree(){
System.out.println("SingleThree对象被创建了");
}
public static SingleThree getInstance(){
return INSTANCE;
}
public static void method(){
System.out.println("这是一个普通的静态方法");
}
}
package com.atguigu.single;
public class TestSingleThree {
public static void main(String[] args) {
/* SingleThree s1 =SingleThree.getInstance();
SingleThree s2 =SingleThree.getInstance();
System.out.println(s1 == s2);//true*/
SingleThree.method();
}
}
示例代码 4:懒汉式
package com.atguigu.single;
public class SingleFour {
private static SingleFour instance;
private SingleFour(){
System.out.println("SingleFour对象被创建了");
}
public static synchronized SingleFour getInstance(){
if(instance == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleFour();
}
return instance;
}
public static void method(){
System.out.println("这是一个普通的静态方法");
}
}
/*
public class SingleFour {
private static SingleFour instance;
private SingleFour(){
}
public static SingleFour getInstance(){
if(instance == null) {//如果instance已经创建了,线程就不需要竞争锁了,直接返回instance对象,效率比较高
synchronized (SingleFour.class) {//同步代码块
if(instance == null) {//如果两个线程同时判断完instance == null都成立,但是只有1个线程进入到synchronized快中,先创建了。另一个线程进来之后,不能再创建第2个对象。这个条件可以避免创建多个对象。
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleFour();
}
}
}
return instance;
}
}*/
package com.atguigu.single;
public class TestSingleFour {
public static void main(String[] args) {
/* SingleFour s1 = SingleFour.getInstance();
SingleFour s2 = SingleFour.getInstance();
System.out.println(s1 == s2);
//这是是单线程的,没有安全问题*/
SingleFour.method();
SingleFour s1 = SingleFour.getInstance();
}
}
package com.atguigu.single;
public class TestSingleFour2 {
private static SingleFour s1;
private static SingleFour s2;
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
s1 = SingleFour.getInstance();
}
};
t1.start();
Thread t2 = new Thread(){
@Override
public void run() {
s2 = SingleFour.getInstance();
}
};
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
System.out.println(s1 == s2);
}
}
示例代码 5:懒汉式
package com.atguigu.single;
public class SingleFive {
private SingleFive(){
System.out.println("SingleFive对象被创建了");
}
static {
System.out.println("外部类的静态代码块");
}
private static class Inner{
static SingleFive INSTANCE = new SingleFive();
//把创建外部类对象的工作,交给内部类
//这个对象是在初始化Inner类的时候才创建的
static {
System.out.println("内部类的静态代码块");
}
}
public static SingleFive getInstance(){
return Inner.INSTANCE;
}
public static void method(){
System.out.println("这是一个普通的静态方法");
}
}
package com.atguigu.single;
public class TestSingleFive {
public static void main(String[] args) {
SingleFive.method();
SingleFive s1 = SingleFive.getInstance();
SingleFive s2 = SingleFive.getInstance();
System.out.println(s1 == s2);
}
}