一、线程安全问题(银行取钱)

问题描述:
当两个人同时对一个账户进行操作取钱的时候,可能会出现线程安全问题

//定义一个用户类
public class Account {// 银行账户private String accountNo;//余额private int balance;public Account(String accountNo, int balance) {this.accountNo = accountNo;this.balance = balance;}// get()  set()  方法
}
// 取钱的类
public class DrawThread extends Thread {// 取钱的账户private Account account;// 取钱的金额private int monny;DrawThread(String name,Account account,int monny){super(name);this.account = account;this.monny = monny;}@Overridepublic void run() {super.run();if (account.getBalance() >= monny){Log.e("testthread",getName()+"取钱成功"+monny);// 强制线程调度切换,这样每次两个用户都能取到钱了Thread.sleep(1);account.setBalance(account.getBalance()-monny);}else {Log.e("testthread","余额不足~~");}}
}
//取钱
mBntFun5.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Account account = new Account("admin",1000);DrawThread thread1 = new DrawThread("用户1",account,800);thread1.start();DrawThread thread2 = new DrawThread("用户2",account,800);thread2.start();}});


上面代码很有可能会执行成功,如上图1,或者如图2;

要每次都出现图1的异常情况,只需要将run()中的 Thread.sleep(1); 打开即可

二、同步代码块

图一是因为run()方法的方法体不具有同步安全性,可以用同步监视器解决这个问题,通用方法就是同步代码块,语法格式如下:

// obj 就是同步监视器
synchronized(obj){...// 此处就是同步代码块
}

说明

  • 上面代码的说明:就是在执行同步代码块之前,必须要对同步监视器进行锁定
  • 任何时刻只能有一个线程可以获得同步监视器的锁定,当同步代码块执行完之后,就会释放同步监视器的锁定
  • 同步监视器 一般都是 可能被并发访问的共享资源
  • 一般逻辑如下:
    加锁–>修改–>释放锁

上面的代码进行优化,如下:

@Overridepublic void run() {super.run();// 符合加锁-->修改-->释放锁的逻辑synchronized(account){if (account.getBalance() >= monny){Log.e("testthread",getName()+"取钱成功"+monny);try {// 强制线程调度切换,这样每次两个用户都能取到钱了Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}account.setBalance(account.getBalance()-monny);Log.e("testthread","余额:"+account.getBalance());}else {Log.e("testthread","余额不足~~");}}}

三、同步方法

  • 用synchronized关键字修饰的方法就是同步方法,无需显示指定同步监视器,它的同步监视器就是this,也就是该对象本身

  • 线程安全的类具有如下特点:
    (1)该类的对象可以被多个线程同时访问
    (2)每个线程调用该类的方法后返回的都是正确结果
    (3)每个线程调用该对象的方法后,该对象的状态仍然保持合理状态

  • 不要对所有的方法都进行同步,只对共享资源进行同步

  • 如果可变类有两种运行环境:单线程和多线程,则要为它提供两种版本:线程安全版本和线程不安全版本;

    • 单线程中使用线程不安全版本保证性能
    • 多线程中使用线程安全版本
public class Account {// 银行账户private String accountNo;//余额private int balance;public Account(String accountNo, int balance) {this.accountNo = accountNo;this.balance = balance;}public synchronized void draw(int monny){if (balance >= monny){Log.e("testthread",Thread.currentThread().getName()+"取钱成功:"+monny);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}setBalance(balance - monny);}else {Log.e("testthread",Thread.currentThread().getName()+"取钱失败");}Log.e("testthread","余额:"+getBalance());}
}@Overridepublic void run() {super.run();account.draw(800);}

说明

  1. synchronized 要写在返回值的前面
  2. 因为draw()用synchronized修饰了,同步方法的同步监视器总是 this,而this指的是调用这个方法的对象。在上面的代码中,调用draw()的是draw,因此多个线程并发修改account的时候,要先对account对象进行加锁。

四、释放同步监视器的锁定

  • 以下几种情况会释放同步监视器:
  1. 当同步代码块或同步方法执行完了,会释放;
  2. 当同步代码块或同步方法中遇到了break、return 进行终止,会释放
  3. 当同步代码块或同步方法中有未处理的Error或Exception,导致了异常退出,会释放
  4. 当执行了同步监视器的ewait()方法,当前线程会暂停,并释放
  • 以下几种情况不会释放:
  1. 在同步代码块或同步方法执行的时候,程序调用了Thread.sleep()或Thread.yield()来暂停当前线程,则不会释放
  2. 在同步代码块或同步方法执行的时候,程序调用了suspend使线程挂起了,则不会释放。应该尽量避免使用suspend和resume来控制线程

五、同步锁(Lock)

  • Lock 是sychronized的升级版,有跟广泛的锁定操作
  • Lock是控制多个线程对共享资源进行访问的工具,提供了对共享资源的独占访问
  • java提供了两个根接口:
    • Lock---->实现类:ReentranLock(可重入锁)
    • ReadWriteLock—>实现类:ReentrantReadWiteLock

可重用性
一个线程可以对已经加锁的ReentranLock再次加锁,线程每次调用lock()加锁后,都必须显示调用unlock()释放锁

使用格式:

class A{ReentrantLock lock = new ReentrantLock();void fun(){//加锁lock.lock();try {//需要保证线程安全的代码// .....}finally {// 释放锁lock.unlock();}}
}

以上取钱的代码进行优化:

public class Account {// 银行账户private String accountNo;//余额private int balance;ReentrantLock lock = new ReentrantLock();public void drawmonny(int monny){// 加锁lock.lock();try {if (balance >= monny){Log.e("testthread",Thread.currentThread().getName()+"取钱成功:"+monny);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}setBalance(balance - monny);}else {Log.e("testthread",Thread.currentThread().getName()+"取钱失败");}Log.e("testthread","余额:"+getBalance());}finally {// 释放锁lock.unlock();}}
}

六、死锁(互相等待释放同步监视器)

当两个线程相互等待对方释放同步监视器的时候就会发生死锁,死锁发生的时候既不会发生异常,也不会给任何提示,所以要尽量避免死锁。当系统中有多个同步监视器的时候就很容易发生死锁。

public class A{public synchronized void funA(B b){Log.e("testthread",Thread.currentThread().getName()+"进入A的fun方法");try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}Log.e("testthread",Thread.currentThread().getName()+"想进B的last方法");b.lashB();}public synchronized void lastA(){Log.e("testthread",Thread.currentThread().getName()+"进入A的last方法");}}public class B{public synchronized void funB(A a){Log.e("testthread",Thread.currentThread().getName()+"进入B的fun方法");try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}Log.e("testthread",Thread.currentThread().getName()+"想进A的last方法");a.lastA();}public synchronized void lashB(){Log.e("testthread",Thread.currentThread().getName()+"进入B的last方法");}}public class DeadLock implements Runnable{A a = new A();B b = new B();void init(){Thread.currentThread().setName("主线程");a.funA(b);}@Overridepublic void run() {b.funB(a);}}
mBntFun5.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {DeadLock lock = new DeadLock();new Thread(lock,"子线程").start();lock.init();}});

代码解释:
因为funA()和funB()都是同步方法,当a和b调用他们的时候,就要对a和b加锁。而A、B中各自sleep(200)后,开始继续执行,这是A中要调用B的lastB()方法,这时候就要对B加锁,但是这时候B的锁并没有释放,同理B中也是这样的情况,所以双方就一直在等待,造成了死锁。

说明
suspend很容易造成死锁,尽量不要使用它来暂停线程

