写出线程同步相关的方法,以银行账号存储款为例

简介:

一.该面试题主要考察多线程中的synchronized或者Lock的使用

 * 线程同步 :使用同步方法,实现线程同步
 * 同步synchronized方法的对象监视锁为this,当前对象
 * 多个线程使用同一把锁,如果线程安全必需确保:多个线程使用的是同一个this对象(Runnable适用于共享同一对象[如:this],如果Thread继承就会有问题[推荐使用Runnable])
 * 所有访问此对象方法的线程都在方法外等待,都会判断同步锁,降低效率,但确保线程安全问题
 * java的每个对象都有一个内置锁,当用synchronized关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,线程需要获得内置锁,否则就处于阻塞状

synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

此例子中使用synchronized关键字:

private synchronized void makeWithdraw(int amount){...}

二.上代码

BankAccount.java

复制代码
 1 public class BankAccount {
 2     //余额
 3     private int balance = 500;
 4     //查询
 5     public int getBalance(){
 6         return balance;
 7     }
 8     //取款
 9     public void withdraw(int amount){
10         balance = balance - amount;
11     }
12     //存款
13     public void deposit(int amount){
14         balance = balance + amount;
15     }
16 }
复制代码

 SyncMethod.java

复制代码
 1 /**
 2  * 此线程类实现Runnable接口
 3  * 线程同步 :使用同步方法,实现线程同步
 4  * 同步synchronized方法的的对象监视锁为this,当前对象
 5  * 多个线程使用同一把锁,如果线程安全必需确保:多个线程使用的是同一个this对象
 6  * 所有访问此对象方法的线程都在方法外等待,都会判断同步锁,降低效率,但确保线程安全问题
 7  * */
 8 public class SyncMethod implements Runnable {
 9     // 所有Thread多线程线程都共享Runnable(接口对象)和account对象
10     private BankAccount account = new BankAccount();
11     @Override
12     public void run() {
13         for (int i = 0; i < 5; i++) {// 总共取款5次
14             makeWithdraw(100); // 每次取款100
15             if (account.getBalance() < 0) {
16                 System.out.println("" + Thread.currentThread().getName()+ "   透支了!");
17             }
18         }
19     }
20 
21     /**
22      * makeWithdraw 账户取款
23      * @param amount取款金额
24      * 打印log记录取款过程
25      * */
26     private synchronized void makeWithdraw(int amount){
27         if(account.getBalance() >= amount){//如果余额足够则取款
28             System.out.println("" + Thread.currentThread().getName() + "   准备取款!");
29             try {
30                 Thread.sleep(500);
31             } catch (InterruptedException e) {
32                 System.out.println(Thread.currentThread().getName() + "   准备取款,等待0.5s线程中断!" + e.getMessage());
33             }
34             account.withdraw(amount);
35             System.out.println("" + Thread.currentThread().getName() + "   完成" + amount + "取款!余额为" + account.getBalance());
36         }else{//余额不足则提示
37             System.out.println("" + "余额不足以支付" + Thread.currentThread().getName() + amount + "   的取款,余额为" + account.getBalance());
38         }
39     }
40 }
复制代码

TreadSyncTest.java

复制代码
 1 public class TreadSyncTest {
 2 
 3     /*
 4         Junit不适合多线程并发测试。
 5         因为线程还在激活状态的时候,Junit已经执行完成。
 6         在Junit的TestRunner中,它没有被设计成搜寻Runnable实例,
 7         并且等待这些线程发出报告,它只是执行它们并且忽略了它们的存在。
 8         综上,不可能在Junit中编写和维护多线程的单元测试。
 9      */    
10     /*
11         @Test
12         public void test() {
13         }
14     */
15     
16     public static void main(String[] args) {
17         //实现Runnable:所有Thread多线程线程都共享Runnable(接口对象)
18         //NoSync target =new NoSync();
19         SyncMethod target = new SyncMethod();
20         //创建罗密欧和朱丽叶两个线程实现取款(同时)
21         Thread romeoThread = new Thread(target);
22         romeoThread.setName("罗密欧");
23         Thread julietThread = new Thread(target);
24         julietThread.setName("朱丽叶");
25         //调用Thread对象的start()方法,启动线程,执行run()方法(OS)
26         romeoThread.start();
27         julietThread.start();
28     }
29 }
复制代码

 运行结果:

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
正常运行结果 1 :
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 400
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 300
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 200
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 100
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0 .
 
正常运行结果 2 :
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 400
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 300
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 200
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 100
朱丽叶   准备取款!
朱丽叶   完成 100 取款!余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付罗密欧 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
 
正常运行结果 3 :
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 400
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 300
罗密欧   准备取款!
罗密欧   完成 100 取款!余额为 200
朱丽叶   准备取款!
朱丽叶   完成 100 取款!余额为 100
朱丽叶   准备取款!
朱丽叶   完成 100 取款!余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付朱丽叶 100    的取款,余额为 0
余额不足以支付罗密欧 100    的取款,余额为 0
余额不足以支付罗密欧 100    的取款,余额为 0

如果不用synchronized关键字的结果:

复制代码
罗密欧   准备取款!
朱丽叶   准备取款!
朱丽叶   完成100取款!余额为300
罗密欧   完成100取款!余额为300
罗密欧   准备取款!
朱丽叶   准备取款!
朱丽叶   完成100取款!余额为100
朱丽叶   准备取款!
罗密欧   完成100取款!余额为100
罗密欧   准备取款!
罗密欧   完成100取款!余额为0
余额不足以支付罗密欧100   的取款,余额为0
余额不足以支付罗密欧100   的取款,余额为0
朱丽叶   完成100取款!余额为-100
朱丽叶   透支了!
余额不足以支付朱丽叶100   的取款,余额为-100
朱丽叶   透支了!
余额不足以支付朱丽叶100   的取款,余额为-100
朱丽叶   透支了!
复制代码
相关文章
|
4月前
|
存储 Oracle Java
|
6月前
|
Java
创建线程的方法
Java中实现多线程有四种方式:1. 继承Thread类,简单但占用继承机会,耦合度高;2. 实现Runnable接口,推荐方式,任务与线程解耦,支持Lambda;3. 实现Callable接口配合FutureTask,可获取返回值和异常;4. 使用线程池(ExecutorService),企业推荐,管理线程生命周期,提升性能,支持多种线程池类型。
153 1
|
7月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
461 5
|
监控 Java
捕获线程执行异常的多种方法
【10月更文挑战第15天】捕获线程执行异常的方法多种多样,每种方法都有其特点和适用场景。在实际开发中,需要根据具体情况选择合适的方法或结合多种方法来实现全面有效的线程异常捕获。这有助于提高程序的健壮性和稳定性,减少因线程异常带来的潜在风险。
298 57
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
267 3
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
151 2
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
198 1
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
334 1