JAVA多线程--线程的同步安全

每当我们在项目中使用多线程的时候,我们就不得不考虑线程的安全问题,而与线程安全直接挂钩的就是线程的同步问题。而在java的多线程中,用来保证多线程的同步安全性的主要有三种方法:同步代码块,同步方法和同步锁。下面就一起来看:

一、引言

最经典的线程问题:去银行存钱和取钱的问题,现在又甲乙两个人去同一个账户中取款,每人取出800,但是账户中一共有1000元,从逻辑上来讲,如果甲取走800,那么乙一定取不出来800:

 1 package thread.threadInBank;
 2
 3 /**
 4  * 取钱的线程类,该线程类根据执行账户,取钱的数量进行取钱操作,取钱的逻辑是当其余额不足时无法提取现金,当余额足够时系统吐出
 5  * 钞票,余额对应的减少
 6  * @author root
 7  *
 8  */
 9 public class DrawThread extends Thread{
10
11     //模拟用户账户
12     private Account account;
13
14     //当前取钱线程所希望取出的的钱数
15     private double drawAmount;
16
17     public DrawThread(String name,Account account,double drawAmount){
18         super(name);
19         this.account = account;
20         this.drawAmount= drawAmount;
21     }
22
23     //当多条线程修改同一个共享数据时,将涉及数据安全问题
24     public void run(){
25         //账户余额大于取钱的数目
26         if (account.getBalance()  >= drawAmount) {
27             //吐出钞票
28             System.out.println(getName()+ "取钱成功,吐出钞票!" + drawAmount);
29
30 //            try {
31 //                Thread.sleep(1);
32 //            } catch (InterruptedException e) {
33 //                e.printStackTrace();
34 //            }
35     //修改余额
36     account.setBalance(account.getBalance() - drawAmount);
37     System.out.println("\t 余额为: " + account.getBalance());
38     }else {
39         System.out.println(getName() + "取钱失败,余额不足");
40     }
41     }
42 }

写一个测试方法,来测试当前的取钱操作:

 1 package thread.threadInBank;
 2
 3 public class testDraw {
 4     public static void main(String[] args) {
 5         //创建一个用户
 6         Account acct = new Account("1234567",1000);
 7         //模拟两个线程对同一个账户取钱
 8         new DrawThread("甲", acct, 800).start();
 9         new DrawThread("乙", acct, 800).start();
10
11     }
12 }

乍一看,上面的程序好像也没有什么问题,但是多次运行之后会出现下面两种结果:

         

所以这样的程序肯定存在问题,那么我们应该如何对上述程序进行更改,使得当前的账户不能取第二次800;这就必须提到java提供的线程同步的第一种方法:同步代码块

二、同步代码块

我们很容易发现上述程序出现问题是因为:当前run方法的方法体不具备同步安全性,而程序中的两个并发线程(甲乙两次取钱)都在修改该账户,所以为了解决这个问题,我们需要使用java多线程引入的同步监视器来解决,也就是将当前取钱的那段代码使用synchronized 关键字修饰,也就是使之成为同步代码块:

package thread.threadInBank;

/**
 * 取钱的线程类,该线程类根据执行账户,取钱的数量进行取钱操作,取钱的逻辑是当其余额不足时无法提取现金,当余额足够时系统吐出 钞票,余额对应的减少
 *
 * @author root
 *
 */
public class DrawThread extends Thread {

    // 模拟用户账户
    private Account account;

    // 当前取钱线程所希望取出的的钱数
    private double drawAmount;

    public DrawThread(String name, Account account, double drawAmount) {
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }

