zoukankan      html  css  js  c++  java
  • 2018.10.29 线程复习

    第10章 线程

    线程是一个单独程序的流程。多线程是指一个程序可以同时运行多个任务,每个任务由一个单独的线程来完成。也就是说,多个线程可以同时在一个程序中运行,并且每一个线程完成不同的任务。程序可以通过控制线程来控制程序的运行。例如线程的等待、休眠、唤起线程等。
    

    10.1 线程的基本知识

    线程是程序运行的基本单位,一个程序中哭同时运行多个线程,如果程序被设置为多线程,可以提高程序运行的效率和处理速度。Java中线程的实现通常有两种方法:派生Thread类和实现Runnable接口。
    

    10.1.1 什么是线程

    	传统的程序设计语言同一时刻只能执行单任务操作,效率非常低,如果网络程序在接收数据时发生阻塞,只能等待程序接收数据之后才能继续运行,后台服务程序就会一直处于等待状态而不能继续任何操作。这种阻塞情况经常发生,这时的CPU资源完全处于闲置状态。
    	多线程实现后台服务程序可以同时处理多个任务,并不发生阻塞现象。多线程是Java语言的一个很重要的特征,多线程程序设计最大的特点就是能够提高程序执行效率和处理速度。Java程序可以同时并运行多个相对独立的线程。例如创建一个线程来接收数据,另一个线程来发送数据,即使发送线程在接收数据时发生阻塞,接受数据线程仍然可以运行。
    	线程(Thread)是控制线程(Thread of Control)的缩写,他是具有一定顺序的指令序列、存放方法中定义局部变量的栈和一些共享数据。线程是相互独立的,每个方法的局部变量和其他线程的局部变量是分开的,因此,任何线程都不能访问除自己之外的其他线程的局部变量。如果两个鲜橙同时访问同一个方法,那每个线程将各自得到此方法的一个拷贝。
    	Java提供的多线程机制使一个程序可同时执行多个任务。线程有时也被称为小进程,它是从一个大进程里分离出来的小的独立进程。由于实现了多线程技术,Java显得更健壮。多线程带来的好处是更好的交互性能和实时控制性能。多线程是强大而灵巧的编程工具,但要用好它却不是一件容易的事。在多线程编程中,每个线程都通过代码实现线程的行为,并将数据供给代码操作。编码和数据有时相当独立的,可分别向线程提供。多个线程可以同时处理同一代码和同一数据,不同的线程也可以处理各自不同的编码和数据。
    

    10.1.2 Thread 创建线程

    	Java中头两种方法创建线程,一种对Thread类的派生并覆盖run() 方法;另一种是通过实现runnable接口创建。
    	先介绍如何通过Thread类派生线程的方法,通过new 创建派生线程类的线程对象。run中的代码实现了线程的行为。在类中实现一个main方法,事实上,前面这些程序就是一个单线程程序。当他执行完main方法的程序后,线程正好退出,程序同时结束运行。
    

    Demo演示

    package com.glut.test;
    /**
     * 描述:只有一个线程
     * 
     * @author qichunlin
     *
     */
    public class OnlyThread {
    	public static void main(String[] args) {
    		run();//调用静态run()  方法
    	}
    
    	public static void run() {
    		//循环计算输出的的* 数目
    		for (int count = 1,row=1; row < 10; count++,row++) {
    			for (int i = 0; i < count;i++) {
    				System.out.print("*");
    			}
    			System.out.println();
    		}
    	}
    }
    
    

    使用Javac命令编译该文件产生class文件,然后使用java命令运行该class文件

    上述程序只是建立了一个单一线程并执行的普通小程序,并没有涉及到多线程概念。java.lang.Thread 类是一个通用的线程类,由于默认情况下run方法是空的,直接通过Thread类实例化的线程对象不能完成任何事,所以通过派生Thread类,并且覆盖run方法

    package com.glut.test;
    /**
     * 创建派生Thread类
     * @author qichunlin
     *
     */
    public class ThreadDemo extends Thread {
    	public static void main(String[] args) {
    		//创建派生线程类
    		ThreadDemo td = new ThreadDemo();
    		td.start();//启动线程
    	}
    	
    	// 声明无参构造方法
    	public ThreadDemo() {
    	}
    
    	// 带参数的构造方法
    	public ThreadDemo(String szName) {
    		super(szName);// 调用父类构造方法
    	}
    
    	@Override
    	public void run() {
    		// 循环计算输出的的* 数目
    		for (int count = 1, row = 1; row < 10; count++, row++) {
    			for (int i = 0; i < count; i++) {
    				System.out.print("*");
    			}
    			System.out.println();
    		}
    	}
    
    }
    
    

    10.1.3 Thread 创建线程步骤

    通常创建一个线程步骤如下

    (1)创建一个新的线程类,继承Thread类并覆盖Thread类的run() 方法
    	public class ThreadDemo extends Thread{
    		public void run(){}
    		
    	 }
    (2)创建一个线程类的对象,创建方法与一般对象创建相同,使用关键字new完成
            ThreadDemo td = new ThreadDemo();
    		
    (3)启动新线程对象,调用start() 方法
            td.start();//启动线程
    
    (4)线程自己调用run() 方法
    		void run();
    

    下面演示创建一个多线程的例子

    package com.glut.test;
    /**
     * 创建多个线程
     * @author qichunlin
     *
     */
    public class ThreadDemo2 extends Thread{
    	public static void main(String[] args) {
    		//创建派生线程类
    		ThreadDemo2 td1 = new ThreadDemo2();
    		td1.start();//启动线程
    		
    		ThreadDemo2 td2 = new ThreadDemo2();
    		td2.start();
    		
    		ThreadDemo2 td3 = new ThreadDemo2();
    		
    		
    		//td3.start();
    	}
    	
    	// 声明无参构造方法
    	public ThreadDemo2() {
    	}
    
    	// 带参数的构造方法
    	public ThreadDemo2(String szName) {
    		super(szName);// 调用父类构造方法
    	}
    
    	@Override
    	public void run() {
    		// 循环计算输出的的* 数目
    		for (int count = 1, row = 1; row < 10; count++, row++) {
    			for (int i = 0; i < count; i++) {
    				System.out.print("*");
    			}
    			System.out.println();
    		}
    	}
    
    }
    
    

    10.1.4 Runnable接口创建线程

    	利用实现Runnable接口来创建线程的方法可以解决Java语言不支持的多重继承问题。Runnable接口提供了run() 方法的原型,因此创建新的线程类时。只要实现此接口,即只要特定的程序代码实现Runnable接口中的run() 方法,就可以完成新的线程类的运行。
    

    例子

    package com.glut.test;
    
    public class ThreadDemo3 implements Runnable {
    
    	public static void main(String[] args) {
    		/**
    		 * 创建Runnable对象,并初始化ThreadDemo3对象rb1
    		 */
    		Runnable rb1 = new ThreadDemo3();
    		
    		/**
    		 * 创建线程对象td1
    		 */
    		Thread td1 = new Thread(rb1);
    		
    		/**
    		 * 启动线程
    		 */
    		td1.start();
    		
    	}
    	
    	// 重载run函数
    	@Override
    	public void run() {
    		// 循环计算输出的的* 数目
    		for (int count = 1, row = 1; row < 10; count++, row++) {
    			for (int i = 0; i < count; i++) {
    				System.out.print("*");
    			}
    			System.out.println();
    		}
    	}
    
    }
    
    

    10.1.5 Runnable创建线程步骤

    (1)创建一个实现Runnable接口的类,并且在这个类中重写run方法
        public class ThreadType extends Runnable{
    	    public void run(){
    		    ......
    	    }
    }
    
    (2)使用关键字new新建一个ThreadType的实例
    	Runnable rb = new ThreadType();
    	
    (3)通过Runnable的实例创建一个线程对象,在创建线程对象时,调用的构造函数是new Thread(ThreadType),他用ThreadType中实现的run() 方法作为新线程对象的run() 方法。
    	Thread td = new Thread(rb);
    
    (4)通过调用ThreadType对象的start()  方法启动线程运行
        td.start();
    

    Runnable多线程实现的例子

    package com.glut.test;
    
    public class ThreadDemo4 implements Runnable {
    
    	public static void main(String[] args) {
    		/**
    		 * 创建Runnable对象,并初始化ThreadDemo4对象rb1
    		 */
    		Runnable rb1 = new ThreadDemo4();
    		Runnable rb2 = new ThreadDemo4();
    		Runnable rb3 = new ThreadDemo4();
    		Runnable rb4 = new ThreadDemo4();
    		
    		/**
    		 * 创建线程对象td1...4
    		 */
    		Thread td1 = new Thread(rb1);
    		Thread td2 = new Thread(rb2);
    		Thread td3 = new Thread(rb3);
    		Thread td4 = new Thread(rb4);
    		
    		/**
    		 * 启动线程
    		 */
    		td1.start();
    		td2.start();
    		td3.start();
    		td4.start();
    	}
    	
    	// 重载run函数
    	@Override
    	public void run() {
    		// 循环计算输出的的* 数目
    		for (int count = 1, row = 1; row < 10; count++, row++) {
    			for (int i = 0; i < count; i++) {
    				System.out.print("*");
    			}
    			System.out.println();
    		}
    	}
    
    }
    
    

    10.2线程周期

    线程整个周期由线程创建、不可运行状态、不可运行状态和退出等部分组成,这些状态之间的转换是通过线程提供的一些方法完成的。
    

    10.2.1 线程周期的概念

    一个线程有4种状态,任何一个线程都处于4种状态种的一种状态。

    (1)创建(new)状态:调用new方法产生一个线程对象后、调用start方法前所处的状态。线程对象虽然已经创建,但还没有调用start方法启动,因此无法执行。当线程处于创建状态时,线程对象可以调用start方法进入启动状态,也可以调用stop方法进入停止状态
    
    (2)可运行(runnable)状态:当线程对象执行start() 方法后,线程就转到可运行状态。进入此状态只是说明线程对象具有了可以运行的条件,但线程不一定处于运行状态。因为单处理器系统中运行多线程程序时,一个时间点只有一个线程运行,系统通过调度机制实现宏观意义上的运行线程共享处理器。因此一个线程是否在运行,除了线程必须处于Runnable状态之外,还取决于优先级和调度。
    
    (3)不可运行状态;线程处于不可运行状态是由线程被挂起或者发生阻塞,例如对一个线程调用wait()  函数之后,他就可能进入阻塞状态;调用线程的notify或notifyAll方法后他才能再次回到可执行状态
    
    (4)退出状态:一个线程可以从任何一个状态中调用stop方法进入退出状态。线程一旦进入退出状态就不存在了,不能在返回到其他状态。除此之外,如果线程执行完run方法,也会自动进入退出状态
    

    10.2.2 线程状态转换

    1.当线程进入可执行状态

    当以下几种情况发生时,线程进入可执行状态。
    	(1)其他线程调用notify()  或者notifyAll() 方法,唤起处于不可执行状态的线程。
    		public final void notify()
    		public final void notifyAll()
    
    	  notify仅仅唤醒一个线程并允许它获得锁,notifyAll唤醒所有等待这个对象的线程,并允许它们获得锁。wait和notify 是Java同步机制的重要内容。
    	  
    	(2)线程调用sleep(long millis)  throws InterruptedException
    	   在毫秒数内让当前正在执行的线程进入休眠状态,等到时间过后,该线程会自动苏醒并继续执行。sleep方法的精度受到系统计数器的影响。
    	   
    	   static void sleep(long millis,int nanos) throws InterruptedException
    	  在毫秒数加纳秒数内让当前正在执行的线程进入休眠状态,此操作的精度也收到系统计数器的影响。
    	  
    	(3)线程对I/O 操作的完成
    

    2.线程进入不可执行状态

    当以下几种情况发生时,线程进入不可执行状态。
    	(1)线程自动调用wait()  方法,等待某种条件的发生
    	(2)线程调用sleep()  进入不可执行状态,在一定时间后会进入可执行状态
    	(3)等待I/O操作的完成
    

    线程阻塞例子

    package com.glut.test;
    /**
     * 线程阻塞例子
     * @author qichunlin
     *
     */
    public class ThreadSleep {
    	public static void main(String[] args) {
    		SubThread st = new SubThread();
    		st.start();
    	}
    }
    
    class SubThread extends Thread {
    	SubThread() {}
    
    	SubThread(String Name) {
    		super(Name);
    	}
    
    	@Override
    	public void run() {
    		for (int count = 1, row = 1; row < 10; count++, row++) {
    			for (int i = 0; i < count; i++) {
    				System.out.print("*");
    			}
    			try {
    				//线程休眠1秒钟
    				Thread.sleep(1000);
    			} catch (Exception e) {
    				// TODO: handle exception
    				e.printStackTrace();//捕获异常
    			}
    			System.out.println();
    		}
    	}
    }
    

    10.2.3 等待线程结束

    isAlive()  方法用来判断一个线程是否存活。当线程处于可执行状态或不可知性状态时,isAlive() 方法返回true ;当线程处于创建状态或退出状态时,则返回false。也就是说,isAlive() 方法返回true,并不能判断线程是否处于可运行状态还是不可运行状态。isAlive() 方法的原型如下
    		public final boolean isAlive()
    	
    	该方法用于测试线程是否处于活动状态。活动状态是指线程已经启动(调用start方法)且尚未退出所处的状态,包括可运行和不可运行状态。可以通过该方法解决程序。
    
    
    	第一种是不断查询第一个线程是否已经终止,如果没有,则让主线程睡眠一直到他终止
    		线程1.start();
    		while(线程1.isAlive()){
    			Thread.sleep("睡眠时间");
    		}
    		
    
    	第二种是利用join() 方法
    		public final void join(long millis) throws InterruptedException
    
    	等待该线程终止的时间最长为毫秒(millis),超时为0意味着一直等下去。
    

    10.3 线程调度

    多线程应用程序的每一个线程的重要性和优先级可能不同,例如由多个线程都在等待获得CPU的时间片,那么优先级高的线程就能抢占CPU并得以执行;当多个线程交替抢占CPU时,优先级高的线程占用的时间应该多。因此,高优先级的线程占用的时间应该多。因此,高优先级的线程执行的效率会高些,执行速度也会快些。
    	在Java中,CPU的使用通常是抢占式调度模式不需要时间片分配进程。抢占式调度模式是指许多线程同时处于可运行状态,但只有一个线程正在运行当线程一直运行到结束,或者进入不可运行状态,或者具有更高优先级的线程变为可运行状态,他将会让出CPU。线程与优先级相关的方法如下:
    		public final void setPriroity(int newPriority)
    		newPriority的值必须是MIN_PRIORITY到MAX_PRIORITY范围内,通常他们的值是1和10 目前Windows系统只支持3个级别的优先级。
    		
    		public final int getPriority()  //获得当前优先级
    

    10.4 线程同步

    Java应用程序中的多线程可以共享资源,例如文件、数据库、内存等。当线程以i 兵法模式访问共享数据时,共享数据可能发生冲突。Java引入线程同步的概念,以实现共享数据的一致性。线程同步机制让多个线程有序的访问共享资源,而不是同时操作共享资源。
    
    

    10.4.1 同步概念

    在线程异步模式的情况下,同一时刻有一个线程在修改共享数据,另一个线程在读取共享数据,当修改共享数据的线程没有处理完毕,读取数据的线程肯定会得到错误的结果。如果采用多线程的同步控制机制,当处理共享数据的线程完成处理数据之后,读取线程读取数据。
    

    线程不同步原因分析及解决方案

    	Java提供了“锁”机制实现线程的同步。锁机制的原理是每个线程进入共享代码之前获得锁否则不能进入共享代码区,并且在退出共享代码之前释放该锁,这样就解决了多个线程竞争共享代码的情况达到线程不同大的目的。Java中锁机制的实现方法是共享代码之前加入synchronized关键字。
    	在同一个类中,用关键字synchronized声明的方法为同步方法。Java由一个专门负责管理线程对象中同步方法访问的工具---同步模型监视器,它的原理是为每个具有同步代码的对象准备惟一的一把“锁”。当多个线程访问对象时,只有取得锁的线程才能进入同步方法,其他访问共享对象的鲜橙停留在对象中等待,如果获得锁的线程调用wait方法放弃锁,那么其他等待获得锁的线程将有机会获得锁。当某一个等待线程取得锁,它将执行同步方法,而其他没有取得锁的线程继续等待获得锁。
    	Java程序中线程之间通过消息实现相互通信,wait()  notify() notifyAll() 方法完成线程间的消息传递。
    

    10.4.2 同步格式

    当把一语句块声明为synchronized,在同一时间,它的访问线程之一才能执行该语句块。用关键字synchronized可讲方法声明为同步,格式如下:

    class 类名{
    	public synchronized 类型名称 方法名称(){}
    }
    

    对于同步块,synchronized获取的是参数中的对象锁

    synchronized(obj){
    	.....
    }
    

    当线程执行到这里的同步代码块时,它必须获取obj这个对象的锁才嫩执行同步块;否则线程只能等待获得锁。必须注意的是obj对象的作用范围不同,控制情况不尽相同。

    public void method(){
    	Object obj = new Object();//创建局部Object类型对象obj
    	synchronized(obj){   //同步块
    		....
    	}
    }
    

    上面的代码创建了一个局部对象obj。由于每一个线程执行到Object obj = new Object( ) 时都会产生obj对象,每一个线程都可以获得创建新的obj对象的锁,不会相互影响,因此这段程序起到同步作用。如果同步的是类属性,情况就不同了。同步类的成员变量一般格式如下:

    class method{
    	Object o = new Object();//创建Object类型的成员变量o
    	public void test(){
    		synchronized(o){
    		}
    	}
    }
    

    注意:由于两个线程都在等待对方释放各自拥有的锁的现象称为死锁。这种现象往往是由于相互嵌套的synchronized代码段造成的,因此,在程序汇总尽量少用嵌套的synchronized代码块

    10.5 线程通信

    多线程之间可以通过消息通讯,以达到相互协作的目的。Java中线程之间的通信是通过Object类中的wait() notify()  notifyAll()  等几种方法实现的。Java中每个对象内部不仅有一个对象锁之外,还有一个线程等待队列,这个队列用于村方法所有等待对象锁的线程。
    

    10.5.1 生产者/消费者

    生产者与消费者是一个很好的线程通信例子。生产者在一个循环中不断产生共享数据,而消费者则不断地消费生产者生产的共享数据。二者之间的关系可以很清楚地说明,必须先有生产者生产共享数据,才能有消费者消费共享数据。因此程序必须保证在消费者消费之前,必须有共享数据,如果没有,消费者必须等待心的共享数据,生产者与消费者之间的数据关系如下:
    	生产者之前,如果共享数据没有被消费,则生产者等待;生产者生产后,通知消费者消费
    	消费者消费前,如果共享数据已经被消费完,则消费者等待;消费者消费后,通知生产者生产
    

    生产者消费者线程定义

    package com.glut.Queue;
    
    /**
     * 生产者消费者线程
     * @author qichunlin
     *
     */
    public class Producer extends Thread{
    	Queue q;//声明队列q
    	
    	//生产者构造方法
    	public Producer(Queue q) {
    		// TODO Auto-generated constructor stub
    		this.q = q;//队列初始化
    	}
    	
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		for (int i = 1; i < 5; i++) {
    			q.put(i);//给队列中添加新的元素
    		}
    	}
    }
    
    
    
    class Customers extends Thread{
    	Queue q;
    	
    	//消费者构造方法
    	public Customers(Queue q) {
    		// TODO Auto-generated constructor stub
    		this.q = q;//队列q初始化
    	}
    	
    	@Override
    	public void run() {
    		
    		//循环消费元素
    		while(true) {
    			q.get();//获取队列中的元素
    		}
    	}
    }
    
    

    Producer是一个生产者类,该生产者类提供了一个以共享队列作为参数的构造方法,它的run方法循环产生新的元素,并将元素添加到共享队列中;Customer是一个消费者类,该消费者类提供一个以共享队列作为参数的构造方法,它的tun方法循环消费元素,并将元素从共享队列删除。

    10.5.2 共享队列

    共享队列是用于保存生产者生产、消费者消费的共享数据。共享队列有两个域:value(元素的数目)、isEmpty(队列的状态)。共享队列提供了put和get两个方法

    package com.glut.Queue;
    /**
     * 共享队列
     * @author qichunlin
     *
     */
    public class Queue {
    	int value = 0;// 声明,并初始化整数类型数据域value
    	boolean isEmpty = true;// 声明
    
    	//生产者生产方法
    	public synchronized void put(int v) {
    		// 如果共享数据没有被消费,则生产者等待
    		if (!isEmpty) {
    			try {
    				System.out.println("生产者等待");
    				wait();//进入等待状态
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    		value += v;// value值加v
    		isEmpty = false;
    		System.out.println("生产者共生产数量" + v);
    		notify();//生产之后通知消费者消费
    	}
    
    	// 消费者消费的方法
    	public synchronized int get() {
    		if (isEmpty) {
    			try {
    				System.out.println("消费者等待");
    				wait();// 进入等待状态
    			} catch (Exception e) {
    				// TODO: handle exception
    				e.printStackTrace();
    			}
    		}
    		value--;
    		if(value<1) {
    			isEmpty = true;
    		}
    		System.out.println("消费则消费一个,剩余"+value);
    		notify();
    		return value;
    	}
    }
    
    

    生产者调用put方法生产共享数据,如果共享数据不为空,生产者进入等待状态;否则将生成新的数据,然后调用notify() 方法唤起消费者线程进行消费;消费者调用get方法消费共享数据,如果共享数据为空,消费者线程进入等待状态,否则将消费共享数据,然后调用notify() 唤起生产者线程进行生产

    10.5.3 运行生产者/消费者

    package com.glut.Queue;
    /**
     * 开始工作
     * @author qichunlin
     *
     */
    public class ThreadCommunication {
    	public static void main(String[] args) {
    		//创建队列
    		Queue q = new Queue();
    		
    		//创建生产者
    		Producer p = new Producer(q);
    		//创建消费者
    		Customers c = new Customers(q);
    		
    		//启动线程
    		c.start();
    		p.start();
    	}
    }
    
    

    考虑到程序的安全性,多数情况下使用notifyAll() ,除非明确可以知道唤醒某一个线程。wait方法调用的前提条件是当前线程获取了这个对象的锁,也就是说wait方法必须放在同步块或同步方法中

    10.6 死锁

    10.6.1死锁产生的原因

    1) 系统资源的竞争

    通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在 运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争 才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。
    

    2) 进程推进顺序非法

    进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 P1、P2分别保持了资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都 会因为所需资源被占用而阻塞。
    
    信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,结果也会使得这 些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A 发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁。
    

    3) 死锁产生的必要条件

    产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。

    • 互斥条件 :进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
    • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
    • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
    • 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有

    10.6.2 如何避免死锁

    在有些情况下死锁是可以避免的。三种用于避免死锁的技术:

    • 1.加锁顺序(线程按照一定的顺序加锁)
    • 2.加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
    • 3.死锁检测

    加锁顺序

    当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。
    

    加锁时限

    另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(译者注:加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)。
    
    

    死锁检测

    死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。
    
    每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。
    
    当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。例如,线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁7;线程B拥有锁7,请求锁1)。
    
    当然,死锁一般要比两个线程互相持有对方的锁这种情况要复杂的多。线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D又在等待线程A。线程A为了检测死锁,它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A找到了线程C,然后又找到了线程D,发现线程D请求的锁被线程A自己持有着。这是它就知道发生了死锁。
    
    

    通常在程序设计中应注意,不要使用stop() suspend() 、resume() 以及destroy() 方法。
    stop方法不安全,他会解除又该线程获得的所有对象锁,而且有可能使对象处于不连贯状态,如果其他线程此时访问对象,而导致的错误很难检测出来。

    10.7 小结

    Java 应用程序通过多线程技术共享系统资源,线程之间的通信是通过间的方法调用完成。可以说,Java语言对多线程的支持增强了Java作为网络程序设计的语言的优势,为实现分布式应用系统中多用户并发访问,提高服务器效率奠定了基础。
    
  • 相关阅读:
    CentOS 7源码安装zabbix
    CentOS 7 yum安装Zabbix
    Centos 7配置LAMP
    Oracle 12c RMAN备份文档
    Oracle 12c: RMAN restore/recover pluggable database
    Oracle 12c利用数据泵DataPump进行Oracle数据库备份
    EBS测试环境DataGuard配置
    oracle数据库将一列的值拼接成一行,并且各个值之间用逗号隔开
    ORA-19602: cannot backup or copy active file in NOARCHIVELOG mode
    rman输出日志的几种方法(转)
  • 原文地址:https://www.cnblogs.com/qichunlin/p/9872866.html
Copyright © 2011-2022 走看看