multithreading--线程不安全的案例,及其采用synchronized的解决办法

一:一个线程不安全的情况举例

public class J_xianchenganqaun {

	/**多线程不可控因素:1,线程调度将时间片段分配给哪个线程;2,时间片段的具体时间。
	 * @param args
	 */
	public static void main(String[] args) {
		final Table table=new Table();
		Thread t1=new Thread(){
			public void run(){
				while(true){
					/**
					 * 当beans=0时,run调用了getBean方法,
					 * 抛出了一个未解决的异常,该线程被杀掉
					 */
					int bean=table.getBean();
					System.out.println(getName()+":"+bean);
					Thread.yield();
				}
			}
		};
		Thread t2=new Thread(){
			public void run(){
				while(true){
					int bean=table.getBean();
					System.out.println(getName()+":"+bean);
					Thread.yield();
				}
			}
		};
		t1.start();
		t2.start();
	}
	
}
class Table{
	private int beans=20;
	public int getBean(){
		if(beans==0){
			throw new RuntimeException("没豆了");
		}
		Thread.yield();//当前线程主动放弃CPU时间片,且在当前运行的位置暂停
		return beans--;
		/**
		 * 线程不安全情况:
		 * 1,当程序运行到beans=1时,线程t1调用getBean方法
		 * 2,此时beans=1!=0;所以不抛出异常
		 * 3,接着,t1运行到Thread.yield();自动放弃CPU时间片,t1在这个位置暂停;
		 * 4,t1放弃CPU时间片后,t2调用getBean方法,此时仍然beans=1!=0
		 * 5,接着t2运行到Thread.yield(),t2也自动放弃时间片,t2也在这个位置暂停;
		 * 6,t2放弃后,t1接着暂停的位置继续运行,运行到return语句,返回1,beans变为0,但是错过了抛出异常
		 * 7,接着,t1的时间片过了,t2接着暂停的位置继续执行,t2执行到return,但是此时beans=0,所以返回0,beans变为-1
		 * 8,从此以后beans逐渐往负无穷靠拢,背离了现实意义。
		 */
	}
}

二:上面线程不安全情况的解决办法

/**
 * 多线程并发访问同一资源时,会产生线程安全问题。
 * 解决多线程并发安全问题的办法是:将异步的操作变为同步的.
 * 所谓同步:按先后顺序执行
 * 所谓异步:根本就没有先后的概念
 * @author Administrator
 *
 */
public class J_y {

	public static void main(String[] args) {
		final Table3 table=new Table3();
		Thread t1=new Thread(){
			public void run(){
				while(true){
					/**
					 * 当beans=0时,run调用了getBean方法,
					 * 抛出了一个未解决的异常,该线程被杀掉
					 */
					int bean=table.getBean();
					System.out.println(getName()+":"+bean);
					Thread.yield();
				}
			}
		};
		Thread t2=new Thread(){
			public void run(){
				while(true){
					int bean=table.getBean();
					System.out.println(getName()+":"+bean);
					Thread.yield();
				}
			}
		};
		t1.start();
		t2.start();
	}
	
}
class Table3{
	private int beans=20;
	/**
	 * synchronized的作用是当线程t1调用该方法时,会加把锁,实际上是给对象加把锁
	 * 这样即使在getBean方法内部,t1的时间片用完了,t2也只能在getBean方法外面等着
	 * 也进不去,等时间片再次轮到t1,t1会继续执行,就是说只能有一个线程进入直到执行完位置,即将异步的操作变为同步的
	 * @return
	 */
	synchronized public int getBean(){
		if(beans==0){
			throw new RuntimeException("没豆了");
		}
		Thread.yield();//当前线程主动放弃CPU时间片
		return beans--;
	}
}

三:更加高效的解决方式

上面使用synchronized同步了整个方法,但是很多时候,只需要同步方法的局部就可解决安全问题,另外因为同步的范围小了,多线程并发执行的效率也会增加,因此可以采用同步块的方式解决线程安全问题。
public class K_jianshiqi {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		final Table1 table=new Table1();
		Thread t1=new Thread(){
			public void run(){
				while(true){
					int bean=table.getBean();
					System.out.println(getName()+":"+bean);
					Thread.yield();
				}
			}
		};
		Thread t2=new Thread(){
			public void run(){
				while(true){
					int bean=table.getBean();
					System.out.println(getName()+":"+bean);
					Thread.yield();
				}
			}
		};
		t1.start();
		t2.start();
	}

}
class Table1{
	private int beans=20;
	public int getBean(){
		/**
		 * 同步块可以有效的缩小同步范围,从而提高并发执行效率。

使用同步块,必须要保证多线程看到的同步监视器这个对象是同一个!
		 */
		synchronized(this){//这里的this是table,t1和t2看到的是同一个,也可以换成任意字符串,但是不可以是new Object(),因为t1和t2会创建!=的对象
		if(beans==0){
			throw new RuntimeException("没豆了");
		}
		Thread.yield();
		return beans--;
	}
}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值