关闭

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

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


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

1,线程引发的数据不安全问题介绍

            就拿火车票售票为分析,假设一个火车站有是个售票窗口,需要卖出10张票,不能卖0号票以及负号票,从10往下递减。

用继承Thread类实现:(注意:ticketnum在类中要静态化,否则就是私有成员了,那就是4个线程一共卖出40张票了)

class saleticket extends Thread{
	//因为采用继承方式执行线程对象,所以共享数据要设置为静态的,这样共享数据属于
	//这个子线程类的,而不是具体的某个子线程对象,各个对象可以对这个共享数据操作
	//否则的话,每个线程都可以卖出十张票了,那么多线程就失去原有的意义了
	private static int ticketnum = 10;
	public void run() {
		while(ticketnum>0){
			System.out.println(this.getName()+"卖出第"+(ticketnum--)+"张票");
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

验证结果:


卖出了0号票,说明线程安全出现了问题。这种出问题的几率很难捕捉到,5到6次才能捕捉到这个现象。下面用Runnable方法来实现,把睡眠动作放在打印之前,这样,问题很容易展现出来,能够卖出-2号票。

用Runnable接口实现:(因为Runnable接口的实例对象只有一个,四个线程公用这个对象,所以ticketnum可以为私有成员了,当线程结束是,对象就会被GC回收,节省了内存资源)

class saleticket1 implements Runnable{ 
	//因为saleticket1对象之创建了一次,但是通过这个saleticket1创建了四个线程
	//相当于四个线程对saleticket1对象进行操作,所以就可以把共享数据设置为私有
	//非静态的,这样生命周期就可以缩短很多,节约资源
	private int ticketnum = 10;
	public void run() {
		while (ticketnum>0) {
			try { 
				Thread.sleep(20); 
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread() + "卖出第" + (ticketnum--)
					+ "张票");
		}
	}
}

验证结果:


2,锁的原理

           线程出现了安全问题,那么用什么办法解决呢,那就是用锁机制,锁的使用注意事项:

 //1,在继承thread类中,锁必须是类的静态成员,否则不起作用,线程只是调用自己的锁,对其他线程没有影响
 //2,在实现Runnable接口中,锁不需要是静态成员,因为线程公用一个runnable对象

代码如下:

class synchronized_method extends Thread{
	private static int ticketnum = 10;
	private static Object mutex = new Object();
	public void run() {
		while(true){
			//同步的范围约定,共享数据的第一次和最后一次的使用范围即可其他不需要同步
			//切忌:不要在while循环上上锁,上锁就只有一个线程卖票了
			//助记方法:火车的卫生间	1,红色(有人)关门,类似上锁
			//				...........
			//			2,绿色(没人)开门,类似解锁
			synchronized (mutex) {
				if(ticketnum <= 0){
					break;
				}
				try { 
					
					Thread.sleep(20); 
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(this.getName()+"卖出第"+(ticketnum--)+"张票");
			}
			
		}
	}
}

结果验证:


3,同步方式在线程创建方式中的可行性分析(继承Thread类)

a,建立私有对象锁

class synchronized_ticket extends Thread{
	private static int ticketmun = 10;
	private Object obj = new Object();

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			synchronized(obj){
				if(ticketmun <= 0){
					break;
				}
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					// TODO: handle exception
				}
				System.out.println(this.getName()+"sales:\t"+ticketmun--);
			}
		}
	}
	/*
	 * 结论:这种方法不可行
	 * 原因:object对象是线程私有的,且在初始化时使用new方式生成的
	 * 		这样四个线程就有四个Object对象,在run方法中使用同步代码块,锁使用自己
	 * 		创建的obj对象,这样四个线程都是用自己的obj实例,起不到共享数据同步的作用了
	 * */
	
}

验证结果:


b,建立类静态锁

class synchronized_ticket1 extends Thread{
	private static int ticketmun = 10;
	private static Object obj = new Object();

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			synchronized(obj){
				if(ticketmun <= 0){
					break;
				}
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					// TODO: handle exception
				}
				System.out.println(this.getName()+"sales:\t"+ticketmun--);
			}
		}
	}
	/*
	 * 结论:这种方法可行
	 * 原因:object对象是静态私有的,且在类初始化时显示方式生成的
	 * 		这样四个线程就共享这个Object对象,在run方法中使用同步代码块,锁变成了共享锁
	 * 		,这样四个线程就需要抢夺锁资源了,方法可行
	 * */
}

