黑马程序员 线程分析(二)

原创 2013年12月06日 13:49:24

----------android培训java培训、java学习型技术博客、期待与您交流! -----------

1,Runnable接口实现类创建线程对象的优越性

          实现Runnable接口的自定义类在线程中直接在run方法中对代码进行同步,不需要使用其他类来封装共享数据。

          因此:开发中常常使用Runnable接口的实现类创建线程对象来替代通过Thread子类创建线程对象的方式。

a,实现Runnable接口达到线程同步

示例代码:

class synchronized_ticket6 implements Runnable{
	private int ticketnum = 10;
	public void run() {
		while(true){
			synchronized(this){
				if(ticketnum <= 0){
					break;
				}
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					// TODO: handle exception
				}
				System.out.println(Thread.currentThread().getName()+"sales:\t"+ticketnum--);
			}
		}
	}
}

验证结果:


b,是否可以在实现Runnable接口的实现子类中添加同步函数呢?看下面代码

class synchronized_ticket7 implements Runnable{
	private int ticketmun = 10;
	public void run() {
		while(true){
			if(ticketmun <= 0)
			{
				break;
			}
			sellTicket();
		}
	}
	
	public synchronized void sellTicket(){  
		 if(ticketmun > 0){
	        try {  
	           Thread.sleep(10);  
	        }catch (InterruptedException e) {  
	        	e.printStackTrace();  
	        }  
	        System.out.println(Thread.currentThread()+". sale: ticket"+ticketmun --);  
	     }   
	}
	/*
	 * 结论:答案是可行的究其原因是,因为synchronized_ticket7只有一个实例化对象而对应的
	 * 		锁也只有一个,四个线程共享这个runnable实现类
	 * */
}

验证结果:


c,改进: 将所有共享对象封装起来,类于synchronized_ticket5方法

class synchronized_ticket8 implements Runnable{
	private synchronized_ticket8.synclass syn = new synclass();
	public void run() {
		while(true){
			show();
		}
	}
	private synchronized void show() {
		syn.sellTicket();
	}
	
	private class synclass{
		private int ticketmun = 10;
		public void sellTicket(){  
			 if(ticketmun >0){
		        try {  
		           Thread.sleep(10);  
		        }catch (InterruptedException e) {  
		        	e.printStackTrace();  
		        }  
		        System.out.println(Thread.currentThread()+". sale: ticket"+ticketmun --);  
		     }  
		  }  
	}
}

验证结果:

d,run接口的详细分析/*
 * Runnable接口分析,产生原因
 * 1,thread类中静态成员的缺点
 *   a,生命周期过长
 *   b,数据容易被误用(多个不同线程如子线程,有些子线程不需要某些静态成员,而误操作)
 * 2,thread类的优化
 *   a,将共享数据抽取到一个自定义类中,且所有共享数据非静态化,最好是私有化,提供get/set方法
 *   b,在thread类中增加一个带参数(自定义类类型)的构造函数,同时声明一个自定义类类型的
 *    非静态成员变量
 *   c,在new thread类之前必须先实例化一个自定义类的对象
 *   d,在thread初始化时,将自定义类型的对象传入构造函数中,让自定义类型的非静态成员变量引用它
 *    这样所有线程都共享了这个自定义类对象,
 * 3,thread类run()方法优化
 *   缺点:
 *   a,增加了访问共享数据的难度,需要使用自定义类的get方法
 *   b,增加了访问锁的难度,需要使用get方法
 *   优化:
 *   a,将run方法移植到自定义类中
 *   b,在线程类的run方法中直接调用自定义类的run方法即可,实现线程类格式化
 *
 * */

