Thread详解5:synchronized的使用(一)
2016-03-28 00:39
393 查看
1 为什么要使用synchronized
我们首先编写一个非常简单的多线程的程序,是模拟银行中的多个线程同时对同一个储蓄账户进行存款、取款操作的。在程序中我们使用了一个简化版本的Account类,代表了一个银行账户的信息。在主程序中我们首先生成了1000个线程,然后启动它们,每一个线程都对John的账户进行存100元,然后马上又取出100元。这样,对于John的账户来说,最终账户的余额应该是还是1000元才对。然而运行的结果却超出我们的想像,首先来看看我们的演示代码:
Account.java
package medium; class Account { String name; float amount; public Account(String name, float amount) { this.name = name; this.amount = amount; } /** * 存钱 * 之所以要把对amount的运算使用一个临时变量首先存储,sleep一段时间,然后,再赋值给amount, * 是为了模拟真实运行时的情况。因为在真实系统中,账户信息肯定是存储在持久媒介中, * 比如RDBMS中,此处的睡眠的时间相当于比较耗时的数据库操作, * 最后把临时变量tmp的值赋值给amount相当于把amount的改动写入数据库中。 * */ public void deposit(float amt) { float tmp = amount; tmp += amt; try { Thread.sleep(100);// 模拟其它处理所需要的时间,比如刷新数据库等 } catch (InterruptedException e) { e.printStackTrace(); } amount = tmp; } /** * 取钱 * */ public void withdraw(float amt) { float tmp = amount; tmp -= amt; try { Thread.sleep(100);// 模拟其它处理所需要的时间,比如刷新数据库等 } catch (InterruptedException e) { e.printStackTrace(); } amount = tmp; } /** * 查询账户余额 * */ public float getBalance() { return amount; } }
AccountTest.java
package medium; public class AccountTest { // 线程的数量 private static int NUM_OF_THREAD = 8888; static Thread[] threads = new Thread[NUM_OF_THREAD]; public static void main(String[] args) { // 账户名为 John,里面有1000元 final Account acc = new Account("John", 1000.0f); // 用for循环新建 NUM_OF_THREAD 个线程,每个线程都是一新建就立刻启动,然后run都是存100再取100 for (int i = 0; i < NUM_OF_THREAD; i++) { threads[i] = new Thread(new Runnable() { public void run() { acc.deposit(100.0f); acc.withdraw(100.0f); } }); threads[i].start(); } // 主线程等 NUM_OF_THREAD 个线程全部运行完才去查询余额,我们期待的是1000 for (int i = 0; i < NUM_OF_THREAD; i++) { try { threads[i].join(); // 等待所有线程运行结束 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Finally, John's balance is:" + acc.getBalance()); } }
运行结果:每次都不一样
Finally, John's balance is:1500.0 Finally, John's balance is:1600.0 Finally, John's balance is:2100.0 ……
为什么会出现这样的问题?这就是多线程中的同步的问题。在我们的程序中,Account中的amount会同时被多个线程所访问,这就是一个竞争资源,通常称作竞态条件。对于这样的多个线程共享的资源我们必须进行同步,以避免一个线程的改动被另一个线程所覆盖。
2 给对象加锁
再详细一点解释。上面的 deposit 和 withdraw 都是三个步骤:从数据库读数据(读amount)保存到零时变量;
利用零时变量进行操作,比如用户存100元,整个操作过程花了5分钟;
将新的 amount 存入数据库。
实际上,上面三个步骤都是要花一定时间的。举个形象一点的例子:
如果线程A(手机端)和B(PC端)现在几乎同时读取了amount ,amount 为1000;
A向账户转账100元,此时tmp = 1100,然后写入了数据库,amount变为 1100;
接着,B向账户转账200元,此时tmp = 1200,然后写入数据库,amount变为 1200。但是实际上,我们都知道amount应该变为1300才正确。
那么我们对deposit 和 withdraw 进行同步,也就是加synchronized原语,也就是让一个线程在调用这个Account对象时,其他所有的线程都不能调用该对象, 它将所有线程串行化。所以,【synchronized加锁的是对象,而不是代码】。我们来测试一下:
…… // public void deposit(float amt) { synchronized public void deposit(float amt) { float tmp = amount; tmp += amt; try { Thread.sleep(100);// 模拟其它处理所需要的时间,比如刷新数据库等 } catch (InterruptedException e) { e.printStackTrace(); } amount = tmp; } /** * 取钱 * */ // public void withdraw(float amt) { synchronized public void withdraw(float amt) { float tmp = amount; tmp -= amt; try { Thread.sleep(100);// 模拟其它处理所需要的时间,比如刷新数据库等 } catch (InterruptedException e) { e.printStackTrace(); } amount = tmp; } ……
输出:
Finally, John's balance is:1000.0
但是我们有一个明显的感受,就是程序的执行变得很慢,而且一开始还有内存的溢出的错误,我不得不将线程数量从我喜欢的 8888 改成了 100 !它还运行了好一会儿。(博主的本本是Macbook Pro 8G/256G 2.7 GHz Intel Core i5 )。所以,解决办法肯定不是这么简单的,后面会讲到。
3 给类加锁
如果两个线程,分别访问两个不同的对象实例,则它们之间没法同步的,所以这里介绍一下给类加同步的方法: 对公共成员加锁。例如给一个类添加一个静态成员,两个实例都可以同步这个对象而达到线程安全。下面举一个对比的例子。非类同步的代码,新建两个线程,则两个线程并发执行:
public class Test1 extends Thread{ private int val; private static Object object = new Object(); public Test1(int val) { this.val = val; } public void printVal() { // 非 类同步的方法 for(int i = 0; i<20; i++){ System.out.println(val + ""); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void run() { super.run(); printVal(); } public static void main(String[] args) { Test1 t1 = new Test1(6); Test1 t2 = new Test1(8); t1.start(); t2.start(); } }
输出
6886866868866868686886866868688686686868
类同步的代码,所以两个线程是串行执行的:
public class Test1 extends Thread{ private int val; private static Object object = new Object(); public Test1(int val) { this.val = val; } public void printVal() { // 类同步的方法 synchronized (object) { // 非 类同步的方法 for(int i = 0; i<20; i++){ System.out.print(val + ""); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override public void run() { super.run(); printVal(); } public static void main(String[] args) { Test1 t1 = new Test1(6); Test1 t2 = new Test1(8); t1.start(); t2.start(); } }
输出
6666666666666666666688888888888888888888
相关文章推荐
- 使用 Syncthing 在多个设备间同步文件
- C#实现多线程的同步方法实例分析
- 科学知识:同步、异步、阻塞和非阻塞区别
- 同步文件备份工具 Super Flexible File Synchronizer Pro v4
- 探讨Ajax中同步与异步之间的区别
- C#线程同步的三类情景分析
- C++使用CriticalSection实现线程同步实例
- 基于C#实现的多生产者多消费者同步问题实例
- ASP.NET之自定义同步HTTP处理程序(图文教程)
- C#中线程同步对象的方法分析
- Jquery ajax 同步阻塞引起的UI线程阻塞问题
- jQuery实现文本框输入同步的方法
- jQuery中$.ajax()和$.getJson()同步处理详解
- MSSQL自动同步设置方法
- .NET中保证线程安全的高级方法Interlocked类使用介绍
- 一个进程间通讯同步的C#框架引荐
- jQuery中的ajax async同步和异步详解
- Java中使用synchronized关键字实现简单同步操作示例
- mysql同步复制搭建方法指南详细步骤
- mysql 触发器实现两个表的数据同步