1、多线程安全问题
2、等待唤醒机制
01线程操作共享数据的安全问题
*A:线程操作共享数据的安全问题
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。
程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
02售票的案例
1 | *A:售票的案例 |
03线程安全问题引发
1 | *A:线程安全问题引发 |
04同步代码块解决线程安全问题
线程同步的方式有两种:
方式1:同步代码块
方式2:同步方法
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。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*A:同步代码块解决线程安全问题
*A:售票的案例
/*
* 多线程并发访问同一个数据资源
* 3个线程,对一个票资源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现类对象
Tickets t = new Tickets();
//创建3个Thread类对象,传递Runnable接口实现类
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
"/*
* 通过线程休眠,出现安全问题
* 解决安全问题,Java程序,提供技术,同步技术
* 公式:
* synchronized(任意对象){
* 线程要操作的共享数据
* }
* 同步代码块
*/"
public class Tickets implements Runnable{
//定义出售的票源
private int ticket = 100;
private Object obj = new Object();
public void run(){
while(true){
//线程共享数据,保证安全,加入同步代码块
synchronized(obj){
//对票数判断,大于0,可以出售,变量--操作
if( ticket > 0){
try{
Thread.sleep(10);
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
}
}
05同步代码块的执行原理
A:同步代码块的执行原理
同步代码块: 在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
06同步的上厕所原理
*A:同步的上厕所原理
a:不使用同步:线程在执行的过程中会被打扰
线程比喻成人
线程执行代码就是上一个厕所
第一个人正在上厕所,上到一半,被另外一个人拉出来
b:使用同步:
线程比喻成人
线程执行代码就是上一个厕所
锁比喻成厕所门
第一个人上厕所,会锁门
第二个人上厕所,看到门锁上了,等待第一个人上完再去上厕所
07同步方法
1 | *A:同步方法: |
08JDK1.5新特性Lock接口,实现类ReentrantLock
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。
虽然 synchronized 方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
1 | Lock l = ...; |
1 | *A:JDK1.5新特性Lock接口 |
09利用Lock接口实现类ReentrantLock改进售票案例
1 | *A:Lock接口改进售票案例 |
10线程的死锁原理
*A:线程的死锁原理
当线程任务中出现了多个同步(多个锁) 时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
1 | synchronzied(A锁){ |
11线程的死锁代码实现
1 | *A:线程的死锁代码实现 |
打印效果之一:
if … locka
if … lockb
else … lockb
if … locka
这是因为:
Thread t0 = new Thread(dead);
Thread t1 = new Thread(dead);
2个线程都执行了DeadLock程序的内容,即2个线程可以共享DeadLock中同一锁对象LockA.locka,和LockB.lockb
在线程1执行完if语句后,进入else语句执行完B同步,正准备执行A同步时,这时线程2突然抢占了资源,执行了if语句中的A同步,准备执行B同步;
即这时线程1获得了B同步的锁对象LockB.lockb;而线程2获得了A同步的锁对象LockA.locka,导致线程1没有A同步的锁对象,无法执行A同步;线程2没有B同步的锁对象,无法执行B同步,陷入死锁
12线程等待与唤醒案例介绍
线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同
通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
*A:线程等待与唤醒案例介绍
等待唤醒机制所涉及到的方法:
wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
13线程等待与唤醒案例资源类编写
1 | *A:线程等待与唤醒案例资源类编写 |
14线程等待与唤醒案例输入和输出线程
1 | A:线程等待与唤醒案例输入和输出线程 |
15线程等待与唤醒案例测试类
1 | A:线程等待与唤醒案例测试类 |
16线程等待与唤醒案例null值解决
1 | A:线程等待与唤醒案例null值解决 |
17线程等待与唤醒案例数据安全解决
1 | A:线程等待与唤醒案例数据安全解决 |
18线程等待与唤醒案例通信的分析
*A:线程等待与唤醒案例通信的分析
输入: 赋值后,执行方法wait()永远等待
输出: 变量值打印输出,在输出等待之前,唤醒【输入】的notify(),自己在wait()永远等待
输入: 被唤醒后,重新对变量赋值,赋值后,必须唤醒【输出】的线程notify(),自己的wait()
19线程等待与唤醒案例的实现
1 | *A 线程等待与唤醒案例的实现 |
20总结
1 | 同步锁 |