多线程__并发同步synchronized

在这里现有三个线程不安全的实例代码

一、线程不安全的体现

1.模拟抢票

package com.lzy.syn;
/**
 * 线程不安全,有负数,重复
 * @author Administration
 *
 */


public class aUnsafeTest01 implements Runnable {
	private int ticketNums=10; //票数
	private boolean flag=true;
	@Override
	public void run() {
		while(flag) {
			test();
		}
		
	}
	public void  test() {
		while(true) {
			if(ticketNums<=0) {
				break;
			}
			try {
				Thread.sleep(200);//模拟延迟
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
		}
	}
	public static void main(String[] args) {
		//一份资源
		aUnsafeTest01 web=new aUnsafeTest01();
		System.out.println(Thread.currentThread().getName());
		//多个代理
		new Thread(web,"栗").start();
		new Thread(web,"李").start();
		new Thread(web,"力").start();
		
	}
}

运行结果:
main
力–>10
李–>9
栗–>9
栗–>8
力–>7
李–>6
栗–>5
力–>4
李–>3
栗–>2
力–>1
李–>0
栗–>-1
可以看出在模拟抢票过程中出现-1,以及同一张票被两个人都抢到,这显然是不合常理的。
出现-1的原因是当票数是1的时候力进入test()方法中拿到了1,但由于延迟等原因,还没有运行到

System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);

这一段代码时,也就是说虽然最后一张票数已经有归属了,但是代码中ticketNums还是等于1,这时候”李“和“栗”也进入Test()方法,所以就会出现负数,
出现相同的票数是因为
在这里插入图片描述
如图所示,在票数为9时 李拿走了9,但是在还没有返还票数8的时候,栗也把9拿走了,所以说出现了票数重复

2.模拟取钱

package com.lzy.syn;
/**
 * 线程不安全:取钱
 * @author Administration
 *
 */
public class aUnSafeTrest02 {
	public static void main(String[] args) {
		//账户
		Account account=new Account(100,"礼金");
		UnSafeDrawing you=new UnSafeDrawing(account,80,"自己");
		UnSafeDrawing wife=new UnSafeDrawing(account,90,"妻子");
		you.start();
		wife.start();	
	}
}
//账户
class Account{
	int money; //金额
	String name;//取钱的人名称
	public Account(int money, String name) {
		
		this.money = money;
		this.name = name;
	}
	
}
//模拟取款
class UnSafeDrawing extends Thread{
	Account account;//取钱账户
	int drawingMoney;//取得钱数
	int packetTotal;//放入口袋的总钱数  
	public UnSafeDrawing(Account account, int drawingMoney,String name) {
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}
	
	public void run() {
		if(account.money-drawingMoney<0) {
			return;
		}
		try {
			Thread.sleep(1000);//模拟延迟
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		account.money-=drawingMoney; //账户余额=账户余额-取得钱数
		packetTotal+=drawingMoney;  //口袋里的钱=口袋里的钱+取得钱数
		System.out.println(this.getName()+"-->账户余额为:"+account.money);
		System.out.println(this.getName()+"-->口袋的钱为:"+packetTotal);
	}
	
	
}

运行结果:
自己–>账户余额为:-70
自己–>口袋的钱为:80
妻子–>账户余额为:-70
妻子–>口袋的钱为:90

这里模拟你和你的妻子同时取一张银行卡上的钱。你取80,妻子取90,在运行结果中,账户余额为-70,显然也是不符合常理,这也是线程不安全的表现

3.创建10000个list对象

package com.lzy.syn;

import java.util.ArrayList;
import java.util.List;

/**
 * 线程不安全:取钱
 * @author Administration
 *
 */
public class aUnsafeTrst03 {

