Presentation is loading. Please wait.

Presentation is loading. Please wait.

第七章 多线程处理 线程的互斥 线程的同步 死锁问题.

Similar presentations


Presentation on theme: "第七章 多线程处理 线程的互斥 线程的同步 死锁问题."— Presentation transcript:

1 第七章 多线程处理 线程的互斥 线程的同步 死锁问题

2 1、多线程的互斥 ——线程间的通信 1. 用管道流实现线程间的通信 线程1 线程2 2. 通过一个中间类在线程间传递信息 线程2 线程1
PipedOutputStream PipedInputStream 输出流outStream 输入流inStream 线程2 2. 通过一个中间类在线程间传递信息 线程2 线程1 中间类m s m.write(s) s=m.read() write() read()

3 1、多线程的互斥 ——线程间的数据共享 例如:银行对同一个帐户的存取款操作。 class Fetch extends Thread{
private Cq a1; private int amount; public Fetch(Cq a1, int amount) { this.a1 = a1; this.amount = amount; } public void run() { int k = a1.howmatch(); try { sleep(1); } catch (InterruptedException e) { System.out.println(e); } System.out.println("现有" + k + ",取走" + a1.get(amount) + ",余额" + a1.howmatch()); } } class Cq { private String name; private int value; void put(int i) { value=value+i;} int get(int i){ if (value>i) value=value-i; else { i=value; value=0; } return i; } int howmatch() { return value; } }