JAVA | 线程(三)线程同步(重要)相关推荐

  1. Java多线程:多线程同步安全问题的 “三“ 种处理方式 ||多线程 ”死锁“ 的避免 || 单例模式”懒汉式“的线程同步安全问题

    Java多线程:多线程同步安全问题的 "三" 种处理方式 ||多线程 "死锁" 的避免 || 单例模式"懒汉式"的线程同步安全问题 每博一文 ...

  2. java多线程三之线程协作与通信实例

    多线程的难点主要就是多线程通信协作这一块了,前面笔记二中提到了常见的同步方法,这里主要是进行实例学习了,今天总结了一下3个实例: 1.银行存款与提款多线程实现,使用Lock锁和条件Condition. ...

  3. Java多线程02(线程安全、线程同步、等待唤醒机制)

    Java多线程2(线程安全.线程同步.等待唤醒机制.单例设计模式) 1.线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量 ...

  4. java 什么是线程同步,java多线程同步集合是什么?并发集合是什么?

    java中关于集合的内容也是十分丰富的,而且相关的知识点也是十分多的.多线程集合所涵盖的范围是十分广阔的.今天就来为大家介绍一下,java多线程同步集合是什么以及并发集合是什么?一起来看看吧. 首先我 ...

  5. java day17 【线程、同步】

    第一章 线程 1.1 多线程原理 昨天的时候我们已经写过一版多线程的代码,很多同学对原理不是很清楚,那么我们今天先画个多线程执行时序图来体现一下多线程程序的执行流程. 代码如下: 自定义线程类: pu ...

  6. JAVA-多线程 三 {多线程状态}JAVA从基础开始 -- 3

    JAVA-多线程 三 {多线程状态}(JAVA从基础开始 -- 3 线程状态 停止方法_stop (舍弃) 休眠状态_sleep 线程礼让_yield 线程强制执行_ join 线程状态观测_Thre ...

  7. java多线程:线程同步synchronized(不同步的问题、队列与锁),死锁的产生和解决

    0.不同步的问题 并发的线程不安全问题: 多个线程同时操作同一个对象,如果控制不好,就会产生问题,叫做线程不安全. 我们来看三个比较经典的案例来说明线程不安全的问题. 0.1 订票问题 例如前面说过的 ...

  8. java多线程交替打印_使用Java实现三个线程交替打印0-74

    使用Java实现三个线程交替打印0-74 题目分析 三个线程交替打印,即3个线程是按顺序执行的.一个线程执行完之后,唤醒下一个线程,然后阻塞,等待被该线程的上一个线程唤醒.执行的顺序是一个环装的队列 ...

  9. Java多线程之线程同步机制(锁,线程池等等)

    Java多线程之线程同步机制 一.概念 1.并发 2.起因 3.缺点 二.三大不安全案例 1.样例一(模拟买票场景) 2.样例二(模拟取钱场景) 3.样例三(模拟集合) 三.同步方法及同步块 1.同步 ...

  10. 学习java的第四十天,线程的优先级、守护线程、线程同步机制、死锁

    一.线程的优先级(priority) Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行. 线程的优先级用数字表示,范围1~10 Thr ...

最新文章

  1. 【imx6】libipu.so.0说明
  2. 常见java相关问题
  3. 艾伟也谈项目管理,在团队中如何推行一项新的实践
  4. 对java集合类的认识——基础很重要
  5. verilog中的代码使用
  6. oracle基础学习---------1
  7. 永恒python配合什么主武器_让Python代码更易维护的七种武器
  8. 定义一个复数类Complex,重载运算符“+”,
  9. 【车牌识别】基于matlab GUI模拟停车位管理系统【含Matlab源码 898期】
  10. java 29期淘淘商城_JavaEE大型分布式电商项目 淘淘商城 29期
  11. 图解谷歌浏览器Chrome的Logo_longware_新浪博客
  12. [树状数组模板] 洛谷P3368
  13. 为什么计算机不显示桌面工具栏,笔记本电脑开机后不显示桌面图标或任务栏怎么解决...
  14. IE浏览器主页被劫持,如何解决主页被篡改问题?
  15. un7.28:redis客户端常用命令。
  16. SSO: Basic-Auth OAuth2 SAML OpeanID
  17. IO流_IO流概述及分类
  18. 【进程、线程和进程间通信】(三)进程间通信
  19. 微信第三方开发行业解决方案
  20. InGaAs APD阵列的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告

热门文章

  1. 图神经网络基础——(一)图论基础
  2. 信息监控——为Web2.0时代解决新问题
  3. 计算机本地硬盘带蓝色问号,本地磁盘出现个问号是什么原因
  4. 基于关系推理的无标记自监督学习训练
  5. 2021云南高考成绩查询系统入口9,云南招生考试工作网高考成绩查询系统入口2021...
  6. 梦幻星空html,制作梦幻星空效果图的滤镜教程
  7. 让代码和迈克杰克逊一起跳舞
  8. 關於python 2.x中文字編碼的簡單說明
  9. 如何从x书搬家到掘金
  10. 终于有人把EMC基础知识总结如此清晰