java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)

java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)

直接po代码和截图

源码点这里

java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)_第1张图片

我们根据一个案例来讲解线程同步的知识点!

案例如下:

张无忌和赵敏有一个共同的存钱罐,存钱罐中初始有1000元,然后张无忌存了10次钱,每次存100元,而赵敏取了10次钱,每次取50元,最终存钱罐还剩1500元

Zhangwuji类

package com.demo.thread2;

public class Zhangwuji implements Runnable{
	
	private String name;
	
	private MoneyBox moneyBox;
	
	public Zhangwuji(String name, MoneyBox moneyBox){
		this.name = name;
		this.moneyBox = moneyBox;
	}
	@Override
	public void run() {
		add();
	}
	
	public void add() {
		for (int frequency = 1; frequency <= 10; frequency++) {
			System.out.println("#######我是" + name + ",这是我第" + frequency + "次存钱#######");
			moneyBox.add(100, name, frequency);
		}
	}

}

Zhaomin类

package com.demo.thread2;

public class Zhaomin implements Runnable {

	private String name;

	private MoneyBox moneyBox;

	public Zhaomin(String name, MoneyBox moneyBox) {
		this.name = name;
		this.moneyBox = moneyBox;
	}

	@Override
	public void run() {
		get();
	}

	public void get() {
		for (int frequency = 1; frequency <= 10; frequency++) {
			System.out.println("*******我是" + name + ",这是我第" + frequency + "次取钱*******");
			moneyBox.get(50, name, frequency);
		}
	}

}

MoneyBox类

package com.demo.thread2;

//存钱罐类
public class MoneyBox {
	
	//存钱罐中的余额
	private double money;
	
	public double getMoney() {
		return money;
	}

	// 构造函数 /构造方法/构造器
	public MoneyBox(double money) {
		this.money = money;
	}
	
	//这是存钱的方法,暂时先不加synchronized(同步),先不写成同步方法,看下什么效果
	public void add(double inMoney, String threadName, int number) {
		 double oldMoney = money;
		 try {
			 //拿到旧的金额后,让当前线程睡眠会以使别的线程操作它
			Thread.sleep(50);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		 money = oldMoney + inMoney;
		 System.out.println("-------" + threadName + "第" + number + "次存了" + inMoney + "元,现在存钱罐中的余额=" + money);
	}
	
	//这是取钱的方法,暂时先不加synchronized(同步),先不写成同步方法,看下什么效果
		public void get(double outMoney, String threadName, int number) {
			 double oldMoney = money;
			 try {
				 //拿到旧的金额后,让当前线程睡眠会以使别的线程操作它
				Thread.sleep(40);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			 money = oldMoney - outMoney;
			 System.out.println("$$$$$$$" + threadName + "第" + number + "次取了" + outMoney + "元,现在存钱罐中的余额=" + money);
		}

		@Override
		public String toString() {
			return "---------------我是存钱罐,我现在的余额=" + money + "---------------";
		}
		
		
	

}

Test类

package com.demo.thread2;

public class Test {

	//测试线程同步
	public static void main(String[] args) {
		//存钱罐初始余额为1000元
		MoneyBox moneyBox = new MoneyBox(1000);
		System.out.println("-------存钱罐初始余额=" + moneyBox.getMoney() + "-------");
		
		//保证张无忌和赵敏这两个线程操作的是同一个对象
		//利用构造函数传参,把moneyBox存钱罐对象传入
		Zhangwuji zhangwuji = new Zhangwuji("张无忌", moneyBox);
		Zhaomin zhaomin = new Zhaomin("赵敏", moneyBox);
		
		Thread t1 = new Thread(zhangwuji, "张无忌少侠");
		Thread t2 = new Thread(zhaomin, "赵敏郡主");
		
		//启动线程
		t1.start();
		t2.start();
		
		//主线程睡眠4秒,以使其他两个线程先执行完
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//打印存钱罐中最终的余额
		System.out.println(moneyBox);
	}

}

运行结果如下:存钱罐中的初始额是1000元,张无忌存了10次钱,每次存100,而赵敏取了10次钱,每次取50,最后存钱罐中应该还剩1500元才对,可下面的运行结果却怎么也不对,可以多运行几次进行测试,每次运行结果都可能不一样,反正存钱罐中余额不等于1500,如果等于1500,那估计是巧合吧

java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)_第2张图片

java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)_第3张图片