    // 当多条线程修改同一个共享数据时,将涉及数据安全问题
    public void run() {
        // 使用account作为同步监视器,任何线程进入下面的同步代码之前
        // 必须Ian获得对account账户的锁定--其他线程无法获得锁,也就无法修改它
        // 这种做法符合:加锁--》 修改完成 -- 》释放锁 的逻辑
    synchronized (account) {

    // 账户余额大于取钱的数目
    if (account.getBalance() >= drawAmount) {
        // 吐出钞票
        System.out.println(getName() + "取钱成功,吐出钞票!" + drawAmount);

    try {
        Thread.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    // 修改余额
    account.setBalance(account.getBalance() - drawAmount);
    System.out.println("\t 余额为: " + account.getBalance());
    } else {
        System.out.println(getName() + "取钱失败,余额不足");
    }
    }
    //同步代码块结束,该线程释放同步锁
    }
}

这样无论何时都会保证当前账户中无法由乙提取出第二个800,:

所以这种方法保证了当前只有一个线程可以处于临界区(修改共享资源的代码区),从而保证了线程的安全性;

三、同步方法

其实同步方法和同步代码块类似,都是使用sychronized关键字。只是这次是修饰整个方法,而这个被修饰的方法就被称为同步方法:

 1 // 使用同步方法提供一个线程安全的draw方法来完成取钱的操作
 2 public synchronized void draw(double drawAmount) {
 3     // 账户余额大于取钱的数目
 4     if (balance >= drawAmount) {
 5         // 吐出钞票
 6         System.out.println(Thread.currentThread().getName() + "取钱成功,吐出钞票!" + drawAmount);
 7
 8     try {
 9         Thread.sleep(1);
10     } catch (InterruptedException e) {
11         e.printStackTrace();
12     }
13     // 修改余额
14     balance -= drawAmount;
15     System.out.println("\t 余额为: " + balance);
16     } else {
17         System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足");
18     }
19     }

这样我们直接在测试类中调用上面的同步方法就可以了,这样可以保证多条线程并发调用draw方法且不会出现问题;

四、同步锁:Lock

其实同步锁就是显式地对当前程序加锁,而且每次只能有一个线程对lock对象加锁。所以我们使用同步锁的方法改善银行取钱问题:

 1 package thread.threadInBank;
 2
 3 import java.util.concurrent.locks.ReentrantLock;
 4
 5 /**
 6  * 模仿银行取钱的经典问题:在当前的账户类中封装账户编号和余额两个属性
 7  *
 8  * @author root
 9  *
10  */
11 public class Account_lock {
12
13     //定义锁对象
14     private final ReentrantLock lock= new ReentrantLock();
15     private String accountNo;
16     private double balance;
17
18     public Account_lock() {
19     }
20
21     // 构造器
22     public Account_lock(String accountNo, double balance) {
23         this.accountNo = accountNo;
24         this.balance = balance;
25     }
26
27      省略了属性的get和set方法
28          省略了equals和hashCode两个方法
29
30     // 使用同步方法提供一个线程安全的draw方法来完成取钱的操作
31     public  void draw(double drawAmount) {
32         //对同步锁进行加锁
33         lock.lock();
34         try {
35             // 账户余额大于取钱的数目
36             if (balance >= drawAmount) {
37                 // 吐出钞票
38                 System.out.println(Thread.currentThread().getName() + "取钱成功,吐出钞票!" + drawAmount);
39
40     try {
41         Thread.sleep(1);
42     } catch (InterruptedException e) {
43         e.printStackTrace();
44     }
45     // 修改余额
46     balance -= drawAmount;
47     System.out.println("\t 余额为: " + balance);
48     } else {
49         System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足");
50     }
51     //使用finally块来确保释放锁
52     } finally{
53         lock.unlock();
54     }
55
56     }
57 }

添加一个测试类,测试当前的取钱操作:

