线程间的协调
线程间的协调
昨天重新学习了 java多线程的使用 ,多线程的难点就在线程之间的协调。在《操作系统》一课中,我们学习了进程,其实多线程和多进程一样,都会涉及到多个进程或者线程对某一资源共享访问的问题,当多个线程都需要修改这个资源的时候就会出现线程安全问题。
比如说在银行开个账户会有一个存折和一张卡,如果某一天同一时间丈夫拿着存折去柜台取钱,而妻子拿着银行卡去ATM取钱。当丈夫查询余额里面有3000元,正准备取2000元,这时候妻子也到ATM里面查询也有3000,也取2000元。其实银行不可能让我们这么做,如果这样的话那我们天天取钱去了,还搞什么工作啊。其实在丈夫查询的时候已经对该账号上了锁,另外的银行卡要取钱的话必须等待该锁被释放。下面用一个程序模拟这个例子:
1 package com.sync; 2 3 public class TestSync2 implements Runnable{ 4 public BankCard bc = new BankCard(); 5 public static void main(String[] args) { 6 TestSync2 test = new TestSync2(); 7 Thread wife = new Thread(test); 8 Thread husband = new Thread(test); 9 wife.setName("wife" ); 10 husband.setName("husband" ); 11 wife.start(); 12 husband.start(); 13 } 14 public void run() { 15 bc.getMoney(Thread.currentThread().getName(), 2000 ); 16 } 17 } 18 class BankCard{ 19 private static int money = 3000; // 模拟账户余额 20 public synchronized void getMoney(String name, int m){ 21 // synchronized(this){ 22 try { 23 Thread.sleep(1 ); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 if (money > m){ 28 System.out.println(name+"取走了"+m+"元" ); 29 money = money - m; 30 } else { 31 System.out.println("对不起,您的余额不足!" ); 32 } 33 // } 34 } 35 }
上面的例子如果在getMoney()方法上面不加synchronized关键字的话,输出结果为:
wife取走了2000元
husband取走了2000元
而加上synchronized后,输出结果为:
wife取走了2000元
对不起,您的余额不足!
上面两种情况说明,如果多个线程同时访问某个资源,而不给该资源枷锁的话,就会出现问题。而加上synchronized关键字后就可以避免这种错误发生了。它能够保证只有一个线程能够访问getMoney()这个方法,其他药访问该方法的线程必须等待。
锁住某个资源可以用synchronized关键字来修饰一个方法或者同步代码块,这样能保证同一时间只能由一个线程访问该资源。
①、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
②、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
③、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
我们都知道,操作系统中多个进程之间如果不进行协调就很容易出现死锁的情况,死锁的四个条件:互斥、占有等待、非剥夺、循环等待。我们只要破坏其中一个条件就能避免死锁发生。线程之间也容易出现死锁,下面这个例子就演示了死锁的情况:
1 package com.sync; 2 3 import com.thread.SleepTest; 4 5 6 public class TestDeadLock implements Runnable{ 7 int flag = 1 ; 8 static Object o1 = new Object(); 9 static Object o2 = new Object(); 10 public void run() { 11 System.out.println(flag); 12 if (flag == 1 ){ 13 synchronized (o1) { 14 try { 15 Thread.sleep(1000 ); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 synchronized (o2) { 20 System.out.println("1" ); 21 } 22 } 23 } 24 if (flag == 0 ){ 25 synchronized (o2) { // 锁住某个对象,相当于占有该对象不让其他人使用 26 try { 27 Thread.sleep(1000 ); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 synchronized (o1) { 32 System.out.println("0" ); 33 } 34 } 35 } 36 } 37 public static void main(String[] args) { 38 TestDeadLock t1 = new TestDeadLock(); 39 TestDeadLock t2 = new TestDeadLock(); 40 t1.flag = 1 ; 41 t2.flag = 0 ; 42 new Thread(t1).start(); 43 new Thread(t2).start(); 44 } 45 }
运行程序输出1 0后就进入死锁状态,该程序永远也不会停止,因为两个线程同时处于等待状态。线程t1锁住了o1对象,等待o2对象,而线程t2锁住o2等待o2对象,谁也不让谁,这就进入了一个循环占有等待的情况了,死锁也就出现了。
所以,如果多个线程如果不进行协调的话很容易出现死锁的问题。操作系统中使用进程调度来协调各个进程,那么java重如何对各个线程进行协调呢?
java中主要使用Object类中的wait()、notify()、notifyAll()方法来协调各个线程。典型的例子有哲学家吃饭问题、生产者和消费者问题、理发师问题。下面一个用一个例子来演示生产者和消费者问题。
问题描述:生产者负责做馒头,做好馒头后放进指定的篓子里面,消费者消费该篓子里面的馒头。篓子里只能装一定量的馒头,满了以后生产者必须进入等待状态,消费者吃完馒头后也必须进入等待状态。
1 package com.sync; 2 3 public class ProductAndConsumer { 4 public static void main(String[] args) { 5 Basket b = new Basket(); 6 Product p = new Product(b); 7 Consumer c = new Consumer(b); 8 new Thread(p).start(); 9 new Thread(c).start(); 10 } 11 } 12 13 class ManTou{ 14 int id; 15 public ManTou( int id) { 16 this .id = id; 17 } 18 @Override 19 public String toString() { 20 return "ManTou"+ id; 21 } 22 } 23 24 // 装馒头的篮子 25 class Basket{ 26 int index = 0; // 相当于栈顶指针 27 ManTou[] manTous = new ManTou[6 ]; 28 // 往篮子里面放馒头 29 public synchronized void push(ManTou m){ 30 while (index == manTous.length){ 31 try { 32 this .wait(); 33 } catch (InterruptedException e) { 34 e.printStackTrace(); 35 } 36 } 37 this .notify(); 38 manTous[index] = m; 39 index++ ; 40 } 41 // 往篮子里面取馒头 42 public synchronized ManTou pop(){ 43 while (index == 0 ){ 44 try { 45 this .wait(); 46 } catch (InterruptedException e) { 47 e.printStackTrace(); 48 } 49 } 50 this .notify(); 51 index-- ; 52 return manTous[index]; 53 } 54 } 55 // 生产者 56 class Product implements Runnable{ 57 Basket basket; 58 public Product(Basket basket) { 59 this .basket = basket; 60 } 61 public void run() { 62 for ( int i = 0; i < 20; i++ ) { 63 ManTou m = new ManTou(i); 64 basket.push(m); 65 System.out.println("生产了"+ m); 66 try { 67 Thread.sleep(1 ); 68 } catch (InterruptedException e) { 69 e.printStackTrace(); 70 } 71 72 } 73 } 74 } 75 76 // 消费者 77 class Consumer implements Runnable{ 78 Basket basket; 79 public Consumer(Basket basket) { 80 this .basket = basket; 81 } 82 public void run() { 83 for ( int i = 0; i < 20; i++ ) { 84 ManTou m = basket.pop(); 85 System.out.println("消费了"+ m); 86 try { 87 Thread.sleep(( int )(Math.random()*1000 )); 88 } catch (InterruptedException e) { 89 e.printStackTrace(); 90 } 91 } 92 } 93 }
wait()、notify()、notifyAll()方法的作用:
wait():导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
notify():唤醒在此对象监视器上等待的单个线程。
notifyAll():唤醒在此对象监视器上等待的所有线程。
wait()与sleep()的区别:
两个方法的共同点就是让当前线程进入等待状态。
不同点:
wait()之后,锁就不归我所有了,必须等醒过来后才能拥有该锁,并且必须要有人唤醒它才会醒过来
sleep()不同,锁还是归我所有,一段时间后会自动醒过来
java基础
java笔记六:线程间的协调
摘要: 昨天重新学习了java多线程的使用,多线程的难点就在线程之间的协调。在《操作系统》一课中,我们学习了进程,其实多线程和多进程一样,都会涉及到多个进程或者线程对某一资源共享访问的问题,当多个线程都需要修改这个资源的时候就会出现线程安全问题。 比如说在银行开个账户会有一个存折和一张卡,如果某一天同一时间丈夫拿着存折去柜台取钱,而妻子拿着银行卡去ATM取钱。当丈夫查询余额里面有3000元,正准备取2000元,这时候妻子也到ATM里面查询也有3000,也取2000元。其实银行不可能让我们这么做,如果这样的话那我们天天取钱去了,还搞什么工作啊。其实在丈夫查询的时候已经对该账号上了锁,另外的银行卡要... 阅读全文
posted @ 2013-04-17 21:02 刘玲 阅读(98) | 评论 (2) 编辑
java笔记五:多线程的使用
摘要: 以前学习基础的时候学习了一段时间的多线程,上课的时候老师也讲过一遍,那时候感觉学的似懂非懂。因为到现在很长一段时间没有用到多线程的知识,所以现在基本上忘了差不多了。但是下个星期要面试了,所以今天特意又研究了一下多线程,免得被问到多线程问题时什么都不记得了那就麻烦了。现在对java比较熟悉了,所以再一次学习多线程知识,感觉没有那么难了(记得刚接触多线程的时候,感觉非常吃力)。 首先讲一下进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程... 阅读全文
posted @ 2013-04-16 22:54 刘玲 阅读(609) | 评论 (1) 编辑
java笔记四:Set接口
摘要: Set不保存重复的元素。Set中最常被使用的是测试归属性,你可以很容易的询问某个对象是否在某个Set中。Set具有与Collection完全一样的接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。 实现了Set接口的主要有HashSet、TreeSet、LinkedHashSet这几个共同点就是每个相同的项只保存一份。他们也有不同点,区别如下: 1.HashSet: HashSet使用的是相当复杂的方式来存储元素的,使用HashSet能够最快的获取集合中的元素,效率非常高(以空间换时间)。会根据hashcode和equals来庞端是否是同一个对象,如果... 阅读全文
posted @ 2013-04-16 16:30 刘玲 阅读(133) | 评论 (0) 编辑
java笔记三:List接口
摘要: List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和删除元素。List与 set 不同,列表通常允许重复的元素。并且存放的顺序与插入的顺序一致。 List接口的常用方法有: add(E o) :将指定对象加入到列表中。 add(int index, E element) :将对象加入到指定位置处。 addAll(Collection<? extends E> c) :追加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序(可选操作)。 . 阅读全文
posted @ 2013-04-15 13:23 刘玲 阅读(218) | 评论 (0) 编辑
java笔记二:final关键字用法总结
摘要: 在java中,可能使用到final关键字修饰的有数据、方法和类。 一、final 修饰数据 有final修饰的数据是用来告诉编译器一块数据是恒定不变的,有时数据恒定不变是很有用的,比如: 1.一个永不改变的编译时常量。 2.一个在运行时被初始化的值,但是又不希望它被改变。 编译时常量必须是基本数据类型,并且以关键字final修饰,在对这个常量进行定义的时候必须进行赋值,并且以后不能被改变。 对于基本类型,final使数值恒定不变;而对于对象引用,final使引用恒定不变,也就是说某个引用不能再指向其他对象了,但是当前指向的这个对象自身的数据是可以改变的。 二、fina... 阅读全文
posted @ 2013-04-09 13:02 刘玲 阅读(181) | 评论 (0) 编辑
java笔记一:类成员的初始化顺序
摘要: 最近一直在看《thinking in java》一书,感觉里面东西讲的很细,很多东西都没有接触过,是值得各位java程序员仔细品味的一本好书。 今天看了关于类成员初始化那节,以前对于成员初始化顺序没有考虑那么多,没有在意初始化的顺序。今天特意仔细研究了一番。 一个类中,最首先被初始化的是静态成员,也就是有关键字static修饰的成员。只要一个类被使用也就是创建对象或者调用了该类的某个静态方法时静态成员就都会被初始化,并且静态数据在内存中只占用一份存储区域,无论创建多少个对象,静态数据被所有对象共享。 初始化静态成员后再初始化非静态成员。对于非静态成员之间,也是有初始化顺序的,变量定义... 阅读全文
posted @ 2013-04-07 12:57 刘玲 阅读(491) | 评论 (2) 编辑
Date、String、Timestamp之间的转换
摘要: 1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); 4 Date date = null; 5 String str = null; 6 7 // String转Date 8 str = "2009-01-06"; ... 阅读全文
posted @ 2012-07-13 18:58 刘玲 阅读(42) | 评论 (0) 编辑
我喜欢,驾驭着代码在风驰电掣中创造完美!我喜欢,操纵着代码在随必所欲中体验生活!我喜欢,书写着代码在时代浪潮中完成经典!每一段新的代码在我手中诞生对我来说就象观看刹那花开的感动!
分类: java基础
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息