2,死锁

         a, 死锁的定义:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象:死锁。

          b,死锁的产生必要条件:

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
class myDeadLock implements Runnable{
	private static boolean flag;
	public myDeadLock(boolean flag) {
		this.flag = flag;
	}
	@Override
	public void run() {
		if(flag){
			while(true){
				synchronized(myInnerLock.locka){
					System.out.println("if locka");
					try {
						Thread.sleep(30);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					synchronized(myInnerLock.lockb){
						System.out.println("if lockb");
					}
				}
			}
		}else{
			while(true){
				synchronized(myInnerLock.lockb){
					System.out.println("else lockb");
					try {
						Thread.sleep(30);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					synchronized(myInnerLock.locka){
						System.out.println("else locka");
					}
				}
			}
		}
	}
	
	private static class myInnerLock{
		static public Object locka = new Object();
		static public Object lockb = new Object();
	}

	public static void main(String[] args) throws Exception {
		myDeadLock mydeadlock = new myDeadLock(true);
		new Thread(mydeadlock).start();
		Thread.sleep(30);
		flag = false;
		new Thread(mydeadlock).start();
	}
}

验证结果:
简单来说就是:线程A需要a锁,然后申请b锁,线程B需要b锁,然后申请a锁,如果每个线程都申请到了第一个资源,但是后面就无法进行下去了,就陷入停止等待了,这就是死锁现象。

 3,生产者消费者

             生产者消费者是线程通信中最好的示例代码,先看一段代码。

给出基本代码,生产者和消费者,

class consumer implements Runnable{
	private apples apples;
	
	public consumer(apples apples) {
		super();
		this.apples = apples;
	}

	public void run() {
		while(true){
			apples.consum();
			try {
				Thread.sleep(5);
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}
	
}
class producter implements Runnable{
	private apples apples;
	
	public producter(apples apples) {
		super();
		this.apples = apples;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			apples.product();
		}
	}
}

调用函数如下:

public static void main(String[] args) {
		apples apples = new apples();
		new Thread(new producter(apples)).start();
		new Thread(new producter(apples)).start();
		new Thread(new consumer(apples)).start();
		new Thread(new consumer(apples)).start();
	}

a,第一种方式实现线程同步。

class apples{
	int count = 0;
	
	public synchronized void product(){
		count++;
		System.out.println("生产一个苹果,buffer中有"+count);
	}
	public synchronized void consum(){
		count--;
		System.out.println("消费一个苹果,buffer中还有"+count);
	}
}

验证结果:

消费异常现象,出现了负数


产能过剩了


一般情况下,线程间的通信是要带上条件的,现在添加一个限制,缓冲区的大小是5.

b,添加缓冲区的代码示例。

class apples{
	int count = 0;
	
	public synchronized void product(){
		while(true){
			if(count<5) break;
		}
		count++;
		System.out.println("生产一个苹果,buffer中有"+count);
	}
	public synchronized void consum(){
		while(true){
			if(count>0) break;
		}
		count--;
		System.out.println("消费一个苹果,buffer中还有"+count);
	}

验证结果:


这时,容量问题,解决了,但是陷入了无限了,一旦有一方进入了while()循环,且判断一直为真时,就不会释放锁,需要改进

c,改造代码

public void product(){
		while(true){
			if(count<5) break;
		}
		count++;
		System.out.println("生产一个苹果,buffer中有"+count);
	}
	public void consum(){
		while(true){
			if(count>0) break;
		}
		count--;
		System.out.println("消费一个苹果,buffer中还有"+count);
	}

去掉synchronized,验证结果:

数据不安全了,线程除了问题:

方法都试过了,就是行不通,那用什么办法解决呢?

3,等待唤醒机制

等待唤醒机制:类似javaswing中的事件,一旦某个状态满足,就唤醒这个事件。

a,普通方法实现等待唤醒

public synchronized void product(){
		while(count >= 5){
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		count++;
		System.out.println(Thread.currentThread()+"生产一个苹果,buffer中有"+count);
		notifyAll();
	}
	public synchronized void consum(){
		while(0 >= count){
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		count--;
		System.out.println(Thread.currentThread()+"消费一个苹果,buffer中还有"+count);
		notifyAll();
	}

验证结果:


分析:当count<5时,生产线程可以生产,到了5就不能生产了,就wait()了,那么如果没有人唤醒,就一直不醒了,消费线程是count小于等于0时也会wait()如果没有人叫,就也会一直不醒,那么就是在生产线程执行生产动作之后加一个notifyAll()操作,这样消费线程可以消费了,因为buf中至少有一个apple了,如果消费线程执行一个消费动作后,那么也可一唤醒生产线程了,因为消费了一个,buf中至少空出一个位置,这样生产线程就可以生产东西了。

b,新方法实现等待唤醒(Lock类,Condition类组合使用)

class apples{
	int count = 0;
	
	Lock mylock = new ReentrantLock();
	Condition condition_pro = mylock.newCondition();
	Condition condition_con = mylock.newCondition();
	
	public void product(){
		mylock.lock();
		try {
			while(count >= 5)
				condition_pro.await();
			count++;
			System.out.println(Thread.currentThread()+"生产一个苹果,buffer中有"+count);
			condition_con.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			mylock.unlock();
		}
		
	}
	public void consum(){
		mylock.lock();
		try {
			while(count <= 0)
				condition_con.await();
			count--;
			System.out.println(Thread.currentThread()+"消费一个苹果,buffer中还有"+count);
			condition_pro.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			mylock.unlock();
		}
	}
}

验证结果:


总结: 这时,是用等待唤醒机制,有效的解决了问题,jdk1.5之后,引入了lock和condition机制,有效的解决了notify和notifyAll 带来的负面作用,notifyAll唤醒所有的进程,影响效率,但是notify只是唤醒本方的进程,容易全部等待

----------android培训java培训、java学习型技术博客、期待与您交流! -----------

 

相关文章推荐

黑马程序员--线程二(安全性)

多线程最怕的就是安全问题 原因:当多条yg

黑马程序员_java基础二(线程和集合)

线程 线程:一个正在执行的程序。 多线程存在的意义

黑马程序员——java语言基础——线程学习笔记二

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------- 线程: 守护线程特点: 优先级非常低,通常在程序里没有其他线程运行时才会执行它;当守护...

黑马程序员——线程Thread二(线程安全)

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——- 在前一篇的文章中,介绍了实现线程的两个方法,一个是继承Thread类,一个是实现Runable方法,而且比较了...

黑马程序员 Java自学总结十一 线程

总结内容来源于黑马毕老师的java基础教学视频 线程的概述、创建 进程:是一个正在执行的程序.        每一个进行执行都有一个执行顺序,该顺序是一个执行路径,或叫一个控制单元. ...

黑马程序员 Java基础线程状态

新建:start() 运行:具备执行资格,同时具备执行权; 冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格; 临时阻塞状态:线程具备cpu的执...

黑马程序员——JAVA 线程

线程     进程是一个正在执行的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。而线程就是一个独立的控制单元,控制着进程的执行。并且一个进程中至少有一个线程。  ...

黑马程序员——07 进程与线程

------- Windows Phone 7手机开发、.Net培训、期待与您交流! ------- ——进程 可以理解为现在正在运行的程序 进程与线程的关系:进程包含线程 设一个进程要完成两个任务 ...

黑马程序员--javaSE--ThreadLocal实现线程范围的共享变量

------- android培训、java培训、期待与您交流! ---------- 在写这篇博客之前,我登了下黑马论坛,想看一下自己提出的string和null的问题有回应没, 却意外地发现自己...

黑马程序员_Java线程

------- android培训、java培训、期待与您交流! ---------- 多线程: 在一个程序内部也可以实现多个任务并发执行,其中每个任务称为线程。 创建线程: 1.继承Th...
  • Mr_love
  • Mr_love
  • 2013年05月22日 19:24
  • 232
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:黑马程序员 线程分析(二)
举报原因:
原因补充:

(最多只允许输入30个字)