黑马程序员-JAVA基础-多线程(下)
2013-03-18 15:21
363 查看
------- android培训、java培训、期待与您交流! ----------
5.多线程的安全问题:多线程同步
当使用多个线程同时访问一个数据时,经常会出现线程安全问题。如下面程序:
运行的结果会出现"X号窗口...卖出了第-1张票"、“X号窗口...卖出了第-2张票”这样的安全问题。
导致的原因:当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致了共享数据的错误。
解决这类问题的方法:
1、同步代码块。
2、同步函数。
5.1 同步代码块
同步代码块的格式如下:
synchronized 后括号里面的 obj 就是同步监视器。代码含义:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。即只有获得对同步监视器的锁定的线程可以在同步中执行,没有锁定的线程即使获得执行权,也不能在同步代码块中执行。
注意:虽然JAVA 程序允许使用任何对象来作为同步监视器。但是还是推荐使用可能被并发访问的共享资源来充当同步监视器。
通过修改代码如下:
加入同步监视器之后的程序就不会出现数据上的错误了。
虽然同步监视器的好处是解决了多线程的安全问题。但也也因为多个线程需要判断锁,较为消耗资源。
注意:同步的前提:
1、必须要有两个或者两个以上的线程。
2、必须是多个线程使用同一锁。
如果加入了synchronized 同步监视器,还出现了安全问题,则可以按照如下步骤找寻错误:
1、明确那些代码是多线程代码。
2、明确共享数据。
3、明确多线程运行代码中那些代码是操作共享数据的。
5.2 同步函数
把 synchronized 作为修饰符修饰函数。则该函数称为同步函数。
注意:同步函数无需显示的指定同步监视器,同步函数的同步监视器是this,也就是该对象本身。
注意:synchronized 关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、属性等。
上面通过模拟火车卖票系统的小程序,通过加入 synchronized 同步监视器,来解决多线程中的安全问题。下面模拟银行取钱问题,通过同步函数来解决多线程的安全问题。
* 银行取钱的基本流程如下:
* 1、用户输入账户、密码,系统判断用户的账户、密码是否匹配。
* 2、用户输入取钱金额。
* 3、系统判断账户余额是否大于取钱金额。
* 4、如果余额大于取款金额,取款成功;否则取款失败
在这里只模拟后面三步:
同步方法的监视器是 this ,因此对于同一个 Account 而言,任意时刻只能有一条线程获得 Account 对象的锁定。
提示:可变类的线程安全是以降低运行程序的运行效率作为代价,为了减少线程安全所带来的负面影响,程序可以采用如下策略:
> 只对会改变竞争资源的方法进行同步。
> 在两个或两个以上的线程操作同一个锁的环境中使用同步。
当如下情况发生时会释放同步监视器的锁定:
> 当前线程的同步方法、同步代码块执行结束。
> 当前线程的同步方法、同步代码块中遇到break 、 return终止了该代码块、该方法的继续执行。
> 当前线程的同步方法、同步代码块出现了未处理的Error或Exception,导致该代码块、该方法异常结束时会释放同步锁。
> 当线程执行同步方法、同步代码块,程序执行了同步监视器对象的wait() 方法时。
5.5 死锁
当两线程相互等待对方释放锁时,就会发生死锁。由于JVM没有监测,也没有采用措施来处理死锁,所以多线程编成时应该采取措施来避免死锁。
6. 线程通信
模拟生产消费者:系统在有两条线程,分别代表生成者和消费者。
程序的基本流程:
1、生成者生产出一件商品。
2、消费者消费生成出的商品。
通过上诉流程要了解:生成者和消费者不能连续生成或消费商品,同时消费者只能消费以生产出的商品。
上面的程序中:第22 和第34 行中的flag 标志位 是判断 是由生产者生成还是由消费者进行消费。其实,在现实生活中,不可能只有一个生成者和消费者,而是多个生成者和消费者。所以需 在 第22 和 第34 行用 while 循环来进行 flag 的判断 , 而不是用 if 。如果用if 容易出现线程安全问题;而且在用while 循环进行flag的判断时,则必须用 notifyAll() 方法来唤醒同步监视器中所有等待中的线程,而不是 用notify() 方法。用notify() 则会导致所有线程进入等待状态。
上面的小程序借助Object 类提供的 wait()、notify()、notifyAll 三个方法实现 。
> wait() :导致当前线程等待,知道其他线程调用该同步监视器的notify()或notifyAll() 方法来唤醒线程。
> notify() : 唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择一个其中一个唤醒。
> notifyAll() :唤醒此同步监视器上等待的所有单个线程。
注意:这三个方法必须用同步监视器对象来调用:
> 同步函数:因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用。
> 同步代码块:必须使用 synchronized 括号中的对象来调用。
7.同步锁LOCK
JDK 1.5之后,JAVA提供了另外一种线程同步机制:显示定义同步锁来实现同步,同步锁应该使用Lock对象充当。
当使用Lock 对象来保证同步时,JAVA提供了 Condition 类保持协调,即Condition 代替了同步监视器的功能。
Condition 实例实质上被绑定在一个Lock 对象上。如:
> await() : 类似 wait() 方法。
> signal() : 类似 notify() 方法。
> signalAll() : 类似 notifyAll() 方法。
------- android培训、java培训、期待与您交流! ----------
5.多线程的安全问题:多线程同步
当使用多个线程同时访问一个数据时,经常会出现线程安全问题。如下面程序:
package Thread; /* * 多个线程同时访问一个数据时,出现的安全问题。 * 模拟一个卖火车票系统:一共有100张票,多个窗口同时卖票 */ class Ticks implements Runnable { private int ticks = 100 ; public void run() { while (ticks > 0) { // 加入sleep 方法 是为了更明显的看到该程序中出现的安全问题。 try{Thread.sleep(10);}catch (Exception e) {} System.out.println(Thread.currentThread().getName() +"...卖出了第"+ticks+"张票"); ticks -- ; } } } public class Text1 { public static void main(String args[]) { // 创建 Runnable 实现类 Ticks 的对象。 Ticks t = new Ticks() ; // 开启4个线程处理同一个 t 对象。 new Thread(t , "一号窗口").start() ; new Thread(t , "二号窗口").start() ; new Thread(t , "三号窗口").start() ; new Thread(t , "四号窗口").start() ; } }
运行的结果会出现"X号窗口...卖出了第-1张票"、“X号窗口...卖出了第-2张票”这样的安全问题。
导致的原因:当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致了共享数据的错误。
解决这类问题的方法:
1、同步代码块。
2、同步函数。
5.1 同步代码块
同步代码块的格式如下:
synchronized(obj) { // 此处的代码就是同步代码块 }
synchronized 后括号里面的 obj 就是同步监视器。代码含义:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。即只有获得对同步监视器的锁定的线程可以在同步中执行,没有锁定的线程即使获得执行权,也不能在同步代码块中执行。
注意:虽然JAVA 程序允许使用任何对象来作为同步监视器。但是还是推荐使用可能被并发访问的共享资源来充当同步监视器。
通过修改代码如下:
package Thread; /* * 多个线程同时访问一个数据时,出现的安全问题。 * 模拟一个卖火车票系统:一共有100张票,多个窗口同时卖票 */ class Ticks implements Runnable { private int ticks = 100 ; public void run() { while (ticks > 0) { synchronized (Ticks.class) { if ( ticks > 0) { // 加入sleep 方法 是为了更明显的看到该程序中出现的安全问题。 try{Thread.sleep(10);}catch (Exception e) {} System.out.println(Thread.currentThread().getName() +"...卖出了第"+ticks+"张票"); ticks -- ; } } } } } public class Text1 { public static void main(String args[]) { // 创建 Runnable 实现类 Ticks 的对象。 Ticks t = new Ticks() ; // 开启4个线程处理同一个 t 对象。 new Thread(t , "一号窗口").start() ; new Thread(t , "二号窗口").start() ; new Thread(t , "三号窗口").start() ; new Thread(t , "四号窗口").start() ; } }
加入同步监视器之后的程序就不会出现数据上的错误了。
虽然同步监视器的好处是解决了多线程的安全问题。但也也因为多个线程需要判断锁,较为消耗资源。
注意:同步的前提:
1、必须要有两个或者两个以上的线程。
2、必须是多个线程使用同一锁。
如果加入了synchronized 同步监视器,还出现了安全问题,则可以按照如下步骤找寻错误:
1、明确那些代码是多线程代码。
2、明确共享数据。
3、明确多线程运行代码中那些代码是操作共享数据的。
5.2 同步函数
把 synchronized 作为修饰符修饰函数。则该函数称为同步函数。
注意:同步函数无需显示的指定同步监视器,同步函数的同步监视器是this,也就是该对象本身。
注意:synchronized 关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、属性等。
上面通过模拟火车卖票系统的小程序,通过加入 synchronized 同步监视器,来解决多线程中的安全问题。下面模拟银行取钱问题,通过同步函数来解决多线程的安全问题。
* 银行取钱的基本流程如下:
* 1、用户输入账户、密码,系统判断用户的账户、密码是否匹配。
* 2、用户输入取钱金额。
* 3、系统判断账户余额是否大于取钱金额。
* 4、如果余额大于取款金额,取款成功;否则取款失败
在这里只模拟后面三步:
package Thread; class Account { // 账户 余额 private double balance ; public Account ( double balance) { this.balance = balance ; } // get和set方法 public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } // 同步函数 // 提供一个线程安全的draw的方法来完成取钱操作。 public synchronized void Draw(double drawAmount) { if (balance > drawAmount) { System.out.println(Thread.currentThread().getName()+"取钱成功!吐出金额" + drawAmount ); try { Thread.sleep(10) ; } catch (Exception e) { } balance -= drawAmount ; System.out.println("卡上余额:"+balance); } else { System.out.println(Thread.currentThread().getName()+"取钱失败!卡上余额:" + balance); } } } class DrawThread implements Runnable { // 模拟账户 private Account account ; // 希望所取钱的金额 private double drawAmount ; private boolean flag = true ; public DrawThread(Account account , double drawAmount) { this.account = account ; this.drawAmount = drawAmount ; } // 当前取钱 public void run() { try { while(flag) { // 调用取钱函数 account.Draw(drawAmount) ; } } catch(Exception e) { flag = false ; } } } public class AccountText { public static void main(String args[]) { Account account = new Account(10000) ; DrawThread draw = new DrawThread( account , 300 ) ; Thread t = new Thread(draw , "A.....") ; Thread t1 = new Thread(draw , "B.") ; t.start() ; t1.start() ; } }
同步方法的监视器是 this ,因此对于同一个 Account 而言,任意时刻只能有一条线程获得 Account 对象的锁定。
提示:可变类的线程安全是以降低运行程序的运行效率作为代价,为了减少线程安全所带来的负面影响,程序可以采用如下策略:
> 只对会改变竞争资源的方法进行同步。
> 在两个或两个以上的线程操作同一个锁的环境中使用同步。
当如下情况发生时会释放同步监视器的锁定:
> 当前线程的同步方法、同步代码块执行结束。
> 当前线程的同步方法、同步代码块中遇到break 、 return终止了该代码块、该方法的继续执行。
> 当前线程的同步方法、同步代码块出现了未处理的Error或Exception,导致该代码块、该方法异常结束时会释放同步锁。
> 当线程执行同步方法、同步代码块,程序执行了同步监视器对象的wait() 方法时。
5.5 死锁
当两线程相互等待对方释放锁时,就会发生死锁。由于JVM没有监测,也没有采用措施来处理死锁,所以多线程编成时应该采取措施来避免死锁。
6. 线程通信
模拟生产消费者:系统在有两条线程,分别代表生成者和消费者。
程序的基本流程:
1、生成者生产出一件商品。
2、消费者消费生成出的商品。
通过上诉流程要了解:生成者和消费者不能连续生成或消费商品,同时消费者只能消费以生产出的商品。
package Thread; class Phone { // 定义商品的编号 private int No ; // 定义商品的名字 private String name ; private boolean flag = true ; // 初始化商品的名字和编号,同时编号是自增的 public Phone (String name , int No) { this.name = name ; this.No = No ; } // 定义商品中的消费方法和生产方法。用synchronized 修饰符修饰 public synchronized void Production() { // 导致当前线程等待,知道其他线程调用notify()或notifyAll()方法来唤醒 // if (!flag) while(!flag) try {this.wait() ;}catch(Exception e){} System.out.println(Thread.currentThread().getName()+ ":生产"+name+";编号为:"+ ++ No ); // 唤醒在此同步监视器上等待的单个线程。 // this.notify() ; // 唤醒在此同步监视器上等待的所有线程。 this.notifyAll() ; flag = false ; } public synchronized void Consumption() { // if(flag) while(flag) try {this.wait() ;}catch(Exception e){} System.out.println(Thread.currentThread().getName()+ ";消费商品:"+name+"商品的编号为"+ No ); // this.notify() ; this.notifyAll() ; flag = true ; } } class ProducerThread implements Runnable { Phone phone ; private boolean flag = true ; // 同步监视器的对象 public ProducerThread (Phone phone) { this.phone = phone ; } public void run() { try { while (flag) phone.Production() ; } catch(Exception e) { flag = false ;} } } class ConsumptionThread implements Runnable { Phone phone ; private boolean flag = true ; // 同步监视器的对象 public ConsumptionThread (Phone phone) { this.phone = phone ; } public void run() { try { while (flag) phone.Consumption() ; } catch(Exception e) { flag = false ;} } } public class ProducerConsumerText { public static void main(String args[]) { Phone phone = new Phone("iPhone 5",0) ; new Thread(new ProducerThread(phone),"生成者000").start() ; new Thread(new ProducerThread(phone),"生成者111").start() ; new Thread(new ConsumptionThread(phone),"消费者000").start() ; new Thread(new ConsumptionThread(phone),"消费者111").start() ; } }
上面的程序中:第22 和第34 行中的flag 标志位 是判断 是由生产者生成还是由消费者进行消费。其实,在现实生活中,不可能只有一个生成者和消费者,而是多个生成者和消费者。所以需 在 第22 和 第34 行用 while 循环来进行 flag 的判断 , 而不是用 if 。如果用if 容易出现线程安全问题;而且在用while 循环进行flag的判断时,则必须用 notifyAll() 方法来唤醒同步监视器中所有等待中的线程,而不是 用notify() 方法。用notify() 则会导致所有线程进入等待状态。
上面的小程序借助Object 类提供的 wait()、notify()、notifyAll 三个方法实现 。
> wait() :导致当前线程等待,知道其他线程调用该同步监视器的notify()或notifyAll() 方法来唤醒线程。
> notify() : 唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择一个其中一个唤醒。
> notifyAll() :唤醒此同步监视器上等待的所有单个线程。
注意:这三个方法必须用同步监视器对象来调用:
> 同步函数:因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用。
> 同步代码块:必须使用 synchronized 括号中的对象来调用。
7.同步锁LOCK
JDK 1.5之后,JAVA提供了另外一种线程同步机制:显示定义同步锁来实现同步,同步锁应该使用Lock对象充当。
class X { // 定义锁对象 private final ReentrantLock lock = new ReentrantLock() ; // ... // 定义需要保证线程安全的方法 public void m() { // 加锁 lock.lock() ; try { // 需要保证线程安全的代码 } finally { lock.unlock() ; } } }
当使用Lock 对象来保证同步时,JAVA提供了 Condition 类保持协调,即Condition 代替了同步监视器的功能。
Condition 实例实质上被绑定在一个Lock 对象上。如:
// 定义锁对象 private final ReentrantLock lock = new ReentrantLock() ; // 指定Lock 对象对应的条件变量 private final Condition condition = lock.newCondition() ;
> await() : 类似 wait() 方法。
> signal() : 类似 notify() 方法。
> signalAll() : 类似 notifyAll() 方法。
------- android培训、java培训、期待与您交流! ----------
相关文章推荐
- 黑马程序员 JAVA基础--多线程(二)
- 黑马程序员---Java基础---多线程
- 黑马程序员:Java基础总结----多线程安全性&同步
- 黑马程序员--Java基础--多线程创建及单例模式
- 黑马程序员——java基础——多线程(1)
- 黑马程序员——Java基础之多线程
- 黑马程序员,Java基础知识六:多线程
- 黑马程序员—Java基础学习笔记之多线程
- 黑马程序员——java基础之多线程
- 黑马程序员---Java基础(多线程)
- 黑马程序员----Java基础之多线程
- 黑马程序员——java基础日记——多线程(1)
- 黑马程序员---java基础--多线程
- 黑马程序员--Java基础之多线程(1)
- 黑马程序员——Java基础--多线程(一)
- 黑马程序员——Java语言基础:多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员————java基础---------多线程与反射
- 黑马程序员--java基础复习之多线程及线程间通信
- 黑马程序员——Java基础:多线程