synchronized同步和join用法

1 synchronized同步


public class Account {

	private int balance;

	public Account(int balance) {
		this.balance = balance;
	}

	public int getBalance() {
		return balance;
	}

	/*
	 * add方法和withdraw方法不是同步方法的情况下:
	 * 两个线程操作同一个Account对象,并访问Account对象中add和withdraw方法时获取到的num值并不是实时的。保证不了数据的安全性。
	 * 同步方法的情况下:
	 * 同步方法,方法含有锁,那么这个锁就是该方法所在的Account类对象
	 * 当同一个Account对象调用add方法时就不会再执行withdraw方法,只有add方法释放掉Account类锁后才能执行withd方法
	 * 这样保证balance数据的安全性。
	 * 
	 * 注意:每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才会有意义,
	 * 在并发环境下,一个没有共享的对象作为锁是没有意义的。
	 * 例如:每个线程中都创建一个Test对象,而Test对象中的同步方法是不起作用的。
	 * 
	 * 专业描述synchronized(lock):
	 * 	每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,
	 * 	就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,
	 * 	当一个被线程被唤醒 (notify)后,才会进入到就绪队列,等待cpu的调度。
	 * 	当一开始线程a第一次执行account.add方法时,jvm会检查锁对象account 的就绪队列是否已经有线程在等待,如果有则表明account的锁已经被占用了,由于是第一次运行,account的就绪队列为空,所以线程a获得了锁, 执行account.add方法。
	 * 	如果恰好在这个时候,线程b要执行account.withdraw方法,因为线程a已经获得了锁还没有释放,所以线程 b要进入account的就绪队列,等到得到锁后才可以执行。
	 * 	一个线程执行临界区代码过程如下:
	 *	1 获得同步锁
	 *	2 清空工作内存
	 *	3 从主存拷贝变量副本到工作内存
	 *	4 对这些变量计算
	 *	5 将变量从工作内存写回到主存
	 *	6 释放锁
	 *	可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
	 * 
	 * 
	 */
	public synchronized void add(int num) {
		balance = balance + num;
	}

	public synchronized void withdraw(int num) {
		balance = balance - num;
	}

	public static void main(String[] args) throws InterruptedException {
		Account account = new Account(1000);
		Thread a = new Thread(new AddThread(account, 20), "add");
		Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");
		a.start();
		b.start();
		a.join();
		b.join();
		System.out.println(account.getBalance());
	}

	static class AddThread implements Runnable {
		Account account;
		int amount;

		public AddThread(Account account, int amount) {
			this.account = account;
			this.amount = amount;
		}

		public void run() {
			for (int i = 0; i < 200000; i++) {
				account.add(amount);
			}
		}
	}

	static class WithdrawThread implements Runnable {
		Account account;
		int amount;

		public WithdrawThread(Account account, int amount) {
			this.account = account;
			this.amount = amount;
		}

		public void run() {
			for (int i = 0; i < 100000; i++) {
				account.withdraw(amount);
			}
		}
	}
}

2 join用法

线程的join方法:
子线程执行join方法,后主线程必须等待子线程执行完之后才能继续往下执行。


public class ThreadJoin extends Thread {
	public void run() {
		Thread aThread = new Thread(new Son());
		Thread bThread = new Thread(new Son());
		aThread.start();
		bThread.start();
//		try {
//			aThread.join();
//			bThread.join();
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
		System.out.println("主线程结束");
	}
	
	public static void main(String[] args) {
		ThreadJoin threadJoin = new ThreadJoin();
		threadJoin.start();
	}
}

class Son extends Thread {
	public void run() {
		System.out.println("子线程执行");
	}
}

子线程不使用join结果:

主线程结束
子线程执行
子线程执行

子线程使用join结果:

子线程执行
子线程执行
主线程结束

3 多线程保证数据安全性(生产者与消费者模式经典案例)


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

public class Plate {

	List<Object> eggs = new ArrayList<Object>();

	public synchronized Object getEgg() {
		while (eggs.size() == 0) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}

		Object egg = eggs.get(0);
		eggs.clear();// 清空盘子
		notify();
		System.out.println("拿到鸡蛋");
		return egg;
	}

	public synchronized void putEgg(Object egg) {
		while (eggs.size() > 0) {
			try {
				System.out.println("执行了putEgg中的wait方法");
				wait();
			} catch (InterruptedException e) {
			}
		}
		eggs.add(egg);// 往盘子里放鸡蛋
		System.out.println("执行了putEgg中的notify方法");
		notify();
		System.out.println("putEgg中的notfy方法执行结束");
		System.out.println("放入鸡蛋");
	}

	static class AddThread extends Thread {
		private Plate plate;
		private Object egg = new Object();

		public AddThread(Plate plate) {
			this.plate = plate;
		}

		public void run() {
			for (int i = 0; i < 5; i++) {
				plate.putEgg(egg);
			}
		}
	}

	static class GetThread extends Thread {
		private Plate plate;

		public GetThread(Plate plate) {
			this.plate = plate;
		}

		public void run() {
			for (int i = 0; i < 5; i++) {
				plate.getEgg();
			}
		}
	}

	public static void main(String args[]) {
		try {
			Plate plate = new Plate();
			Thread add = new Thread(new AddThread(plate));
			Thread get = new Thread(new GetThread(plate));
			/**
			 * 线程执行过程:
			 * 	有两个线程add线程和get线程同时共享Plate类对象。
			 * 	add.start(); // add线程首先进入就绪队列,等待执行
				get.start(); // get线程进入就绪队列,等待执行
			 * 	add线程执行putEgg方法:
			 * 	add线程启动,执行putEgg方法时获取Plate类对象锁,此时get线程不能获取Plate类对象锁,因此执行不了getEgg方法。
			 * 	当add线程执行putEgg方法中的notify()方法后,add线程的锁并没有释放掉,而是唤醒锁(Plate类对象)的阻塞队列的线程(程序开始并没有阻塞队列的线程)到就绪队列,putEgg继续执行,打印出放入鸡蛋
			 * 	当add线程执行完putEgg方法方法后,释放掉Plate类锁对象,此时盘子中有一个鸡蛋。这时get线程有可能获取锁对象开始执行(要不要执行取决于cpu会不会分配资源给get线程)。
			 *	add线程执行getEgg方法:
			 *	当add线程释放掉锁后,不一定执行get线程
			 *		1.如果仍然执行get线程,get线程获取锁执行getEgg方法,此时盘子中有鸡蛋,会执行clear方法,盘子中鸡蛋被清空,继续执行notify方法,将会唤醒add线程进入就绪队列,get线程继续执行,打印出拿到鸡蛋。释放掉锁,get线程执行结束。
			 *		2.如果执行add线程,add线程获取锁执行putEgg方法,此时盘子中有鸡蛋,会执行wait方法,这时add线程释放掉锁,进入阻塞队列等待get线程唤醒add线程,此时盘子中有鸡蛋,add线程执行结束。
			 *			这时get线程获取锁执行getEgg方法,清空盘子中的鸡蛋后,继续执行notify方法,add线程进入就绪队列,等待执行,get线程继续执行getEgg方法,打印出拿到鸡蛋,并释放掉锁,此时盘子清空了。
			 */
			add.start();
			get.start();
			add.join();
			get.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("测试结束");
	}
}

执行结果是有序的

执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
测试结束
上一篇:币安智能合约发币


下一篇:C++ 之Boost 实用工具类及简单使用