接下来,看改进版

Zhangwuji2类

package com.demo.thread2;

public class Zhangwuji2 implements Runnable{
	
	private String name;
	
	private MoneyBox2 moneyBox2;
	
	public Zhangwuji2(String name, MoneyBox2 moneyBox2){
		this.name = name;
		this.moneyBox2 = moneyBox2;
	}
	@Override
	public void run() {
		add();
	}
	
	public void add() {
		for (int frequency = 1; frequency <= 10; frequency++) {
			System.out.println("#######我是" + name + ",这是我第" + frequency + "次存钱#######");
			moneyBox2.add(100, name, frequency);
		}
	}

}

Zhaomin2类

package com.demo.thread2;

public class Zhaomin2 implements Runnable {

	private String name;

	private MoneyBox2 moneyBox2;

	public Zhaomin2(String name, MoneyBox2 moneyBox2) {
		this.name = name;
		this.moneyBox2 = moneyBox2;
	}

	@Override
	public void run() {
		get();
	}

	public void get() {
		for (int frequency = 1; frequency <= 10; frequency++) {
			System.out.println("*******我是" + name + ",这是我第" + frequency + "次取钱*******");
			moneyBox2.get(50, name, frequency);
		}
	}

}

MoneyBox2类

package com.demo.thread2;

//存钱罐类
public class MoneyBox2 {
	
	//存钱罐中的余额
	private double money;
	
	public double getMoney() {
		return money;
	}

	// 构造函数 /构造方法/构造器
	public MoneyBox2(double money) {
		this.money = money;
	}

	/**
	 * 线程同步是为了保证当多个线程同时并发访问某资源时,在任何指定时间只有一个线程
	 * 可以访问该资源,从而使该资源的数据保持一致
	 * 其中同步又分为同步方法和同步块。我们先看同步方法,同步方法的实现很简单,只要
	 * 在方法的声明前面加上 synchronized 关键字就可以。它的原理是在任何时刻一个对象中只
	          有一个同步方法可以执行。只有对象当前执行的同步方法结束后,同一个对象的另一个同步
	          方法才可能开始。这里的思想是,保证每个线程在调用对象同步方法时以独占的方法操作该
	          对象。
	          线程同步的粒度越小越好,即线程同步的代码块越小越好。这时我们可以使用同步块。
	          因为同步块后面需要传递同步的对象参数,所以它可以指定更多的对象享受同步的优势,而
	         不像同步方法那样只有包含相关代码的对象受益。	
	 */
	
	/**
	 * 同一时刻只能由一个同步方法执行,因为
	 * 该同步方法已经设置了锁,阻止了其他同步方法的启动
	 */
	
	/**
	 *
	 *synchronized(对象)同步块后面的小括号中,可以放任何对象,只要是引用类型(即对象类型)就可以,
	 *所以synchronized同步块比较灵活,没有局限性(同步块后面需要传递同
	 *步的对象,所以同步块的好处就是,可以指定更多的对象享受同步的优势),而不像同步方法那样只
	 *有包含相关代码的对象受益(即,同步方法写在哪个类中,哪个类的对象才能有同步的功能,所以同步方法相
	 *对于同步块来说,有局限性,所以,我个人建议大家使用同步块)
	 */
	
