相关链接
P14 【进阶】多线程、同步、匿名内部类、死锁、生命周期
- 今日要掌握的内容:
- 1.【应用】多线程概述 & 多线程实现
- a.【理解】能够阐述进程与线程的概念
- b.【应用】能够独立写出线程的两种实现方式
- c.【理解】能够阐述两种线程实现方式的优缺点
- 2.【理解】多线程安全问题产生 & 解决方案
- a.【应用】能够分析多线程共享资源产生的安全问题
- b.【应用】能够使用同步代码块解决多线程的安全问题
- c.【应用】能够使用同步方法解决多线程的安全问题
- d.【应用】匿名内部类练习
1 概述
- 进程 (可执行文件, 程序(例如: .exe)
- 进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
- 一个程序运行后至少有一个进程,进程有多条执行路径, 合称为: 多线程
- 简单理解为:
车
- 线程 进程的执行路径(执行单元)
- 线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线的,这个应用程序也可以称之为多线程程序。
- 8核CPU:等于有八个线程可以同时运行各类进程
- 简单理解为:
车道
- 单线程
- 进程只有一条执行路径,这种执行方式叫单线程
- 简单理解为:
一辆车在一条车道上跑
- 多线程
- 一个程序运行后至少有一个进程,进程有多条执行路径, 合称为: 多线程
案例代码一 多线程概述
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/12
* @introduction 多线程概述
*
* main:主线程
* system.out:标准的输出流(黑色字体)。 可以理解为:这是一个线程。
* system.err:标准的错误流(红色字体)。 可以理解为:这也是一个线程。
*
* 1.一台电脑上可以有多个进程,这些进程之间的数据是相互隔离的
* //例如:qq.exe wechat.exe
* 2.一个进程可以有多条线程,
* //例如:往QQ群共享放一个文件,该群中的所有用户都可以下载
*
*/
public class Demo1MultiThread {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
System.out.println(3);
System.err.println(4);
System.err.println(5);
System.out.println(6);
System.out.println(1/0);
//多次执行结果顺序不相同,因为out和err是两个线程,两个线程在抢夺资源,谁抢到资源谁就先执行程序
/*
4
5
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.groupies.base.day14.a.Demo01_多线程概述.main(Demo01_多线程概述.java:27)
1
2
3
6
*/
/*
1
2
3
6
4
5
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.groupies.base.day14.a.Demo01_多线程概述.main(Demo01_多线程概述.java:29)
*/
}
}
1.1 并行&并发
- 并行
- 两个(多个)线程同时执行. (提前: 需要多核CPU)
- 并发
- 两个(多个)线程同时请求执行, 但是同一瞬间, CPU只能执行一个
- 于是就安排它们交替执行, 因为时间间隔非常短, 我们看起来好像是同时执行的, 其实不是
多线程并行和并发的区别
1.2 主方法是多线程的吗?
主方法是由JVM虚拟机调用的,执行顺序为:从上到下、从左到右。
案例代码二 多线程实现方式一 继承Thread类 【a.线程类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/11
* @introduction 主方法中代码的执行是多线程的吗?
*
* 测试方法:
* 如果主方法是单线程的,则进入method死循环方法无法进入到下一步function方法
* 如果主方法是多线程的,则进入method死循环方法不影响其他线程进入下一步的function方法
* 测试结果:
* 未进入function方法,程序循环打印method方法内容
* 结论:
* 主方法是单线程的
*/
public class Demo2MainThreadTest {
public static void main(String[] args) throws InterruptedException {
/*
*/
//测试类1
method();
//测试类2
function();
}
/**
* @introduction 测试类1
*/
public static void method() throws InterruptedException {
while (true){
Thread.sleep((long) 500.00);
System.out.println("method");
}
}
/**
* @introduction 测试类2
*/
public static void function() throws InterruptedException {
while (true){
Thread.sleep((long) 500.00);
System.out.println("function");
}
}
}
2 多线程实现
- 多线程的实现方式
- 方式一:继承Thread类
- 方式二:实现Runnable接口
- 方式三:结合线程池使用(实现Callable接口) — 了解即可
2.1 方式一:继承Thread类
该如何创建线程呢?通过API中搜索,查到Thread类。通过阅读Thread类中的描述。Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
1 创建线程的步骤
- 1)定义一个类(MyThread)继承Thread;
- 2)重写Thread#run()方法;
- 3)把要执行的代码放入run()方法;
- 4)在测试类中,创建线程对象
- 5)调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
- 6)CPU执行程序的随机性
- 7)统一线程不能重复开启,否则会报IllegalThreadStateException异常
2 多线程的实现方式
-
方式一:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
//Thread //返回该线程的名称。 String getName() //改变线程名称,使之与参数 name 相同。 void setName(String name)
3 CPU执行程序的随机性
案例代码三 多线程实现方式一 继承Thread类 【a.线程类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/11
* @introduction 多线程实现方式一 继承Thread类 线程类
*
* 创建线程的步骤:
* 1) 定义一个类(MyThread)继承Thread;
* 2) 重写Thread#run()方法;
* 3) 把要执行的代码放入run()方法;
* 4) 在测试类中,创建线程对象
* 5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
* 6) CPU执行程序的随机性
* 7) 统一线程不能重复开启,否则会报IllegalThreadStateException异常
*
* 多线程的实现方式:
* 方式1:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
*
* Thread
* String getName() 返回该线程的名称。
* void setName(String name) 改变线程名称,使之与参数 name 相同
*/
//1)定义一个类(MyThread)继承Thread;
public class Demo3MyThread extends Thread {
//2) 重写Thread#run()方法;
@Override
/**
* @introduction 该线程要执行的操作,打印10次指定内容
*/
public void run() {
for (int i = 0; i < 10; i++) {
//3) 把要执行的代码放入run()方法;
System.out.println(getName() + ":" + i);
}
}
}
案例代码三 多线程实现方式一 继承Thread类 【b.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/11
* @introduction 多线程实现方式一 测试类
*
* 创建线程的步骤:
* 1) 定义一个类(MyThread)继承Thread;
* 2) 重写Thread#run()方法;
* 3) 把要执行的代码放入run()方法;
* 4) 在测试类中,创建线程对象
* 5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
* 6) CPU执行程序的随机性
* 7) 统一线程不能重复开启,否则会报IllegalThreadStateException异常
*
* 多线程的实现方式:
* 方式1:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
*
* Thread
* String getName() 返回该线程的名称。
* void setName(String name) 改变线程名称,使之与参数 name 相同。
*/
public class Demo3MyThreadTest {
public static void main(String[] args) {
//4) 在测试类中,创建线程对象
Demo3MyThread mt = new Demo3MyThread();
Demo3MyThread mt2 = new Demo3MyThread();
//修改线程名字
mt.setName("张三");
mt2.setName("李四");
//如果调用run()方法,只是普通的方法调用,并不会以多线程方式运行
/*
张三:0
张三:1
张三:2
张三:3
张三:4
张三:5
张三:6
张三:7
张三:8
张三:9
李四:0
李四:1
李四:2
李四:3
李四:4
李四:5
李四:6
李四:7
李四:8
李四:9
*/
mt.run();
mt2.run();
System.out.println("==========================");
//5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
mt.start();
mt2.start();
//6) CPU执行程序的随机性
/*
张三:0
张三:1
李四:0
李四:1
李四:2
李四:3
李四:4
李四:5
张三:2
张三:3
张三:4
张三:5
张三:6
张三:7
张三:8
张三:9
李四:6
李四:7
李四:8
李四:9
*/
//7)统一线程不能重复开启,否则会报IllegalThreadStateException异常
try {
mt.start();
} catch (IllegalThreadStateException e) {
//e.printStackTrace();
System.out.println("IllegalThreadStateException: 非法线程状态异常");
}
}
}
2.1.1 细节及注意事项
Q:为什么同样的代码,在有的电脑上执行,可能结果看着还是像单线程的?
A:有些电脑的CPU机制对多线程任务进行优化(常见联想、小米电脑)
先判断单核CPU线程量有多大,如果优化机制判断当前进程仅需一个CPU即可完成,则会使用单个CPU处理多线程任务
张三:0
张三:1
张三:2
张三:3
张三:4
张三:5
张三:6
张三:7
张三:8
张三:9
李四:0
李四:1
李四:2
李四:3
李四:4
李四:5
李四:6
李四:7
李四:8
李四:9
2.1.2 Thread类的成员
构造方法:
public Thread();
public Thread(String name);
public Thread(Runnable target);
public Thread(Runnable target,String name);
成员方法:
run(); //里边定义的是线程要执行的代码,该方法会自动被start()方法调用
start(); //开启线程,会自动调用run();
getName();
setName();
sleep(); //休眠线程,单位是:毫秒(1s=1000ms)
currentThread; //获取当前正在执行的线程对象(的引用)
2.2 方式二:实现Runnable接口
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。
为何要实现Runnable接口,Runable是啥玩意呢?继续API搜索。
查看Runnable接口说明文档:Runnable接口用来指定每个线程要执行的任务。包含了一个 run 的无参数抽象方法,需要由接口实现类重写该方法。
创建线程的步骤
-
1)定义一个类(MyRunnableThread),实现Runnable接口;
-
2)重写Runnable#run()方法;
-
3)把要执行的代码放入run()方法中;
-
4)创建Runnable接口的子类对象;
MyRunnableThread mrt = new MyRunnableThread();
-
5)并将其作为参数传入Thread类的构造,创建线程对象
Thread th = new Thread(mrt);
-
6)开启线程
th.start();
案例代码四 多线程实现方式一 实现Runnable接口 【a.Runnable子类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/11
* @introduction 多线程实现方式二 实现Runnable接口 Runnable实现类
*
* 可以理解为:Thread类的资源类
*
* 创建线程的步骤。
* 1) 定义一个类(MyRunnableThread),实现Runnable接口;
* 2) 重写Runnable#run()方法;
* 3) 把要执行的代码放入run()方法中;
* 4) 创建Runnable接口的子类对象;
* MyRunnableThread mrt = new MyRunnableThread();
* 5) 并将其作为参数传入Thread类的构造,创建线程对象
* Thread th = new Thread(mrt);
* 6) 开启线程
* th.start();
*
* Thread.currentThread().getName()
* Thread.currentThread() 得到当前thread对象
* Thread.currentThread().getName() 得到当前thread对象的名称
*/
//1) 定义一个类(MyRunnableThread),实现Runnable接口;
public class Demo4MyRunnableThread implements Runnable {
//姓名
String name;
/**
* @introduction 带参构造
* @param name 姓名
*/
public Demo4MyRunnableThread(String name) {
this.name = name;
}
@Override
/**
* @introduciton 2、覆盖接口中的run方法 (Runnable接口的实现类必须实现run方法)
*/
public void run() {
for (int i = 0; i < 10; i++) {
//Thread t = Thread.currentThread();
//System.out.println(t.getName() + ":" + i);
//链式编程
System.out.println(Thread.currentThread().getName() + ":" + name + i);
}
}
}
案例代码四 多线程实现方式一 实现Runnable接口 【b.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/12
* @introduction 多线程实现方式二 实现Runnable接口 测试类
*
* 创建线程的步骤。
* 1) 定义一个类(MyRunnableThread),实现Runnable接口;
* 2) 重写Runnable#run()方法;
* 3) 把要执行的代码放入run()方法中;
* 4) 创建Runnable接口的子类对象;
* MyRunnable mr = new MyRunnable();
* 5) 并将其作为参数传入Thread类的构造,创建线程对象
* Thread th = new Thread(mr);
* 6) 开启线程
* th.start();
*
*/
public class Demo4MyRunnableThreadTest {
public static void main(String[] args) throws InterruptedException {
//4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数
Demo4MyRunnableThread mrt1 = new Demo4MyRunnableThread("张三");
Demo4MyRunnableThread mrt2 = new Demo4MyRunnableThread("李四");
Demo4MyRunnableThread mrt3 = new Demo4MyRunnableThread("林青霞");
Demo4MyRunnableThread mrt4 = new Demo4MyRunnableThread("张曼玉");
//3、创建Thread类的对象
//5、调用Thread类的start方法开启线程
//将Runnable接口的子类对象作为参数传递给Thread类的构造函数时,多线程方式
new Thread(mrt1).start();
new Thread(mrt2).start();
/*
Thread-0:张三0
Thread-1:李四0
Thread-1:李四1
Thread-1:李四2
Thread-0:张三1
Thread-1:李四3
Thread-1:李四4
Thread-0:张三2
Thread-1:李四5
Thread-0:张三3
Thread-1:李四6
Thread-0:张三4
Thread-1:李四7
Thread-0:张三5
Thread-1:李四8
Thread-0:张三6
Thread-1:李四9
Thread-0:张三7
Thread-0:张三8
Thread-0:张三9
*/
Thread.sleep(500);
/*
main:林青霞0
main:林青霞1
main:林青霞2
main:林青霞3
main:林青霞4
main:林青霞5
main:林青霞6
main:林青霞7
main:林青霞8
main:林青霞9
main:张曼玉0
main:张曼玉1
main:张曼玉2
main:张曼玉3
main:张曼玉4
main:张曼玉5
main:张曼玉6
main:张曼玉7
main:张曼玉8
main:张曼玉9
*/
//如果调用run()方法,只是普通的方法调用,并不会以多线程方式运行
mrt3.run();
mrt4.run();
}
}
2.3 方式三:结合线程池使用(实现Callable接口)
暂略
3 多线程安全问题&解决方案
- 出现的问题
- 出现负数
- 出现重复值
- 解决方案
- 采用同步代码块解决
3.1 多线程案例(模拟买票)
- 需求
- 四个窗口,卖100张票
- 买票动作的思路
- A:如果当前票数大于0则继续销售
- B:为了加大出现错误的概率,我们加入:休眠线程 1s = 1000ms
- C:如果有票,就正常的卖票即可
- 第x张票,出现负数票的原因: while判断 + 休眠线程
- 假设现在是最后一张票了,tickets的值应该是1,此时
- 如果线程1抢到了资源,线程1休眠,此时还没有进入卖票的步骤
- 此时线程2,线程3…也抢到了资源,由于票还没有卖出去,所以tickets为1
- 线程2,线程3…可以正常通过while判断,也进入休眠的流程
- 休眠时间结束,程序继续运行
- 假设线程1先抢到票,打印:正在出售1号票,然后会把tickets的值改为:0
- 假设线程2后抢到票,打印:正在出售0号票,然后会把tickets的值改为:-1
- 假设线程3后抢到票,打印:正在出售-1号票,然后会把tickets的值改为:-2
- 假设线程4后抢到票,打印:正在出售-2号票,然后会把tickets的值改为:-3
- 第x张票,出现重复值的原因:tickets–
- tickets-- 相当于 tickets = tickets - 1
- tickets-- 做了3件事:
- A:读值,读取tickets的值
- B:改值,将tickets的值 - 1
- C:赋值,将修改后的值重新赋值给tickets
- 还没有来得及执行C的动作,此时别的线程抢走资源了,就会出现重复值
3.1.1 继承Thread类方式
案例代码五 模拟买票的实现 【a.线程类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/13
* @introduction 模拟买票的实现 线程类
*
* 思路:
* 1.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)
* 2.定义一个静态变量(tickets),记录票数
* 3.需要区分不同窗口,所以要指定每个线程名称
* 4.因为是四个窗口,所以需要创建四个线程对象 给线程自定义名字
* 5.开启线程
* 6.让负数票几率变大->
* 6.1 增加票数 不好找
* 6.2 每次卖票前线程sleep();
*
*/
//2.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)
public class Demo5TicketSellMyThread extends Thread {
//1.定义一个变量(tickets),记录票数
private static int tickets = 100;//因为是共享数据,所以用static修饰
/**
* @introduction 3.需要区分不同窗口,所以要指定每个线程名称
* @param window 窗口名称
*/
public Demo5TicketSellMyThread(String window) {
super(window);
}
@Override
public void run() {
/*
* 买票动作的思路
* A:如果当前票数大于0则继续销售
* B:为了加大出现错误的概率,我们加入:休眠线程 1s = 1000ms
* C:如果有票,就正常的卖票即可
*/
//A:如果当前票数大于0则继续销售
while (tickets > 0) {
//B 每次卖票前线程sleep();
try {
sleep(100 );
} catch (InterruptedException e) {
e.printStackTrace();
}
//C:如果有票,就正常的卖票即可
/**
* 不指定范围,默认在this范围内查找getName方法,当前类找不到则会进入super(父类)继续寻找,如果还找不到则会报错
*/
//System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets-- + "张票");
//System.out.println(super.getName() + ":正在出售第" + tickets-- + "张票");
//System.out.println(this.getName() + ":正在出售第" + tickets-- + "张票");
System.out.println(getName() + ":正在出售第" + tickets-- + "张票");
/*
* 第x张票,出现负数票的原因: while判断 + 休眠线程
* 假设现在是最后一张票了,tickets的值应该是1,此时
* 如果线程1抢到了资源,线程1休眠,此时还没有进入卖票的步骤
* 此时线程2,线程3..也抢到了资源,由于票还没有卖出去,所以tickets为1
* 线程2,线程3..可以正常通过while判断,也进入休眠的流程
*
* 休眠时间结束,程序继续运行
* 假设线程1先抢到票,打印:正在出售1号票,然后会把tickets的值改为:0
* 假设线程2后抢到票,打印:正在出售0号票,然后会把tickets的值改为:-1
* 假设线程3后抢到票,打印:正在出售-1号票,然后会把tickets的值改为:-2
* 假设线程4后抢到票,打印:正在出售-2号票,然后会把tickets的值改为:-3
*/
/*
* 第x张票,出现重复值的原因:tickets--
* tickets-- 相当于 tickets = tickets - 1
* tickets-- 做了3件事:
* A:读值,读取tickets的值
* B:改值,将tickets的值 - 1
* C:赋值,将修改后的值重新赋值给tickets
* 还没有来得及执行C的动作,此时别的线程抢走资源了,就会出现重复值
*/
}
}
}
案例代码五 模拟买票的实现 【b.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/13
* @introduction 模拟买票的实现 测试类
*/
public class Demo5TicketSellMyThreadTest {
public static void main(String[] args) {
//测试:卖票的动作
//4.因为是四个窗口,所以需要创建四个线程对象 给线程自定义名字
Demo5TicketSellMyThread mt1 = new Demo5TicketSellMyThread("窗口1");
Demo5TicketSellMyThread mt2 = new Demo5TicketSellMyThread("窗口2");
Demo5TicketSellMyThread mt3 = new Demo5TicketSellMyThread("窗口3");
Demo5TicketSellMyThread mt4 = new Demo5TicketSellMyThread("窗口4");
//5.开启线程
mt1.start();
mt2.start();
mt3.start();
mt4.start();
/**
* ...
* 窗口3:正在出售第3张票
* 窗口1:正在出售第1张票
* 窗口3:正在出售第1张票
* 窗口4:正在出售第0张票
* 窗口2:正在出售第-1张票
*/
}
}
3.1.2 实现Runnable接口方式
(见3.2.1.2 实现Runnable接口方式)
3.2 多线程安全问题解决
3.2.1 使用同步代码块
- 格式
synchronized(锁对象){
//要加锁的代码
}
/* 锁对象:
* 1)同步代码块的锁对象可以是任意类型的对象
* 2)必须使用【同一把锁】,否则可能出现锁不住的情况
*
* 同一把锁:
* √-1) static修饰的静态对象(static Object obj = new Object();) 但不建议使用
* √-2) 当前类对象的字节码文件(类名.Class)
* ×-3) this (当前类对象) 每个线程new一个新的对象,所以不是同一把锁
*/
3.2.1.1 继承Thread类方式
案例代码六 模拟买票的Thread方式实现-解决安全问题 【a.线程类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/13
* @introduction 模拟买票的Thread方式实现-解决安全问题 线程类
*
* 多线程安全问题解决
* 使用同步代码块 synchronized
* synchronized(锁对象){
* //要加锁的代码
* }
*
* 锁对象:
* 1)同步代码块的锁对象可以是任意类型的对象
* 2)必须使用【同一把锁】,否则可能出现锁不住的情况
*/
//2.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)
public class Demo6TicketSellMyThread extends Thread {
//1.定义一个变量(tickets),记录票数
private static int tickets = 100;//因为是共享数据,所以用static修饰,在类加载前就放入静态常量池,被所有对象共享
//模拟一个锁对象,可以实现同步代码块,但没必要为了同步代码块专门new一个obj,直接使用本类的.class文件即可
static Object obj = new Object();
public Demo6TicketSellMyThread(String window) {
super(window);
}
@Override
public void run() {
while (true) {
/* 锁对象:
* 1)同步代码块的锁对象可以是任意类型的对象
* 2)必须使用【同一把锁】,否则可能出现锁不住的情况
*/
synchronized (Demo6TicketSellMyThread.class) {//不能直接锁在while条件上,否则会导致一个窗口卖完所有票
if (tickets < 1) {
break;
}
//B
try {
sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//C
System.out.println(getName() + ":正在出售第" + tickets-- + "张票");
}
}
}
}
案例代码六 模拟买票的Thread方式实现-解决安全问题 【b.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/13
* @introduction 模拟买票的Thread方式实现-解决安全问题 测试类
*
* 多线程安全问题解决
* 使用同步代码块 synchronized
* synchronized(锁对象){
* //要加锁的代码
* }
*
* 锁对象:
* 1)同步代码块的锁对象可以是任意类型的对象
* 2)必须使用【同一把锁】,否则可能出现锁不住的情况
* 同一把锁 -> 当前类对象的字节码文件
*/
public class Demo6TicketSellMyThreadTest {
public static void main(String[] args) {
//测试:卖票的动作
//4.因为是四个窗口,所以需要创建四个线程对象 给线程自定义名字
Demo6TicketSellMyThread mt1 = new Demo6TicketSellMyThread("窗口1");
Demo6TicketSellMyThread mt2 = new Demo6TicketSellMyThread("窗口2");
Demo6TicketSellMyThread mt3 = new Demo6TicketSellMyThread("窗口3");
Demo6TicketSellMyThread mt4 = new Demo6TicketSellMyThread("窗口4");
//5.开启线程
mt1.start();
mt2.start();
mt3.start();
mt4.start();
/** 通过同步代码块【synchronized(类名.class){}】方式解决线程安全问题
*
* ...
* 窗口1:正在出售第7张票
* 窗口1:正在出售第6张票
* 窗口1:正在出售第5张票
* 窗口4:正在出售第4张票
* 窗口4:正在出售第3张票
* 窗口4:正在出售第2张票
* 窗口4:正在出售第1张票
*/
}
}
3.2.1.2 实现Runnable接口方式
Runnable和Thread方式区别
1) 因为四个Thread对象共用一个Runnable对象,所以tickets可以不用static修饰。
2) 因为四个Thread对象共用一个Runnable对象,所以锁对象可以是当前类this。
案例代码七 模拟买票的Runnable接口方式实现-解决安全问题 【a.Runnable子类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 模拟买票的Runnable接口方式实现-解决安全问题 Runnable的实现类
*
* 可以理解:MyRunnable是资源类
*/
public class Demo7TicketSellMyRunnableThread implements Runnable {
//1.定义变量,记录票
//private static int tickets = 100; //因为是共享数据,所以用static修饰,在类加载前就放入静态常量池,被所有对象共享
private int tickets = 100; //因为四个Thread对象共用一个Runnable对象,所以可以不用static修饰
//2.模拟卖票
@Override
public void run() {
while (true) {
//synchronized (Demo7TicketSellMyRunnableThread.class) {
synchronized (this) {//因为四个Thread对象共用一个Runnable对象,所以锁对象可以是当前类
if (tickets < 1) {
break;
}
try {
//sleep(20);
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println(getName() + ":正在出售第" + tickets-- + "张票");//getName()是Thread类的方法,不能使用
//static Thread currentTrhead():返回当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets-- + "张票");
}
}
}
}
案例代码七 模拟买票的Runnable接口方式实现-解决安全问题 【b.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 模拟买票的Runnable接口方式实现-解决安全问题 测试类
*
*
*/
public class Demo7TicketSellMyRunnableThreadTest {
public static void main(String[] args) {
Demo7TicketSellMyRunnableThread mr = new Demo7TicketSellMyRunnableThread();
Thread t1 = new Thread(mr,"窗口1");
Thread t2 = new Thread(mr,"窗口2");
Thread t3 = new Thread(mr,"窗口3");
Thread t4 = new Thread(mr,"窗口4");
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
4 匿名内部类
- 内部类:类里边还有一个类,里边那个类叫内部类,外边那个类叫外部类。
- 成员内部类:定义在成员位置的内部类(类中,方法外)
- 局部内部类:定义在局部位置的内部类(只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。)
-
匿名内部类
-
概述:就是没有名字的局部内部类
-
格式:
new 类名或者接口名(){ //重写类或者接口中 所有的 抽象方法; }
-
本质:
- 专业版:就是一个继承了类或者实现了接口的 匿名的子类对象
- 大白话:匿名内部类不是类,而是子类对象
-
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 匿名内部类 成员内部类&局部内部类
*/
//外部类
public class AnonymousInnerClass {
//成员变量
String name;
//成员内部类
class Inner1 {}
//成员方法
public void show() {
//局部变量,基本类型
int num = 10;
//局部变量,引用类型
Object obj = new Object();
//局部变量,引用类型,局部内部类
class Inner2 {}
}
}
4.1 三种实现方式
案例代码八 匿名内部类实现抽象类 【a.抽象类Animal】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 匿名内部类实现抽象类 抽象类Animal
*
*/
public abstract class Demo8AbstractAnimal {
//有抽象方法的类一定是抽象类,抽象类不一定有抽象方法
public abstract void eat();
}
案例代码八 匿名内部类实现抽象类 【b.Animal实现类Cat】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 匿名内部类实现抽象类 Animal实现类Cat
*/
public class Demo8Cat extends Demo8AbstractAnimal {
@Override
public void eat() {
System.out.println("我是Cat类的eat方法,猫吃鱼");
}
}
案例代码八 匿名内部类实现抽象类 【c.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 匿名内部类实现抽象类 测试类
*
* 需求:调用 Demo8Animal#eat();
*/
public class Demo8AnonymousInnerClass {
public static void main(String[] args) {
/**
* 方式一:【普通类】实现
* A:定义一个Cat类,继承Animal
* B:重写eat()方法
* C:多态方式创建Cat类的对象,调用eat()方法
*/
Demo8AbstractAnimal an1 = new Demo8Cat();
an1.eat();
/**
* 方式二:【匿名对象】实现
*/
new Demo8Cat().eat();
System.out.println("*********************");
/**
* 方式三:【匿名内部类】实现
*/
new Demo8AbstractAnimal() {
public void eat() {
System.out.println("我是匿名内部类的方式实现的,猫吃鱼");
}
}.eat();
}
}
4.2 开发中的应用
- 匿名内部类在实际开发中的应用
- 1)当对象方法(成员方法)仅调用一次的时候
- 2)可以作为方法的实参进行传递
- 3)采用多态方式实现多次调用
- 个人建议
-
当接口中或抽象类中的抽象方法仅有一个的时候,就可以考虑使用匿名内部类(如果有多个方法,匿名调用一个方法也要同时实现其他方法)
new Demo9InterfaceJumping(){ @Override public void jump() { //我会跳,1)当对象方法(成员方法)仅调用一次的时候 System.out.println("我会跳,1)当对象方法(成员方法)仅调用一次的时候"); } @Override public void eat() {} ... }.jump();
-
案例代码九 匿名内部类在实际开发中的应用 【a.接口类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 匿名内部类在实际开发中的应用 接口
*/
public interface Demo9InterfaceJumping {
public abstract void jump();
}
案例代码九 匿名内部类在实际开发中的应用 【b.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 匿名内部类在实际开发中的应用 测试类
*
* 1)当对象方法(成员方法)仅调用一次的时候(如果调用多次,就采用子类对象实现方法,再调用子类对象#方法)
* 2)可以作为方法的实参进行传递
* 3)采用多态方式实现多次调用
*/
public class Demo9AnonymousInnerClass {
public static void main(String[] args) {
//对,Demo9InterfaceJumping#jump 调用一次
//1)当对象方法(成员方法)仅调用一次的时候(如果调用多次,就采用子类对象实现方法,再调用子类对象#方法)
new Demo9InterfaceJumping(){
@Override
public void jump() {
//我会跳,1)当对象方法(成员方法)仅调用一次的时候
System.out.println("我会跳,1)当对象方法(成员方法)仅调用一次的时候");
}
}.jump();
//2)可以作为方法的实参进行传递
show(new Demo9InterfaceJumping() {
@Override
public void jump() {
//我会跳,2)可以作为方法的实参进行传递
System.out.println("我会跳,2)可以作为方法的实参进行传递");
}
});
//3)采用多态方式实现多次调用
Demo9InterfaceJumping jm =new Demo9InterfaceJumping(){ //多态 (接口指向子类对象)
@Override
public void jump() {
System.out.println("我会跳,3)采用多态方式实现多次调用");
}
};
/*
* 我会跳,3)采用多态方式实现多次调用
* 我会跳,3)采用多态方式实现多次调用
* 我会跳,3)采用多态方式实现多次调用
*/
jm.jump();
jm.jump();
jm.jump();
}
//2)可以作为方法的实参进行传递
public static void show(Demo9InterfaceJumping jm) {
jm.jump();
}
}
5 同步
- 同步和效率的问题
- 线程安全(线程同步),效率低
- 线程不安全(线程不同步),效率高
- 概述/作用
- 多线程(环境),并发, 操作同一数据,有可能引发安全问题,就需要用到同步解决
5.1 同步代码块
-
格式
synchronized(锁对象){ //要加锁的代码 } /* 锁对象: * 1)同步代码块的锁对象可以是任意类型的对象 * 2)必须使用【同一把锁】,否则可能出现锁不住的情况 * * 同一把锁: * √-1) static修饰的静态对象(static Object obj = new Object();) 但不建议使用 * √-2) 当前类对象的字节码文件(类名.Class) * ×-3) this (当前类对象) 每个线程new一个新的对象,所以不是同一把锁 */
5.2 同步方法
- 静态方法
- 锁对象:该类的字节码文件对象(类名.Class)
- 非静态方法
- 锁对象:this
5.2.1 静态方法锁对象
- 静态方法
- 锁对象:该类的字节码文件对象(类名.Class)
案例代码十 静态方法锁对象:该类的字节码文件对象 【a.方法类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 静态方法锁对象:该类的字节码文件对象 方法类
*/
public class Demo10 {
/*
* 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?
* 静态方法:
* 锁对象:该类的字节码文件对象
* 非静态方法:
* 锁对象:this
*
* 思路:
* 1.创建两个线程
* 2.分别调用Demo类的两个方法
* 一个线程调用Demo10#method1(),method1()用同步代码块
* 一个线程调用Demo10#method2(),method2()用同步方法
* 3.为了让效果更明显,用while(true)循环
*/
public static void method1(){
//synchronized (锁对象) {
//synchronized (String.class) { //method1锁对象是String.Class时 【没锁住】
synchronized (Demo10.class) { //method1锁对象是Demo10.Class(该类的字节码)时 【锁住了】
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
}
}
//静态方法锁对象:该类的字节码文件对象(Demo10.class)
public synchronized static void method2(){
System.out.print("i");
System.out.print("t");
System.out.print("c");
System.out.print("a");
System.out.print("s");
System.out.print("t");
System.out.print("\r\n");
}
}
案例代码十 静态方法锁对象:该类的字节码文件对象 【b.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 静态方法锁对象:该类的字节码文件对象 测试类
*/
public class Demo10SynchronizedTarget {
public static void main(String[] args) {
/*
* 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?
* 静态方法:
* 锁对象:该类的字节码文件对象
* 非静态方法:
* 锁对象:this
*
* 思路:
* 1.创建两个线程
* 2.分别调用Demo类的两个方法
* 一个线程调用Demo10#method1(),method1()用同步代码块
* 一个线程调用Demo10#method2(),method2()用同步方法
* 3.为了让效果更明显,用while(true)循环
*/
//1.创建一个线程
new Thread() {
public void run() {
//2.一个线程调用Demo10#method1(),method1()用同步代码块
//3.为了让效果更明显,用while(true)循环
while (true) Demo10.method1();
}
}.start();
//1.创建一个线程
new Thread() {
public void run() {
//2.一个线程调用Demo10#method2(),method2()用同步方法
//3.为了让效果更明显,用while(true)循环
while (true) Demo10.method2();
}
}.start();
/** method1锁对象是String.Class时 【没锁住】
*
* 黑itcast
* 马程序员
* 黑马程序员
* 黑马程序员itcast
*/
/** method1锁对象是Demo10.Class(该类的字节码)时 【锁住了】
*
* itcast
* itcast
* 黑马程序员
* 黑马程序员
*/
}
}
5.2.2 非静态方法锁对象
- 非静态方法
- 锁对象:this
案例代码十一 非静态方法锁对象:this 【a.方法类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 非静态方法锁对象:this 方法类
*/
public class Demo11 {
/*
* 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?
* 静态方法:
* 锁对象:该类的字节码文件对象
* 非静态方法:
* 锁对象:this
*
* 思路:
* 1.创建两个线程
* 2.分别调用Demo类的两个方法
* 一个线程调用Demo11#method1(),method1()用同步代码块
* 一个线程调用Demo11#method2(),method2()用同步方法
* 3.为了让效果更明显,用while(true)循环
*/
public void method1(){
//synchronized (Demo11.class) { //method1锁对象是Demo11.Class(该类的字节码)时 【没锁住】
synchronized (this) { //method1锁对象是this时 【锁住了】
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
}
}
//非静态方法锁对象:this
public synchronized void method2(){
System.out.print("i");
System.out.print("t");
System.out.print("c");
System.out.print("a");
System.out.print("s");
System.out.print("t");
System.out.print("\r\n");
}
}
案例代码十一 非静态方法锁对象:this 【b.测试类】
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 静态方法锁对象:该类的字节码文件对象 测试类
*/
public class Demo11SynchronizedTarget {
public static void main(String[] args) {
/*
* 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?
* 静态方法:
* 锁对象:该类的字节码文件对象
* 非静态方法:
* 锁对象:this
*
* 思路:
* 1.创建两个线程
* 2.分别调用Demo类的两个方法
* 一个线程调用Demo11#method1(),method1()用同步代码块
* 一个线程调用Demo11#method2(),method2()用同步方法
* 3.为了让效果更明显,用while(true)循环
*/
//jdk1.8这里默认了关键字final Demo11 demo = new Demo11();
Demo11 demo = new Demo11();
//demo = new Demo11(); //因为demo是final修饰的,所以不能重新赋值
new Thread() {public void run() { while (true) demo.method1(); }}.start();
new Thread() {public void run() { while (true) demo.method2(); }}.start();
/** method1锁对象是Demo11.Class(该类的字节码)时 【没锁住】
*
* 黑马itcast
* itcast程序员
* 黑马程序员
*/
/** method1锁对象是this时 【锁住了】
*
* itcast
* itcast
* 黑马程序员
* 黑马程序员
*/
}
}
6 多线程的难点
6.1 死锁
-
死锁
- 1)死锁需要两个线程,两把锁
- 2)
- 一个线程先拿锁A,再拿锁B
- 另一个线程先拿锁B,再拿锁A
- 3)为了让效果更明显,用while(true)改进
案例代码十二 死锁的实现
package com.groupies.base.day14;
/**
* @author GroupiesM
* @date 2021/05/17
* @introduction 死锁的实现
*
* 1)死锁需要两个线程,两把锁
* 2)
* 一个线程先拿锁A,再拿锁B
* 另一个线程先拿锁B,再拿锁A
* 3)为了让效果更明显,用while(true)改进
*
*/
public class Demo12DeadLock {
public static final String LOCKA = "锁A";
public static final String LOCKB = "锁B";
public static void main(String[] args) {
/*
new Thread(new Runnable() {
@Override
public void run() {
}
}){}.start();
*/
new Thread() {
public void run() {
//3)为了让效果更明显,用while(true)改进
while (true) {
//2)一个线程先拿锁A,再拿锁B
synchronized (LOCKA) {
System.out.println("线程一获取到" + LOCKA + ",等待" + LOCKB);
synchronized (LOCKB) {
System.out.println("线程一获取到" + LOCKB + ",成功进入小房间...");
}
}
}
}
}.start();
new Thread() {
public void run() {
//3)为了让效果更明显,用while(true)改进
while (true) {
//2)另一个线程先拿锁B,再拿锁A
System.out.println("线程二获取到" + LOCKB + ",等待" + LOCKA);
synchronized (LOCKB) {
synchronized (LOCKA) {
System.out.println("线程二获取到" + LOCKA + ",成功进入小房间...");
}
}
}
}
}.start();
/** 死锁的实现:线程一与线程二互相等待对方手里的那把锁
*
* 线程一获取到锁B,成功进入小房间...
* 线程一获取到锁A,等待锁B
* 线程一获取到锁B,成功进入小房间...
* 线程二获取到锁B,等待锁A
* 线程一获取到锁A,等待锁B
*/
}
}
6.2 多线程的生命周期
1.新建:创建线程对象
2.就绪:线程对象已经启动了,但是还没有获取到CPU的执行权
3.运行(有可能会发生阻塞或者等待状态) :获取到了CPU的执行权
阻塞:没有CPU的执行权,回到就绪
4.死亡:代码运行完毕,线程消亡
//唤醒在此对象监视器上等待的单个线程(随机唤醒)
Object#notify()
//唤醒在此对象监视器上等待的所有线程(随机唤醒)
Object#notifyAll()
//在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
Object#wait()
线程生命周期(一)
1.新建:创建线程对象
2.就绪:线程对象已经启动了,但是还没有获取到CPU的执行权
3.运行(有可能会发生阻塞或者等待状态) :获取到了CPU的执行权
阻塞:一般指IO流阻塞,没有CPU的执行权,回到就绪
等待:一般是可控的,等待另一线程先执行完毕或等待
4.死亡:代码运行完毕,线程消亡
//唤醒在此对象监视器上等待的单个线程(随机唤醒)
Object#notify()
//唤醒在此对象监视器上等待的所有线程(随机唤醒)
Object#notifyAll()
//在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
Object#wait()
//等待(插队) ->
//主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。
Thread#join()
//等待(休眠),单位毫秒
Thread#sleep()
线程生命周期(二)
7.面试题
- 1.多线程并行和并发的区别是什么
并行:两个(多个)线程同时执行. (提前: 需要多核CPU)
并发:两个(多个)线程同时请求执行, 但是同一瞬间, CPU只能执行一个
于是就安排它们交替执行, 因为时间间隔非常短, 我们看起来好像是同时执行的, 其实不是
- 2.Java程序是多线程的吗?
是,因为至少开启了main(主线程),GC(垃圾回收线程)
- 3.多线程的两种实现方式之间的区别是什么?
//Java中的继承特点:类只能单继承,接口可以多继承
继承Thread类:
好处:代码相对比较简单 //因为是继承Thread类,所以可以直接使用Thread类中的非私有成员(成员变量,成员方法)
弊端:扩展性相对比较差 //因为是继承,而Java中类之间的继承只能单继承,不能多继承,但是可以多层继承
实现Runnable接口:
好处:扩展性相对比较强
弊端:代码相对比较繁琐
//修饰符
因为多个Thread对象共用一个Runnable对象,所以可以不用static修饰票数tickets。
//锁对象
因为多个Thread对象共用一个Runnable对象,所以锁对象可以是当前类this。(Thread类锁对象一般是当前类的字节码文件)
- 4.多线程的执行特点是什么?
随机性、延迟性
延迟性的原因:因为CPU在做着高效的切换
延迟性的体现:关闭QQ后,当前已打开的QQ对话框会延迟几秒才会退出
- 5.多线程的默认命名规则是什么
命名规则:Thread-${索引编号} //Thread-0 Thread-1
索引编号:从0开始
- 6.匿名内部类(本质是一个对象)
内部类:
概述:类里边还有一个类,里边那个类叫内部类,外边那个类叫外部类。
分类:
成员内部类:定义在成员位置的内部类(类中,方法外)
局部内部类:定义在局部位置的内部类(只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。)
匿名内部类:
概述:就是没有名字的局部内部类
格式:
new 类名或者接口名(){
//重写类或者接口中 所有的 抽象方法;
}
本质:
专业版:就是一个继承了类或者实现了接口的 匿名的子类对象
大白话:匿名内部类不是类,而是子类对象
匿名内部类在实际开发中的应用:
1)当对象方法(成员方法)<font color=red>仅调用一次</font>的时候
2)可以作为方法的实参进行传递
3)采用多态方式实现多次调用
个人建议
当接口中或抽象类中的抽象方法仅有一个的时候,就可以考虑使用匿名内部类(如果有多个方法,匿名调用一个方法也要同时实现其他方法)
new Demo9InterfaceJumping(){
@Override
public void jump() {System.out.println("我会跳");}
@Override
public void eat() {}
...
}.jump();
- 7.实现Runnable接口的原理
实现原理:多态
背景:
多线程的第一种实现方式是:继承Thread类,因为我们自定义的类(MyThread)是Thread类的子类
所以MyThread类的对象调用start()方法的时候,自动调用MyThread#run()
但是MyRunnable类是实现了Runnable接口,而Runnable#run()、Thread#start()没有关系
问:为什么Thread#start(),会自动调用Runnable接口的子类(MyRunnable)中的run()方法呢?
简化版的源码:
//测试类中的代码:
public static void main(String[] args){
MyRunnable mr = new MyRunnable();
Thread th = new Thread(mr);
th.start();//问:为什么会自动调用MyRunnable#run();?
}
//简化版的源码:
public class Thread{
private Runnable target; //new MyRunnable();
public Thread(Runnable target){
this.target = target; //new MyRunnable();
}
public void run(){
if(target != null){
target.run(); //new MyRunnable().run();
}
}
}
//Thread源码:
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
/**
* Allocates a new {@code Thread} object. This constructor has the same
* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
* {@code (null, target, gname)}, where {@code gname} is a newly generated
* name. Automatically generated names are of the form
* {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
*
* @param target
* the object whose {@code run} method is invoked when this thread
* is started. If {@code null}, this classes {@code run} method does
* nothing.
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
/**
* Initializes a Thread with the current AccessControlContext.
* @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
init(g, target, name, stackSize, null, true);
}
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
/*****************************************************/
/*****************************************************/
//关键代码
this.target = target;
/*****************************************************/
/*****************************************************/
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
- 8.public static final有没有顺序要求
没有,可以随意调换顺序
- 9.jdk1.8新特性,当匿名内部类访问其所在方法的局部变量时,该变量必须加final修饰,为什么?
//5.2.2 非静态方法锁对象
为了延长该变量的生命周期
但是jdk1.8起,final可以不写,因为程序会默认加上
public static void main(String[] args) {
/** 局部方法随着main方法调用结束,被GC回收
* 此时成员方法method1()尚未被GC回收,就会导致demo.method1找不到demo引用对象
* 所以需要用final修饰所在方法的局部变量,将其放入静态常量池,延长生命周期
*/
Demo demo = new Demo();
new Thread() {public void run() { while (true) demo.method1(); }}.start();
}
- 10.手写一个死锁代码
见6.1
- 11.多线程的生命周期是什么?
见6.2
-
12.数据结构 - 对象创建的过程
21/05/18
M