多线程的同步
1、为什么要同步?
在多个线程并发访问同一个资源的时候,可能会出现一个线程对共享资源(临界资源)进行了修改,但是该线程还没有结束,这时另一个线程也来对这个共享资源进行修改,后果可想而知了。举个例子,假设甲和乙都用同一个账户取钱,原来账户中有余额800,甲取了800,在甲取钱之后,银行还没有计算剩下余额之前,乙跑过来取钱,这里也取800,也是可以的(余额还是800)。当甲乙两线程都结束的时候,账户余额是-800……
代码如下
//这里不考虑验证账户和密码 public class Account { // 封装账户余额 private double balance; public Account(){} // 构造器 public Account(double balance) { this.balance = balance; } // balance的setter和getter方法 public void setBalance(double balance) { this.balance = balance; } public double getBalance() { return this.balance; } }
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() { // 账户余额大于取钱数目 if (account.getBalance() >= drawAmount) { // 吐出钞票 System.out.println(getName() + "取钱成功!吐出钞票:" + drawAmount); try { Thread.sleep(1);//这里让当前线程阻塞,效果更明显 } catch (InterruptedException ex) { ex.printStackTrace(); } // 修改余额 account.setBalance(account.getalance() - drawAmount); System.out.prrintln("余额为: " +account.getBalance()); } else { System.out.println(getName() + "取钱失败!余额不足!"); } } }
public class DrawTest { public static void main(String[] args) { // 创建一个账户 Account acct = new Account(800); // 模拟两个线程对同一个账户取钱 new DrawThread("甲" , acct , 800).start(); new DrawThread("乙" , acct , 800).start(); } }
运行上面代码总是会出现余额为-800的情况,而线程的同步就是为了避免类似于上述情况的发生。
2.同步的实现方式
多线程同步的关键字就是synchronized,没错,就是这货。可能有不少童鞋经常看到这货出现在方法签名里,还有代码块也会出现这货,这两种写法有什么不同?
(1)先来看出现在代码块的那种(同步代码块)
写法如下
synchronized(Object o){
//写一些你想锁上的东西
}
o是什么?学名同步监视器,对应上面例子的acct对象,就是线程要并发访问的对象。使用了这个同步代码块就可以保证一次只能由一个线程来访问临界资源。按照上面的 例子就是在甲取钱那个方法结束之后,才能让乙来取钱。再举一个接地气的例子,就像是上厕所一样,一个厕所一次只能让一个人使用,呃..一般人都会锁门(synchronized)吧,就类似于用了上面方法进行了同步,想一下如果不锁门,是不是尴尬了( ̄▽ ̄)”。
改造一下,同步之后的代码如下:
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作为同步监视器,任何线程进入下面同步代码块之前, // 必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它 synchronized (account) { // 账户余额大于取钱数目 if (account.getBalance() >= drawAmount) { // 吐出钞票 System.out.println(getName() + "取钱成功!吐出钞票:" + drawAmount); try { Thread.sleep(1); } catch (InterruptedException ex) { ex.printStackTrace(); } // 修改余额 account.setBalance(account.getBalance() - drawAmount); System.out.println("余额为: " + account.getBalance()); } else { System.out.println(getName() + "取钱失败!余额不足!"); } } // 同步代码块结束,该线程释放同步锁 } }
(2)出现在方法签名里的(同步方法)
写法如下:
public synchronized zzz XXX(YYY yyy){
//写一些你想锁上的东西
}
谁调用这个方法谁就是同步监视器(acct对象),其实就是this啦。
前面的代码中是那个环节出了问题?对,取钱。那两个线程用一个账户访问的是取钱方法(run()),所以如果使用这种同步方法的方式进行同步,就需要改造取钱(run())方法了。
代码如下:
// 提供一个线程安全draw()方法来完成取钱操作 public synchronized void draw(double drawAmount) { // 账户余额大于取钱数目 if (balance >= drawAmount) { // 吐出钞票 System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawAmount); try { Thread.sleep(1); } catch (InterruptedException ex) { ex.printStackTrace(); } // 修改余额 balance -= drawAmount; System.out.println("余额为: " + balance); } else { System.out.println(Thread.currentThread().getName() + "` 钱失败!余额不足!"); } }
总结一下哈,个人感觉同步代码块就是对多线程并发访问的对象加锁,而同步方法,是对多线程调用的方法上锁。
以上为个人见解,如有错误,请批评指正,谢谢~!
注:代码参考自《疯狂Java讲义》
- 多线程的同步和通信
- IOS多线程学习五:GCD线程间通信+主队列添加同步任务崩溃问题
- Linux同步机制 - 多线程开发总结
- 多线程-锁的类型 && CAS 非阻塞同步
- 线程间同步与多线程
- 从头认识多线程-2.14 解决由同步的synchronized (newobject()) 引起的脏读的方法
- 多线程中的lua同步问题
- Linux — 浅析线程以及多线程的同步与互斥
- java中多线程安全问题产生&解决方案——同步方法
- HELP,多线程通过同一个socket发送数据到服务端,socket底动会不会同步呢?
- 同步、异步、多线程与事件型综述
- C#的多线程机制探索(三)—线程的同步和通讯(生产者和消费者)
- 多线程及其同步
- (转)多线程编程之四——线程的同步
- 复习基础-Java多线程synchronized同步
- 多线程间的同步
- 多线程编程13-----Exchanger同步工具类实现两线程数据交换
- 并发和并行,多线程和多进程,单核和多核,同步和异步的关系
- 剖析MFC多线程程序的同步机制
- 多线程中的互斥与同步机制