	//这是存钱的方法,加上synchronized(同步),写成同步方法,看下什么效果
	public synchronized void add(double inMoney, String threadName, int number) {
		 double oldMoney = money;
		 try {
			 //拿到旧的金额后,让当前线程睡眠会以使别的线程操作它
			Thread.sleep(50);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		 money = oldMoney + inMoney;
		 System.out.println("-------" + threadName + "第" + number + "次存了" + inMoney + "元,现在存钱罐中的余额=" + money);
	}
	
	//这是取钱的方法,加上synchronized(同步),写成同步方法,看下什么效果
		public synchronized void get(double outMoney, String threadName, int number) {
			 double oldMoney = money;
			 try {
				 //拿到旧的金额后,让当前线程睡眠会以使别的线程操作它
				Thread.sleep(40);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			 money = oldMoney - outMoney;
			 System.out.println("$$$$$$$" + threadName + "第" + number + "次取了" + outMoney + "元,现在存钱罐中的余额=" + money);
		}

		@Override
		public String toString() {
			return "---------------我是存钱罐,我现在的余额=" + money + "---------------";
		}
}

Test2类

package com.demo.thread2;

public class Test2 {

	//测试线程同步
	public static void main(String[] args) {
		//存钱罐初始余额为1000元
		MoneyBox2 moneyBox2 = new MoneyBox2(1000);
		System.out.println("-------存钱罐初始余额=" + moneyBox2.getMoney() + "-------");
		
		//保证张无忌和赵敏这两个线程操作的是同一个对象
		//利用构造函数传参,把moneyBox存钱罐对象传入
		Zhangwuji2 zhangwuji2 = new Zhangwuji2("张无忌", moneyBox2);
		Zhaomin2 zhaomin2 = new Zhaomin2("赵敏", moneyBox2);
		
		Thread t1 = new Thread(zhangwuji2, "张无忌少侠");
		Thread t2 = new Thread(zhaomin2, "赵敏郡主");
		
		//启动线程
		t1.start();
		t2.start();
		
		//主线程睡眠4秒,以使其他两个线程先执行完
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//打印存钱罐中最终的余额
		System.out.println(moneyBox2);
	}

}

运行结果如下:这结果就是正确的

java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)_第4张图片

java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)_第5张图片

接下来,我们再改进一下

Zhangwuji3类

package com.demo.thread2;

public class Zhangwuji3 implements Runnable{
	
	private String name;
	
	private MoneyBox3 moneyBox3;
	
	public Zhangwuji3(String name, MoneyBox3 moneyBox3){
		this.name = name;
		this.moneyBox3 = moneyBox3;
	}
	@Override
	public void run() {
		add();
	}
	
	public void add() {
		for (int frequency = 1; frequency <= 10; frequency++) {
			System.out.println("#######我是" + name + ",这是我第" + frequency + "次存钱#######");
			moneyBox3.add(100, name, frequency);
		}
	}

}

Zhaomin3类

package com.demo.thread2;

public class Zhaomin3 implements Runnable {

	private String name;

	private MoneyBox3 moneyBox3;

	public Zhaomin3(String name, MoneyBox3 moneyBox3) {
		this.name = name;
		this.moneyBox3 = moneyBox3;
	}

	@Override
	public void run() {
		get();
	}

	public void get() {
		for (int frequency = 1; frequency <= 10; frequency++) {
			System.out.println("*******我是" + name + ",这是我第" + frequency + "次取钱*******");
			moneyBox3.get(50, name, frequency);
		}
	}

}

MoneyBox3类

package com.demo.thread2;

//存钱罐类
public class MoneyBox3 {
	
	//存钱罐中的余额
	private double money;
	
	public double getMoney() {
		return money;
	}

	// 构造函数 /构造方法/构造器
	public MoneyBox3(double money) {
		this.money = money;
	}
	