 1 package thread.threadInBank;
 2
 3 /**
 4  * 取钱的线程类,该线程类根据执行账户,取钱的数量进行取钱操作,取钱的逻辑是当其余额不足时无法提取现金,当余额足够时系统吐出 钞票,余额对应的减少
 5  *
 6  * @author root
 7  *
 8  */
 9 public class DrawThread extends Thread {
10     private Account_lock account_lock;
11
12     // 当前取钱线程所希望取出的的钱数
13     private double drawAmount;
14
15     public DrawThread(String name, Account_lock account_lock, double drawAmount) {
16         super(name);
17         this.account_lock = account_lock;
18         this.drawAmount = drawAmount;
19     }
20
21     // 当多条线程修改同一个共享数据时,将涉及数据安全问题
22     public void run() {
23         //直接调用使用同步锁的方法
24         account_lock.draw(drawAmount);
25         //同步代码块结束,该线程释放同步锁
26     }
27 }

可以发现,当前的方法依旧可以实现取钱成功的操作;

但是可能会有人担心,线程的同步会不会影响程序的性能?

所以我们在使用多线程的时候,要尽可能的只对会改变竞争资源的方法进行同步,并且在多线程环境中使用线程安全的版本,这样尽可能地减少多线程安全给我们带来的负面影响;

时间: 2024-12-08 16:55:55

JAVA多线程--线程的同步安全的相关文章

Java多线程-线程的同步与锁

一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏.例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. package cn.thread; public class Foo { private int x = 100; public int getX() { return x; } public int fix(int y) { x = x - y; return x; } } package cn.thread;

Java多线程-线程的同步与锁【转】

出处:http://www.cnblogs.com/linjiqin/p/3208843.html 一.同步问题提出 线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. 例如:两个线程ThreadA.ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据. package cn.thread; public class Foo { private int x = 100

[ 转载 ] Java多线程-线程的同步与锁

http://www.cnblogs.com/linjiqin/p/3208843.html 原文地址:https://www.cnblogs.com/ILoke-Yang/p/8137374.html

Java多线程——线程之间的同步

Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,以及如何使用synchronized关键字和volatile关键字. 部分内容来自以下博客: https://www.cnblogs.com/hapjin/p/5492880.html https://www.cnblogs.com/paddix/p/5367116.html https://www.cnblogs.com/paddix/p/5428507.html https://www.cnblogs.com/liu

java的线程问题同步与互斥

以前学过java的线程,但是当时对Tread的理解不是很深,对于里面的同步与互斥,生产者与消费者问题,理解的不够深入,这次又从新学习java的多线程,感觉对线程的理解更加的深入了,觉得有必要写下博客记录下. 本文原创,转载请著明:http://blog.csdn.net/j903829182/article/details/38420503 1.java实现线程的方法: 1.实现Runnable接口,重写run方法,通过Thread的start方法启动线程.这种方法可以实现资源的共享 2.继承T

java 多线程—— 线程让步

java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 java 多线程—— 线程等待与唤醒 java 多线程—— 线程让步 概述 第1 部分 yield()介绍 第2 部分 yield()示例 第3 部分 yield() 与 wait()的比较 第1 部分 yield()介绍 yield()的作用是让步.它能让当前线程由“运行状态”进入到“就绪状态”

java 多线程—— 线程等待与唤醒

java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 java 多线程—— 线程等待与唤醒 概述 第1部分 wait(), notify(), notifyAll()等方法介绍 第2部分 wait()和notify()示例 第3部分 wait(long timeout)和notify() 第4部分 wait() 和 notifyAll() 第5部分 

Java多线程——线程同步

在之前,已经学习到了线程的创建和状态控制,但是每个线程之间几乎都没有什么太大的联系.可是有的时候,可能存在多个线程多同一个数据进行操作,这样,可能就会引用各种奇怪的问题.现在就来学习多线程对数据访问的控制吧. 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问.   一.多线程引起的数据访问安全问题 下面看一个经典的问题,银行取钱的问题: 1).你有一张银行卡,里面有50

Java多线程:线程同步与关键字synchronized

一.同步的特性1. 不必同步类中所有的方法, 类可以同时拥有同步和非同步方法.2. 如果线程拥有同步和非同步方法, 则非同步方法可以被多个线程自由访问而不受锁的限制. 参见实验1:http://blog.csdn.net/huang_xw/article/details/73185613. 如果两个线程要执行一个类中的同步方法, 并且两个线程使用相同的实例来调用方法, 那么一次只能有一个线程能够执行方法, 另一个需要等待, 直到锁被释放. 参见实验2:http://blog.csdn.net/h