验证结果:


c,建立私有对象锁,但是锁是一个引用对象,引用从构造函数传进来的Object对象

class synchronized_ticket2 extends Thread{
	private static int ticketmun = 10;
	private Object obj;
	
	public synchronized_ticket2(Object obj){
		this.obj = obj;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			synchronized(obj){
				if(ticketmun <= 0){
					break;
				}
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					// TODO: handle exception
				}
				System.out.println(this.getName()+"sales:\t"+ticketmun--);
			}
		}
	}
	/*
	 * 结论:这种方法可行
	 * 原因:object对象是私有的,但是在类实例化时引用了一个对象锁,并且
	 * 		这四个线程的obj同时这引用个Obj对象,在run方法中使用同步代码块,锁变成了共享锁
	 * 		,这样四个线程就需要抢夺锁资源了,方法可行
	 * */
}

验证结果:


d,在thread子类中定义同步函数,并且在run方法中调用,这个方法可行吗?

       假设同步函数是静态的,那么就有可能实现线程同步,下面是代码实现,

class synchronized_ticket3 extends Thread{
	private static int ticketmun = 10;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			if(ticketmun <= 0){
				break;
			}
			try {
				Thread.sleep(10);
			} catch (Exception e) {
				// TODO: handle exception
			}
			show();
		}
	}
	
	public static synchronized void show(){
		System.out.println(Thread.currentThread().getName()+"sales:\t"+ticketmun--);
	}
	/*
	 * 结论:这种方法不可行
	 * 原因:虽说使用了静态同步函数,但是同步的锁还是线程子类对象本身
	 * */
}

验证结果:

e,如果无法在线程子类中实现同步函数,那使用其他类(静态内部类)是否可行呢?

class synchronized_ticket4 extends Thread{
	private static int ticketmun = 10;
	private static synchronized_ticket4.synclass syn = new synchronized_ticket.synclass();
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			if(ticketmun <= 0){
				break;
			}
			try {
				Thread.sleep(10);
			} catch (Exception e) {
				// TODO: handle exception
			}
			syn.sellTicket(ticketmun--);
		}
	}
	
	static class synclass{
		 public synchronized void sellTicket(int tickNum){  
			 if(tickNum >0){
		        try {  
		           Thread.sleep(10);  
		        }catch (InterruptedException e) {  
		        	e.printStackTrace();  
		        }  
		        System.out.println(Thread.currentThread()+". sale: ticket"+ tickNum--);  
		     }  
		  }  
	}
	
	/*
	 * 结论:这种方法完全可行
	 * 原因: 因为内部类被初始化时只有一个类对象,这样就可以同步了
	 * 补充:内部类也可以,只要是在类初始化是被定义为静态的就可以了,不再赘述
	 * 	private static synchronized_ticket4.synclass syn = new synchronized_ticket4().new synclass();
	 * */
}

验证结果:


f,改进方法:将共享对象封装在一个内部类中,着呀更容易理解,同时对共享数据进行的隐藏,

class synchronized_ticket5 extends Thread{
	private static synchronized_ticket5.synclass syn = new synchronized_ticket5().new synclass();
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			try {
				Thread.sleep(10);
			} catch (Exception e) {
				// TODO: handle exception
			}
			syn.sellTicket();
		}
	}
	
	private class synclass{
		private int ticketmun = 10;
		public synchronized void sellTicket(){  
			 if(ticketmun >0){
		        try {  
		           Thread.sleep(10);  
		        }catch (InterruptedException e) {  
		        	e.printStackTrace();  
		        }  
		        System.out.println(Thread.currentThread()+". sale: ticket"+ticketmun --);  
		     }  
		  }  
	}
	
	/*
	 * 结论:这种方法完全可行
	 * 原因: 因为静态内部类被初始化时只有一个类对象,这样就可以同步了
	 * 补充:内部类也可以,只要是在类初始化是被定义为静态的就可以了,不再赘述,更严谨
	 * 		的做法是把内部类私有化,外部无法直接调用
	 * */
}

验证结果:


补充:实际开发中不需要使用私有内部类,普通的一个java类就可以作为锁,只要保证每个线程引用同一个锁对象即可,就是把私有内部类提取到外面,用线程引用就可以了,不需要太严谨。

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

0
0

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