	//这是存钱的方法,加上synchronized(同步)关键字,写成同步块,看下什么效果
	public void add(double inMoney, String threadName, int number) {
		/**
		 * 如果,我们add()函数写成synchronized同步方法,而不是写成synchronized同步块的话,
		 * 假设add()函数中有一段安全验证等等业务代码,而且这段安全验证等等业务代码执行挺长时间,而在执行这段安
		 * 全验证代码的这段时间内,其他线程又不能操作(本类/本对象)同一个MoneyBox3对象的其他同
		 * 步方法,要一直等add同步方法执行结束后,其他线程才能操作(本类/本对象)同一个MoneyBox3对
		 * 象的其他同步方法,显然,效率比较低
		 * 所以,建议使用synchronized同步块
		 */
		//安全验证等业务代码占用时间
		 try {
			 //拿到旧的金额后,让当前线程睡眠会以使别的线程操作它
			Thread.sleep(50);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//使用同步块,可以减小线程同步的粒度,线程同步的粒度越小越好,即线程同步的代码块越小越好
		 /*
		  * 同步代码块可以用更细粒度的控制锁,同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越
		  * 大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好
		  */
		 synchronized(this){
//			 double oldMoney = money;
//			 money = oldMoney + inMoney;
//			 可以将上面两句话,合二为一
			 money += inMoney;
			 System.out.println("-------" + threadName + "第" + number + "次存了" + inMoney + "元,现在存钱罐中的余额=" + money);
		 }
	}
	
	//这是取钱的方法,加上synchronized(同步)关键字,写成同步块,看下什么效果
		public void get(double outMoney, String threadName, int number) {
			/**
			 * 安全验证等业务代码占用时间
			 * 假设这里有一段安全验证等等业务代码,而且这段安全验证等等业务代码执行挺长时间
			 * 
			 */
			 try {
				 //拿到旧的金额后,让当前线程睡眠会以使别的线程操作它
				Thread.sleep(40);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			 //使用同步块,可以减小线程同步的粒度,线程同步的粒度越小越好,即线程同步的代码块越小越好
			 /*
			  * 同步代码块可以用更细粒度的控制锁,同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越
			  * 大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好
			  */
			 synchronized(this){
//				 double oldMoney = money;
//				 money = oldMoney - outMoney;
//				可以 将上面两句话,合二为一
				 money -= outMoney;
				 System.out.println("$$$$$$$" + threadName + "第" + number + "次取了" + outMoney + "元,现在存钱罐中的余额=" + money);
			 }
		}

		@Override
		public String toString() {
			return "---------------我是存钱罐,我现在的余额=" + money + "---------------";
		}
	/*
	 * 小结:
	 * 同步方法直接在方法上加synchronized实现加锁,同步代码块则在方法内部加锁,很明显,同步方法锁的范
	 * 围比较大,而同步代码块范围要小点,一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯
	 * 定是范围越小越好,这样性能更好
	 * 
	 * 同步代码块可以用更细粒度的控制锁
	 * 
	 * 使用同步块,可以减小线程同步的粒度,线程同步的粒度越小越好,即线程同步的代码块越小越好	
	 */
		
	

}

Test3类

package com.demo.thread2;

public class Test3 {

	//测试线程同步
	public static void main(String[] args) {
		//存钱罐初始余额为1000元
		MoneyBox3 moneyBox3 = new MoneyBox3(1000);
		System.out.println("-------存钱罐初始余额=" + moneyBox3.getMoney() + "-------");
		
		//保证张无忌和赵敏这两个线程操作的是同一个对象
		//利用构造函数传参,把moneyBox存钱罐对象传入
		Zhangwuji3 zhangwuji3 = new Zhangwuji3("张无忌", moneyBox3);
		Zhaomin3 zhaomin3 = new Zhaomin3("赵敏", moneyBox3);
		
		Thread t1 = new Thread(zhangwuji3, "张无忌少侠");
		Thread t2 = new Thread(zhaomin3, "赵敏郡主");
		
		//启动线程
		t1.start();
		t2.start();
		
		//主线程睡眠4秒,以使其他两个线程先执行完
		try {
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//打印存钱罐中最终的余额
		System.out.println(moneyBox3);
	}

}

运行结果如下:这结果也是正确的

java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)_第6张图片

java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信)_第7张图片

你可能感兴趣的:(java中线程的一些相关概念,第4篇(线程同步、死锁、线程间的通信))