4 class Save extends Thread{
private Cq a1; private int amount; public Save(Cq a1,int amount) { this.a1=a1; this.amount=amount; } public void run() { int k = a1.howmatch(); try { sleep(1); catch (InterruptedException e) { System.out.println(e); } a1.put(amount); System.out.println("现有" + k + ",存入" + amount + ",余额" + a1.howmatch()); } } public static void main(String args[]) { Cq a1=new Cq(); Cq a2=new Cq(); (new Save(a1,100)).start(); (new Save(a1,200)).start(); (new Fetch(a1,500)).start(); (new Fetch(a2,500)).start();

5 1、多线程的互斥 ——线程间的资源互斥共享 运行结果: 现有0,存入100,余额100 现有0,存入200,余额300
现有0,取走300,余额0

6 1、多线程的互斥 ——线程间的资源互斥共享 线程 1 线程 2 线程 10 资源 取过来 加1后送回去 取钱1 取钱2 透支 余额 变量

7 1、多线程的互斥 ——线程间的资源互斥共享 产生这种问题的原因是对共享资源访问的不完整。为了解决这种问题,需要寻找一种机制来保证对共享数据操作的完整性,这种完整性称为共享数据操作的同步,共享数据叫做条件变量。 在Java语言中,引入了“对象互斥锁”的概念(又称为监视器、管程)来实现不同线程对共享数据操作的同步。 “对象互斥锁”阻止多个线程同时访问同一个条件变量。 在Java语言中,有两种方法可以实现“对象互斥锁”: 用关键字volatile来声明一个共享数据(变量); 用关键字synchronized来声明一个操作共享数据的方法或一段代码。

8 1、多线程的互斥 ——线程间的资源互斥共享 class Fetch extends Thread{ class Cq {
private Cq a1; private int amount; public Fetch(Cq a1, int amount) { this.a1 = a1; this.amount = amount; } public void run() { synchronized(a1){ int k = a1.howmatch(); try { sleep(1); } catch (InterruptedException e) { System.out.println(e); } System.out.println("现有" + k + ",取走" + a1.get(amount) + ",余额" + a1.howmatch()); } } } class Cq { private String name; private int value; void put(int i) { value=value+i;} int get(int i){ if (value>i) value=value-i; else { i=value; value=0; } return i; } int howmatch() { return value; } }

9 5、多线程的互斥与同步 ——线程间的资源互斥共享 class Save extends Thread{ private Cq a1;
private int amount; public Save(Cq a1,int amount) { this.a1=a1; this.amount=amount; } public void run() { synchronized(a1) { int k = a1.howmatch(); try { sleep(1); catch (InterruptedException e) { System.out.println(e); } a1.put(amount); System.out.println("现有" + k + ",存入" + amount + ",余额" + a1.howmatch()); } } public static void main(String args[]) { Cq a1=new Cq(); Cq a2=new Cq(); (new Save(a1,100)).start(); (new Save(a1,200)).start(); (new Fetch(a1,500)).start(); (new Fetch(a2,500)).start();

10 1、多线程的互斥 ——线程间的资源互斥共享 用synchronized来标识的代码段或方法即为“对象互斥锁”锁住的部分。如果一个程序内有两个或以上的方法使用synchronized标志,则它们在同一个“对象互斥锁”管理之下。 push pop 线程1 线程2 一般情况下,都使用synchronized关键字在方法的层次上实现对共享资源操作的同步,很少使用volatile关键字声明共享变量。

11 synchronized的用法 1、对于同步的方法或者代码块来说,必须获得对象锁才能够进入同步方法或者代码块进行操作; 2、如果采用method级别的同步,则对象锁即为method所在的对象,如果是静态方法,对象锁即指method所在的Class对象(唯一); 3、对于代码块,对象锁即指synchronized(abc)中的abc; 4、因为第一种情况,对象锁即为每一个线程对象,因此有多个,所以同步失效,第二种共用同一个对象锁lock,因此同步生效,第三个因为是static因此对象锁为ThreadTest3的class 对象,因此同步生效。 如上述同步有两种方式:同步块和同步方法。 如果是同步代码块,则对象锁需要编程人员自己指定,一般有些代码为synchronized(this)只有在单例模式才生效;(本类的实例有且只有一个) 如果是同步方法,则分静态和非静态两种。 静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。

12 ReentrantLock的用法 ReentrantLock是“一个可重入的互斥锁 Lock,它具有与使用 synchronized  方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。 ” 简单来说,ReentrantLock有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。 ReentrantLock  类(重入锁)实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

13

14 2、多线程的同步 ——线程间的同步 除了要处理多线程间共享数据操作的同步问题之外,在进行多线程程序设计时,还会遇到另一类问题,这就是如何控制相互交互的线程之间的运行进度,即多线程的同步。 典型的模型:生产者——消费者问题 生产者 消费者 共享对象 put get 若共享对象中只能存放一个数据,可能出现以下问题: 生产者比消费者快时,消费者会漏掉一些数据没有取到; 消费者比生产者快时,消费者取相同的数据。

15 2、多线程的同步 ——线程间的同步 为了解决所出现的问题,在Java语言中可以用wait () 和notify()/notifyAll()方法(在java.lang.Object类中定义)来协调线程间的运行进度(读取)关系。 wait()方法的作用是让当前线程释放其所持有的“对象互斥锁”,进入wait队列(等待队列);而notify()/notifyAll()方法的作用是唤醒一个或所有正在等待队列中等待的线程,并将它(们)移入等待同一个“对象互斥锁”的队列。 需要指出的是: notify()/notifyAll()方法和wait ()方法都只能在被声明为synchronized的方法或代码段中调用。

16 public class ProducerConsumerTest
{ public static void main(String args[]) CubbyHole c = new CubbyHole(); //共享资源 Producer p1 = new Producer(c, 1); //资源生产者 Consumer c1 = new Consumer(c, 1); //资源消耗者 p1.setPriority(Thread.MAX_PRIORITY); c1.setPriority(Thread.MAX_PRIORITY); p1.start(); c1.start(); }

17 class Producer extends Thread
{ private CubbyHole cubbyhole; private int number; public Producer(CubbyHole c, int number) cubbyhole = c; this.number = number; } public void run() for (int i = 0; i <10; i++) cubbyhole.put(i); System.out.println("Producer #" + this.number + " put: " + i); try{ sleep((int)(Math.random() * 100)); } catch (InterruptedException e) {

18 class Consumer extends Thread
{ private CubbyHole cubbyhole; private int number; public Consumer(CubbyHole c, int number) cubbyhole = c; this.number = number; } public void run() int value = 0; for (int i = 0; i <10; i++) value = cubbyhole.get(); System.out.println("Consumer #" + this.number + " got: " + value);

19 ProducerConsumerTest.java get()方法在读信息之前先等待,直到信息可读,读完后通知要写的线程。
class CubbyHole { private int seq; private boolean available = false; //信号量 public synchronized int get() while (available == false) try wait(); // waits for notify() call from Producer }catch (InterruptedException e){ } available = false; notify(); return seq; get()方法在读信息之前先等待,直到信息可读,读完后通知要写的线程。 public synchronized void put(int value) { while (available == true) try{ wait(); // waits for notify() call from consumer }catch (InterruptedException e){ } seq = value; available = true; notify(); put()方法在写信息之前先等待,直到信息被取走,写完后通知要读的进程。 ProducerConsumerTest.java

20 3、死锁问题 note pen 如果一个线程持有一个锁并试图获取另一个锁时,就有死锁的危险。
线程2 pen 线程1 note 把“pen”给我,我 才能给你“note” 把“note”给我,我 才能给你“pen” 如果一个线程持有一个锁并试图获取另一个锁时,就有死锁的危险。 死锁是资源的无序使用而带来得,解决死锁问题的方法就是给资源施加排序。note编号为1,pen编号为2,线程1和线程2都必须先获得1号资源后方可再获取2号资源。

21 3、死锁问题 例如:发牌程序

22 4、线程的优先级及调度 Java提供一个线程调度器来监控程序中启动后进入可运行状态的所有线程。线程调度器按照线程的优先级决定调度哪些线程来执行,具有高优先级的线程会在较低优先级的线程之前得到执行。同时线程的调度是抢先式的,即如果当前线程在执行过程中,一个具有更高优先级的线程进入可执行状态,则该告优先级的线程会被立即调度执行。 多个线程运行时,若线程的优先级相同,由操作系统按时间片轮转方式或独占方式来分配线程的执行时间。

23 4、线程的优先级及调度 在Java中线程的优先级是用数字来表示的,分为三个级别:
高优先级:Thread.MIN_PRIORITY,数值为1 (2~4) 缺省优先级: Thread. NORM_PRIORITY,数值为5 低优先级:Thread.MAX_PRIORITY,数值为10 (6~9) 具有相同优先级的多个线程,若它们都为高优先级Thread.MAX_PRIORITY,则每个线程都是独占式的,也就是说这些线程将被顺序执行;若该优先级不为高优先级,则这些线程将同时执行,也就是说这些线程的执行是无序的。 线程被创建后,其缺省的优先级是缺省优先级Thread. NORM_PRIORITY。可以用方法 int getPriority()来获得线程的优先级,同时也可以用方法 void setPriority( int p ) 在线程被创建后改变线程的优先级。

24 5、守护线程 (Daemon) 在客户/服务器模式下,服务器的作用是持续等待用户发来请求,并按请求完成客户的工作。 客户端 服务器端
request daemon 守护线程是为其它线程提供服务的线程,它一般应该是一个独立的线程,它的run()方法是一个无限循环。 可以用方法public boolean isDaemon()确定一个线程是否守护线程,也可以用方法public void setDaemon( boolean )来设定一个线程为守护线程。 守护线程与其它线程的区别是,如果守护线程是唯一运行着的线程,所有用户线程终止后,系统将其强迫终止。

25 5、守护线程 (Daemon) 一般当最后一个线程结束时,Java程序才退出 守护线程的存在不影响Java程序的退出
setDaemon(true) 使线程成为守护线程(必须在start之前调用) setDaemon(false) 使线程成为一般线程(必须在start之前调用) 守护线程一般不能用于执行关键任务 任务未执行完,线程就可能被强制结束 守护线程一般用来做辅助性工作 提示,帮助等

26 6、线程组 (ThreadGroup) / 线程局部变量(ThreadLocal) 把一组线程统一管理 构造方法 声明线程的线程组
例如对一组线程同时调用interrupt 构造方法 ThreadGroup(String groupName) ThreadGroup(ThreadGroup tg, String groupName) 线程组可以递归 声明线程的线程组 缺省创建的线程与父线程同组 Thread(ThreadGroup tg, Runnable ro) Thread(ThreadGroup tg, Runnable ro, String threadName)

27 6、线程组 (ThreadGroup) / 线程局部变量(ThreadLocal) ThreadGroup0 ThreadGroup1

28 6、线程组 (ThreadGroup) / 线程局部变量(ThreadLocal) int activeCount()
线程组下的所有活动线程数(递归) int enumerate(Thread[] list[, boolean recursive]) int enumerate(ThreadGroup[] list[, boolean recursive]) getMaxProirity / setmaxProirity 获得 / 设置 线程组中线程的最大优先级 getName 获得线程组的名字 getParent / parentOf 获得 / 判断 线程父子关系

29 6、线程组 (ThreadGroup) / 线程局部变量(ThreadLocal) 不同的线程对相同的线程局部变量的访问相互不影响
Object get() void set(Object o) protected  Object initialValue() InheritableThreadLocal extends ThreadLocal InheritableThreadLocal() protected  Object childValue(Object parentValue) 将线程变量定义为类变量(静态变量),并与相关的线程关联起来。

30 7、使用线程 线程的使用取决于程序的需求: 1、使用多线程不一定会增加CPU的处理能力; 如果程序对IO或网络连接、数据库连接等相对于CPU来说非常慢的设备操作比较多,应用程序经常性的等待缓慢的资源,在CPU上进行的是一些简单耗时短的操作,使用本地操作可以使CPU在系统进行这些较慢操作时多处理其他线程的请求,从而CPU得到充分利用;如果应用程序是计算密集型的,每个运算占CPU时间较长,即使用线程也未必能提高多少性能。 2、基于Internet的应用有必要使用多线程; 每个线程可以为不同的客户或客户组服务,从而缩短不响应时间。

31 小结 1. 实现线程有两种方法: 实现Ruannable接口 继承Thread类
2. 当新线程被启动时,Java运行系统调用该线程的run()方法,它是Thread的核心。 3. 线程有四个基本状态:创建、可运行、不可运行、死亡。

32 小结 4. 两个或多个线程竞争资源时,需要用同步的方法协调资源。
5. 多个线程执行时,要用到同步方法,即使用synchronized的关键字设定同步区。 6. wait和notify起协调作用。


Download ppt "第七章 多线程处理 线程的互斥 线程的同步 死锁问题."

Similar presentations


Ads by Google