多线程总结四:线程同步(一)

1、线程安全问题

a、银行取钱问题:取钱时银行系统判断账户余额是否大于取款金额,如果是,吐出钞票,修改余额。这个流程在多线程并发的场景下就可能会出现问题。

 1 /**
 2  * @Title: Account.java
 3  * @Package
 4  * @author 任伟
 5  * @date 2014-12-8 下午5:35:27
 6  * @version V1.0
 7  */
 8
 9 /**
10  * @ClassName: Account
11  * @Description: 账户类
12  * @author 任伟
13  * @date 2014-12-8 下午5:35:27
14  */
15 public class Account {
16     private String accountNo; // 账户编号
17     private double balance; // 账户余额
18
19     public String getAccountNo() {
20         return accountNo;
21     }
22
23     public void setAccountNo(String accountNo) {
24         this.accountNo = accountNo;
25     }
26
27     public double getBalance() {
28         return balance;
29     }
30
31     public void setBalance(double balance) {
32         this.balance = balance;
33     }
34
35     public Account() {
36         super();
37     }
38
39     public Account(String accountNo, double balance) {
40         super();
41         this.accountNo = accountNo;
42         this.balance = balance;
43     }
44
45     public boolean equals(Object anObject) {
46         if(this==anObject)
47             return true;
48         if(anObject!=null && anObject.getClass()==Account.class){
49             Account target = (Account) anObject;
50             return target.getAccountNo().equals(accountNo);
51         }
52         return false;
53     }
54
55     public int hashCode() {
56         return accountNo.hashCode();
57     }
58
59 }

Account

 1 /**
 2  * @Title: DrawThread.java
 3  * @Package
 4  * @author 任伟
 5  * @date 2014-12-8 下午5:41:46
 6  * @version V1.0
 7  */
 8
 9 /**
10  * @ClassName: DrawThread
11  * @Description: 取钱类
12  * @author 任伟
13  * @date 2014-12-8 下午5:41:46
14  */
15 public class DrawThread extends Thread {
16     private Account account; // 用户帐户
17     private double drawAmount; // 希望取的钱数
18
19     public DrawThread(String name, Account account, double drawAmount) {
20         super(name);
21         this.account = account;
22         this.drawAmount = drawAmount;
23     }
24
25     //当多个线程修改同一共享数据时,将涉及线程安全问题
26     /* (non-Javadoc)
27      * @see java.lang.Thread#run()
28      */
29     @Override
30     public void run() {
31         if(account.getBalance()>=drawAmount){
32             System.out.println(this.getName()+"取钱成功,吐出钞票:"+drawAmount);
33             try {
34                 Thread.sleep(1);
35             } catch (InterruptedException e) {
36                 // TODO Auto-generated catch block
37                 e.printStackTrace();
38             }
39             account.setBalance(account.getBalance()-drawAmount);
40             System.out.println("余额为:"+account.getBalance());
41         }else{
42             System.out.println(this.getName()+"取钱失败!余额不足!");
43         }
44     }
45
46     public static void main(String[] args) {
47         //创建一个账户
48         Account acct = new Account("1234567", 1000);
49         //模拟两个线程对同一个账户取钱
50         new DrawThread("甲", acct, 800).start();
51         new DrawThread("乙", acct, 800).start();
52     }
53
54 }

DrawThread

运行结果:

2、同步代码块