	public static void main(String[] args) {
		List<String>list=new ArrayList<String>();
		for(int i=0;i<10000;i++) {
			new Thread(()->{
				list.add(Thread.currentThread().getName());
			}) .start();
		}
		System.out.println(list.size());
	}
}

运行结果
9982
显然,运行结果并非达到我们的预期,创建10000个,这也是线程不安全。

二、如何解决以上线程不安全问题

解决以上问题,需要线程同步,它包括两种用法:synchronized方法和synchronized块
缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。

1.

package com.lzy.syn;
/**
 * 线程安全,在并发时帮正数据的正确性、效率尽可能高
 * synchronized
 * 1.同步方法
 * 2.同步块
 * @author Administration
 *
 */
public class bSafeTest01  {
	public static void main(String[] args) {
		//一份资源
		SafeWeb123061 web=new SafeWeb123061();	
		//多个代理
		new Thread(web,"栗").start();
		new Thread(web,"李").start();
		new Thread(web,"力").start();		
	}	
}
class SafeWeb123061 implements Runnable{
	private int ticketNums=10;
	private boolean flag=true;
	@Override
	public void run() {
		while(flag) {
			try {
				Thread.sleep(200);//模拟延迟
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			test();
		}
		
	}
	//线程安全  同步
	public synchronized void  test() {
		
			if(ticketNums<=0) {
			flag=true;
			return;
			}
			//模拟延时
			try {
				Thread.sleep(200);//模拟延迟
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
		}
	}

运行结果:
栗–>10
李–>9
力–>8
李–>7
栗–>6
李–>5
力–>4
李–>3
栗–>2
李–>1

在test()方法加入synchronized后,假如"栗"进入Test()方法中,开始取票,Test()方法将会被锁定,其他两个代理只能等”栗“完成后,才能轮到他们,避免同时进去导致票数出现问题。
注意:这里锁得不是test()方法,而是锁的对象web,要知道test()中操作的flag,ticketNums都是属于SafeWeb123061类的。

2.模拟取钱

package com.lzy.syn;
/**
 * 线程不安全:取钱
 * @author Administration
 *
 */
public class bSafeTrst02 {

	public static void main(String[] args) {
		//账户
		Account account=new Account(100,"礼金");
		Drawing you=new Drawing(account,80,"自己");
		Drawing wife=new Drawing(account,90,"妻子");
		you.start();
		wife.start();
		
	}
}
class Account{
	int money; //金额
	String name;//名称
	public Account(int money, String name) {
		
		this.money = money;
		this.name = name;
	}
	
}
//模拟取款
class Drawing extends Thread{
	Account account;//取钱账户
	int drawingMoney;//取得钱数
	int packetTotal;//口袋的总数
	public Drawing(Account account, int drawingMoney,String name) {
		super(name);
		this.account = account;
		this.drawingMoney = drawingMoney;
	}
	public void run() {
		test();
	}
	public void test() {
		if(account.money==0) {
			return;                   //提高性能
		}
		synchronized(account) {
		if(account.money-drawingMoney<0) {
			return;
		}
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		account.money-=drawingMoney;
		packetTotal+=drawingMoney;
		System.out.println(this.getName()+"-->账户余额为:"+account.money);
		System.out.println(this.getName()+"-->口袋的钱为:"+packetTotal);
		}
	}
	
	
}

运行结果:
自己–>账户余额为:20
自己–>口袋的钱为:80

由于你取了80,卡里省20,所以妻子取90判定失败
如果模拟抢票的代码懂了。在这你可能认为只要在模拟取钱的Test()前加上synchronized就可以解决问题了,但这是错误的,因为锁资源没有锁对,test()中的account.money,drawingMone,packetTotal都是属于Drawing类的, Drawing相当于取款机,你锁一个取款机显然是不对的,锁的应该是你的账户account,所以这就用到”同步块“

同步块:synchronized(obj){ },obj称之为同步监视器
1.obj可以是任何对象,但是推荐使用共享资源作为同步监视器
2.同步方法中无需指定同步监视器,因为同步方法的同步监视器时this即该对象本身,或class即类的模子

3.创建对象

package com.lzy.syn;
import java.util.ArrayList;
import java.util.List;

/**
 * 线程安全,容器
 * @author Administration
 *
 */
public class bSafeTrst03 {

	public static void main(String[] args) {
		List<String>list=new ArrayList<String>();
		for(int i=0;i<10000;i++) {
			new Thread(()->{
				synchronized(list) {
				list.add(Thread.currentThread().getName());
				}
			}) .start();
		}
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(list.size());
	}
}

这里用到的依然是同步块

三、提高性能

package com.lzy.syn;

import java.util.ArrayList;
import java.util.List;

/**
 * 线程安全:在并发时保证数据的正确性、效率尽可能高
 * @author Administration
 *
 */
public class bSynBlockTest04 {

	public static void main(String[] args) {
		//一份资源
		SynWebd12306 web =new SynWebd12306();
				//多个代理
				new Thread(web,"栗").start();
				new Thread(web,"李").start();
				new Thread(web,"力").start();;
	}
	
}
class SynWebd12306 implements Runnable{
private int ticketNums=10;
private boolean flag=true;
	@Override
	public void run() {
		while(flag) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			test5();
		}
		
	}
	//线程安全:尽可能锁定合理的范围(不是指代码  指数据的完整性)
	//double checking
	public  void test5() {
		if(ticketNums<=0) {//考虑的是没有票的情况
			flag = false;
			return ;
		}
		synchronized(this) {			
			if(ticketNums<=0) {//考虑最后的1张票
				flag = false;
				return ;
			}
			//模拟延时
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
		}
	}
	//线程不安全  范围太小锁不住
	public  void test4() {
		synchronized(this) {
				if(ticketNums<=0) {
					flag = false;
					return ;
				}
			}
			//模拟延时
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
		
	}
	//线程不安全  ticketNums对象在变
	public  void test3() {
		synchronized((Integer)ticketNums) {
			if(ticketNums<=0) {
				flag = false;
				return ;
			}
			//模拟延时
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
		}
	}
	//线程安全 范围太大 -->效率低下
	public  void test2() {
		synchronized(this) {
			if(ticketNums<=0) {
				flag = false;
				return ;
			}
			//模拟延时
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
		}
	}
	
	
	//线程安全  同步
	public synchronized void test1() {
		if(ticketNums<=0) {
			flag = false;
			return ;
		}
		//模拟延时
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值