关闭

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

325人阅读 评论(0) 收藏 举报

----------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学习型技术博客、期待与您交流! -----------

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:6230次
    • 积分:244
    • 等级:
    • 排名:千里之外
    • 原创:19篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章存档