a、Java多线程引入了同步监视器来解决这个问题:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,在同步代码块执行完以后,该线程会释放对该同步监视器的锁定。通常使用可能被并发访问的共享资源充当同步监视器。

 1 synchronized(obj){//obj同步监视器 2 //同步代码块 3 } 

 1 /**
 2  * @Title: SynchronizedBlockDrawThread.java
 3  * @Package
 4  * @author 任伟
 5  * @date 2014-12-8 下午5:59:06
 6  * @version V1.0
 7  */
 8
 9 /**
10  * @ClassName: SynchronizedBlockDrawThread
11  * @Description: 使用Account作为同步监视器
12  * @author 任伟
13  * @date 2014-12-8 下午5:59:06
14  */
15 public class SynchronizedBlockDrawThread extends Thread {
16     private Account account; // 用户帐户
17     private double drawAmount; // 希望取的钱数
18
19     public SynchronizedBlockDrawThread(String name, Account account,
20             double drawAmount) {
21         super(name);
22         this.account = account;
23         this.drawAmount = drawAmount;
24     }
25
26     // 当多个线程修改同一共享数据时,将涉及线程安全问题
27     /*
28      * (non-Javadoc)
29      *
30      * @see java.lang.Thread#run()
31      */
32     @Override
33     public void run() {
34         synchronized (account) {
35             if (account.getBalance() >= drawAmount) {
36                 System.out.println(this.getName() + "取钱成功,吐出钞票:" + drawAmount);
37                 try {
38                     Thread.sleep(1);
39                 } catch (InterruptedException e) {
40                     // TODO Auto-generated catch block
41                     e.printStackTrace();
42                 }
43                 account.setBalance(account.getBalance() - drawAmount);
44                 System.out.println("余额为:" + account.getBalance());
45             } else {
46                 System.out.println(this.getName() + "取钱失败!余额不足!");
47             }
48         }
49     }
50
51     public static void main(String[] args) {
52         // 创建一个账户
53         Account acct = new Account("1234567", 1000);
54         // 模拟两个线程对同一个账户取钱
55         new SynchronizedBlockDrawThread("甲", acct, 800).start();
56         new SynchronizedBlockDrawThread("乙", acct, 800).start();
57     }
58
59 }

SynchronizedBlockDrawThread

运行结果:

 

3、同步方法
a、使用synchronized关键字来修饰某一个方法,使用this作为同步监视器,也就是调用该方法的对象。
将Account变为线程安全的类:

 1 /**
 2  * @Title: Account2.java
 3  * @Package
 4  * @author 任伟
 5  * @date 2014-12-8 下午6:50:34
 6  * @version V1.0
 7  */
 8
 9 /**
10  * @ClassName: Account2
11  * @Description: 线程安全Account类
12  * @author 任伟
13  * @date 2014-12-8 下午6:50:34
14  */
15 public class Account2 {
16     private String accountNo; // 账户编号
17     private double balance; // 账户余额
18
19     public Account2() {
20         super();
21     }
22
23     public Account2(String accountNo, double balance) {
24         super();
25         this.accountNo = accountNo;
26         this.balance = balance;
27     }
28
29     public boolean equals(Object anObject) {
30         if(this==anObject)
31             return true;
32         if(anObject!=null && anObject.getClass()==Account.class){
33             Account target = (Account) anObject;
34             return target.getAccountNo().equals(accountNo);
35         }
36         return false;
37     }
38
39     public int hashCode() {
40         return accountNo.hashCode();
41     }
42
43     public synchronized void draw(double drawAmount){
44         if (balance >= drawAmount) {
45             System.out.println(Thread.currentThread().getName() + "取钱成功,吐出钞票:" + drawAmount);
46             try {
47                 Thread.sleep(1);
48             } catch (InterruptedException e) {
49                 // TODO Auto-generated catch block
50                 e.printStackTrace();
51             }
52             balance -= drawAmount;
53             System.out.println("余额为:" + balance);
54         } else {
55             System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足!");
56         }
57     }
58 }

Account2

 1 /**
 2  * @Title: DrawThread2.java
 3  * @Package
 4  * @author 任伟
 5  * @date 2014-12-8 下午6:54:59
 6  * @version V1.0
 7  */
 8
 9 /**
10  * @ClassName: DrawThread2
11  * @Description: 取钱类
12  * @author 任伟
13  * @date 2014-12-8 下午6:54:59
14  */
15 public class DrawThread2 extends Thread {
16     private Account2 account; // 用户帐户
17     private double drawAmount; // 希望取的钱数
18
19     public DrawThread2(String name, Account2 account, double drawAmount) {
20         super(name);
21         this.account = account;
22         this.drawAmount = drawAmount;
23     }
24
25     /*
26      * (non-Javadoc)
27      *
28      * @see java.lang.Thread#run()
29      */
30     @Override
31     public void run() {
32         account.draw(drawAmount);
33     }
34
35     public static void main(String[] args) {
36         // 创建一个账户
37         Account2 acct = new Account2("1234567", 1000);
38         // 模拟两个线程对同一个账户取钱
39         new DrawThread2("甲", acct, 800).start();
40         new DrawThread2("乙", acct, 800).start();
41     }
42 }

DrawThread2

运行结果:

4、延伸
a、synchronized关键字。可以修饰方法,可以修饰代码块,但不能修饰构造器、成员变量等。

b、Domain Driven Design:在面向对象里有一种设计方法:Domain Driven Design(领域驱动设计,DDD),这种方式认为每个类都应该是完备的领域对象,例如Account代表用户帐户,应该提供用户帐户的相关方法;通过draw()方法来执行取钱操作,而不是将setBalance()方法暴露出来任人操作,才能更好地保证Account对象的完整性和一致性。

c、线程安全类特征:通过同步方法可以非常方便的实现线程安全的类,线程安全的类具有如下特征:
.该类的对象可以被多个线程安全地访问;
.每个线程调用该类的任意方法后都能得到正确的结果;
.每个线程调用该类的任意方法后,该对象状态依然保持合理的状态;

d、可变类和不可变类(Mutable and Immutable Objects):
可变类:当你获得这个类的一个实例引用时,你可以改变这个实例的内容。
不可变类:当你获得这个类的一个实例引用时,你不可以改变这个实例的内容。不可变类的实例一但创建,其内在成员变量的值就不能被修改。

e、如何创建一个自己的不可变类:
.所有成员都是private
.不提供对成员的改变方法,例如:setXXXX
.确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有类方法加上final(弱不可变类)。
.如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过在成员初始化(in)或者get方法(out)时通过深度clone方法,来确保类的不可变。

f、不可变类总是线程安全的,因为对象状态不可变;可变类需要额外的方法保证其线程安全。

5、线程安全与运行效率
可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,可采用以下策略:
a、不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(共享资源)的方法同步;
b、如果可变类有两种运行环境:单线程环境和多线程环境,这应该提供该类的线程不安全版本和线程安全版本;

时间: 2024-10-12 22:14:46

多线程总结四:线程同步(一)的相关文章

.NET面试题解析(07)-多线程编程与线程同步

系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等,本文只是从常见面试题的角度(也是开发过程中常用)去深入浅出线程相关的知识.如果想要系统的学习多线程,没有捷径的,也不要偷懒,还是去看专业书籍的比较好. 常见面试题目: 1. 描述线程与进程的区别? 2. 为什么GUI不支持跨线程访问控件?一般如何解决这个问题? 3. 简述后台线程和前台线程的区别? 4. 说说常

Linux程序设计学习笔记----多线程编程之线程同步之条件变量

转载请注明出处:http://blog.csdn.net/suool/article/details/38582521. 基本概念与原理 互斥锁能够解决资源的互斥访问,但是在某些情况下,互斥并不能解决问题,比如两个线程需 要互斥的处理各自的操作,但是一个线程的操作仅仅存在一种条件成立的情况下执行,一旦错过不可再重现,由于线程间相互争夺cpu资源,因此在条件成立的时候,该线程不一定争夺到cpu而错过,导致永远得不到执行..... 因此需要某个机制来解决此问题,更重要的是,线程仅仅只有一种情况需要执

说说C# 多线程那些事-线程同步和线程优先级

上个文章分享了一些多线程的一些基础的知识,今天我们继续学习. 努力学习,成为最好的自己. 一.Task类 上次我们说了线程池,线程池的QueueUserWorkItem()方法发起一次异步的线程执行很简单 但是该方法最大的问题是没有一个内建的机制让你知道操作什么时候完成,有没有一个内建的机制在操作完成后获得一个返回值.为此,可以使用System.Threading.Tasks中的Task类. 简单代码实现: using System; using System.Threading.Tasks;

C# 多线程编程第二步——线程同步与线程安全

上一篇博客学习了如何简单的使用多线程.其实普通的多线程确实很简单,但是一个安全的高效的多线程却不那么简单.所以很多时候不正确的使用多线程反倒会影响程序的性能. 下面先看一个例子 : class Program { static int num = 1; static void Main(string[] args) { Stopwatch stopWatch = new Stopwatch(); //开始计时 stopWatch.Start(); ThreadStart threadStart

Java多线程与并发——线程同步

1.多线程共享数据 在多线程的操作中,多个线程有可能同时处理同一个资源,这就是多线程中的共享数据. 2.线程同步 解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行. 线程进行同步,有以下两种方法: (1)同步代码块 synchronized(要同步的对象){ 要同步的操作; } (2)同步方法 public synchronized void method(){ 要同步的操作; } (3)Lock 3.同步

java多线程二之线程同步的三种方法

java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题.java在处理线程同步时,常用方法有: 1.synchronized关键字. 2.Lock显示加锁. 3.信号量Semaphore. 线程同步问题引入: 创建一个银行账户Account类,在创建并启动100个线程往同一个Account类实例里面添加一块钱.在没有使用上面三种方法的情况下: 代码: 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

Delphi中线程类TThread实现多线程编程(线程同步技术、Synchronize、WaitFor……)

接着上文介绍TThread. 现在开始说明 Synchronize和WaitFor 但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区 事件(Event)与Delphi中的事件有所不同.从本质上讲,Event其实就相当于一个全局的布尔变量.它有两个赋值操作:Set和ReSet,相当于把它设置为 True或False.而检查它的值是通过WaitFor操作进行.对应在Windows平台上,是三个API函数:SetEvent.ResetEvent.WaitForSignalObje

Linux多线程实现及线程同步函数分析

在Linux中,多线程的本质仍是进程,它与进程的区别: 进程:独立地址空间,拥有PCB 线程:也有PCB,但没有独立的地址空间(共享) 线程的特点: 1,线程是轻量级进程,有PCB,创建线程使用的底层函数和进程一样,都是clone 2,从内核看进程和线程是一样的,都有各自不同的PCB 3,进程可以蜕变成线程 4,在LINUX中,线程是最小的执行单位,进程是最小的分配资源单位 查看指定线程的LWP号命令: ps -Lf pid 线程优点: 提高程序并发性 开销小 数据通信,共享数据方便 线程缺点:

多线程--懒汉式的线程同步安全问题

package cn.zz; /** * * @author Administrator 饿汉式: class single{private static Single s=new Single(); private Single(){ } public static getInstance(){ return s; } } * * * * * * * * * */class Single { // 懒汉式的线程同步安全的问题:用同步锁(同步代码块 同步方法) 当用多线程进行访问时 同步方法会很

多线程-2(线程同步)

带着问题去思考!大家好.今天我们来了解下什么是线程同步? 首先我们先知道这些概念和一些类: 执行基本的原子性 Mutex类 SemaphoreSlim类 AutoResetEvent类 ManualRestEventSlim类 CountDownEvent类 Barrier类 ReaderWriterLockSilm类 SpinWait类 我们都知道确保当一个线程使用某些资源的时候,同时其他线程无法使用该资源.这引入一个概念是共享资源. 多个线程同时使用共享对象会造成很多问题.同步线程使得对共享