前言
为什么要学习线程安全,举个例子,假设我们有1000块钱,呢么我们在取钱时,取了两次钱,一次取了100元,按照之前的知识,剩下多少钱呢,800吗?不,900元,因为都是两次都是1000-100,而我们需要的是,第一次取100,我们还剩900,第二次取100,是在900的基础上减100,这就是线程安全
- 本节也就是线程安全引出的知识点
*
线程安全
- 临界资源:同一个数据或对象每次只能允许一个线程进行方法操作
- 原子操作:不可分割的多部操作被视为一个整体。其顺序和步骤不可打乱或缺省
在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
线程同步
安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
怎么实现呢?
-
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
-
Java提供了同步代码块的方式来解决
同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步方法的锁对象是什么呢?
this
静态同步方法
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步静态方法的锁对象是什么呢?
类名.class
同步的好处和弊端
-
好处:解决了多线程的数据安全问题
-
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
线程死锁
-
概述
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
-
什么情况下会产生死锁
- 资源有限
- 同步嵌套
-
代码演示
public class Demo {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(()->{
while(true){
synchronized (objA){
//线程一
synchronized (objB){
System.out.println("张三正在走路");
}
}
}
}).start();
new Thread(()->{
while(true){
synchronized (objB){
//线程二
synchronized (objA){
System.out.println("李四 正在走路");
}
}
}
}).start();
}
}
生产者/消费者
能够有效的解决死锁的问题
-
概述
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。
所谓生产者消费者问题,实际上主要是包含了两类线程:
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
-
Object类的等待和唤醒方法
方法名 说明 void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 void notify() 唤醒正在等待对象监视器的单个线程 void notifyAll() 唤醒正在等待对象监视器的所有线程
public class Desk {
//定义一个标记
//true 就表示桌子上有汉堡包的,此时允许吃货执行
//false 就表示桌子上没有汉堡包的,此时允许厨师执行
//public static boolean flag = false;
private boolean flag;
//汉堡包的总数量
//public static int count = 10;
//以后我们在使用这种必须有默认值的变量
// private int count = 10;
private int count;
//锁对象
//public static final Object lock = new Object();
private final Object lock = new Object();
public Desk() {
this(false,10); // 在空参内部调用带参,对成员变量进行赋值,之后就可以直接使用成员变量了
}
public Desk(boolean flag, int count) {
this.flag = flag;
this.count = count;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Object getLock() {
return lock;
}
@Override
public String toString() {
return "Desk{" +
"flag=" + flag +
", count=" + count +
", lock=" + lock +
'}';
}
}
public class Cooker extends Thread {
private Desk desk;
public Cooker(Desk desk) {
this.desk = desk;
}
// 生产者步骤:
// 1,判断桌子上是否有汉堡包
// 如果有就等待,如果没有才生产。
// 2,把汉堡包放在桌子上。
// 3,叫醒等待的消费者开吃。
@Override
public void run() {
while(true){
synchronized (desk.getLock()){
if(desk.getCount() == 0){
break;
}else{
//System.out.println("验证一下是否执行了");
if(!desk.isFlag()){
//生产
System.out.println("厨师正在生产汉堡包");
desk.setFlag(true);
desk.getLock().notifyAll();
}else{
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
public class Foodie extends Thread {
private Desk desk;
public Foodie(Desk desk) {
this.desk = desk;
}
@Override
public void run() {
// 1,判断桌子上是否有汉堡包。
// 2,如果没有就等待。
// 3,如果有就开吃
// 4,吃完之后,桌子上的汉堡包就没有了
// 叫醒等待的生产者继续生产
// 汉堡包的总数量减一
//套路:
//1. while(true)死循环
//2. synchronized 锁,锁对象要唯一
//3. 判断,共享数据是否结束. 结束
//4. 判断,共享数据是否结束. 没有结束
while(true){
synchronized (desk.getLock()){
if(desk.getCount() == 0){
break;
}else{
//System.out.println("验证一下是否执行了");
if(desk.isFlag()){
//有
System.out.println("吃货在吃汉堡包");
desk.setFlag(false);
desk.getLock().notifyAll();
desk.setCount(desk.getCount() - 1);
}else{
//没有就等待
//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
/*消费者步骤:
1,判断桌子上是否有汉堡包。
2,如果没有就等待。
3,如果有就开吃
4,吃完之后,桌子上的汉堡包就没有了
叫醒等待的生产者继续生产
汉堡包的总数量减一*/
/*生产者步骤:
1,判断桌子上是否有汉堡包
如果有就等待,如果没有才生产。
2,把汉堡包放在桌子上。
3,叫醒等待的消费者开吃。*/
Desk desk = new Desk();
Foodie f = new Foodie(desk);
Cooker c = new Cooker(desk);
f.start();
c.start();
}
}
好的,本节到此结束,做个小结,本节我们学到了
- 线程安全
- 为了实现线程安全-》线程同步(synchronized)
- 但是线程同步会导致一个严重的问题=》线程死锁
- 怎么解决线程死锁-》生产者/消费者