java笔记六:线程间的协调

 昨天重新学习了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()不同,锁还是归我所有,一段时间后会自动醒过来

  

时间: 2024-09-20 21:49:23

java笔记六:线程间的协调的相关文章

彻底明白Java的多线程-线程间的通信

线程间的通信 1. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种: 1) 产生(New):线程对象已经产生,但尚未被启动,所以无法执行.如通过new产生了一个线程对象后没对它调用start()函数之前. 2) 可执行(Runnable):每个支持多线程的系统都有一个排程器,排程器会从线程池中选择一个线程并启动它.当一个线程处于可执行状态时,表示它可能正处于线程池中等待排排程器启动它:也可能它已正在执行.如执行了一个线程对象的start()方法后,线程就处于可执行状态,但

Java多线程之线程间协作 notify与wait的使用

(转载请注明出处:http://blog.csdn.net/buptgshengod) 1.背景        Java多线程操作运用很广,特别是在android程序方面.线程异步协作是多线程操作的难点也是关键,也是找工作面试经常考到的地方.下面分享一下我的使用心得. 介绍几个关键字: synchronized:线程锁,使得系统只执行当前线程. notifyAll():唤醒其它被锁住的线程 wait():挂起线程 ExecutorService exec=Executors.newCachedT

Java中一个线程执行死循环有什么后果_java

假设有两个线程在并发运行,一个线程执行的代码中含有一个死循环如:while(true)....当该线程在执行while(true)中代码时,另一个线程会有机会执行吗? 示例代码(代码来源于互联网) public class Service { Object object1 = new Object(); public void methodA() { synchronized (object1) { System.out.println("methodA begin"); boolea

java学习笔记14--多线程编程基础1

多线程编程基础 多进程 一个独立程序的每一次运行称为一个进程,例如:用字处理软件编辑文稿时,同时打开mp3播放程序听音乐,这两个独立的程序在同时运行,称为两个进程 进程要占用相当一部分处理器时间和内存资源 进程具有独立的内存空间 通信很不方便,编程模型比较复杂   多线程 一个程序中多段代码同时并发执行,称为多线程,线程比进程开销小,协作和数据交换容易 Java是第一个支持内置线程操作的主流编程语言,多数程序设计语言支持多线程要借助于操作系统"原语(primitives)" Threa

Java线程间通讯概述

这个故事源自一个很简单的想法:创建一个对开发人员友好的.简单轻量的线程间通讯框架,完全不 用锁.同步器.信号量.等待和通知,在Java里开发一个轻量.无锁的线程内通讯框架:并且也没有队列 .消息.事件或任何其他并发专用的术语或工具. 只用普通的老式Java接口实现POJO的通讯. 它可能跟Akka的类型化actor类似,但作为一个必须超级轻量,并且要针对单台多核计算机进行优化的 新框架,那个可能有点过了. 当actor跨越不同JVM实例(在同一台机器上,或分布在网络上的不同机器上)的进程边界时,

Java中利用管道实现线程间的通讯

在Java 语言中,提供了各种各样的输入输出流(stream),使我们能够很方便的对数据进行操作,其中,管道(pipe)流是一种特殊的流,用于在不同线程(threads)间直接传送数据.一个线程发送数据到输出管道,另一个线程从输入管道中读数据.通过使用管道,实现不同线程间的通讯.无需求助于类似临时文件之类的东西.本文在简要介绍管道的基本概念后,将以一个具体的实例pipeapp加以详细说明. 1.管道的创建与使用 Java提供了两个特殊的专门的类专门用于处理管道,它们就是pipedinputstr

java线程间通信[实现不同线程之间的消息传递(通信),生产者和消费者模型]

线程通信,线程之间的消息传递: 多个线程在操作同一个资源,但对共享资源的操作动作不同:它们共享同一个资源,互为条件,相互依赖,相互通信让任务向前推进. 线程的同步,可以解决并发更新同一个资源,实现线程同步;但不能用来实现线程间的消息传递. 线程通信生产者和消费者和仓库是个典型模型: 生产者:没有生产之前通知消费者等待,生产产品结束之后,马上通知消费者消费 消费者:没有消费之前通知生产者等待,消费产品结束之后,通知生产者继续生产产品以供消费 线程通信:使用java中Object中提供的: publ

线程间通讯-Java写交易客户端(多线程、对象数据传送等简单问题)

问题描述 Java写交易客户端(多线程.对象数据传送等简单问题) 有如下几个类: 1.Ticker类//此Ticker为简单java类,其属性与服务器传来的json字段对应,通过Decoder类反系列化json//反系列化以后的数据,用以做逻辑判断,符合逻辑,则向服务器发出某种请求 public class Ticker implements Serializable { private static final long serialVersionUID = 2015022601L; priv

JAVA线程间协作:wait.notify.notifyAll

    JAVA的进程同步是通过synchronized()来实现的,需要说明的是,JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步.互斥操作.明白这个原理,就能理解为什么synchronized(this)与synchronized(static XXX)的区别了,synchronized就是针对内存区块申请内存锁,this关键字代表类