欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第二章 Basic Thread Synchronization (基础线程同步) 【上】

程序员文章站 2022-06-01 11:09:59
...

涉及的内容

  • 同步一个方法
  • 同步类中分配一个独立属性
  • 在同步代码中使用条件
  • 使用Lock锁定代码块
  • 同步数据的读写锁
  • 修改Lock公平模式
  • 在Lock中使用多条件

简介

同步类似就是车过收费站,一杆一车,排队出收费站,不许并行。

对于同步的代码块称为critical section (临界断面)

为了实现这个同步将会介绍两种同步方法:

  • 关键字:synchronized
  • Lock的接口和它的实现类

1、同步方法(synchronized)

注意:对于一个类有个static的同步方法和非static方法,两个线程可以同时进入同一对象不同方法,如果同时修改同一个数据,将会产生数据不一致。

例子:模拟取存款过程

public class Account {
	private double balance;

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}
	
	public synchronized void addAmount(double amount){
		System.out.printf("增加了金额++++++:%f\n", amount);
		double tmp = balance;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		tmp += amount;
		balance=tmp;
	}
	
	public synchronized void substractAmount(double amount){
		System.out.printf("减少了金额------:%f\n", amount);
		double tmp = balance;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e){
			e.printStackTrace();
		}
		tmp -= amount;
		balance = tmp;
	}
	
}

public class Bank implements Runnable{

	private Account account;
	
	

	public Bank(Account account) {
		super();
		this.account = account;
	}



	@Override
	public void run() {
		for (int i=0; i< 10; i++){
			account.substractAmount(1000);
		}
	}

}

public class Company implements Runnable{

	private Account account;
	
	public Company(Account account) {
		super();
		this.account = account;
	}

	@Override
	public void run() {
		for(int i=0; i<10; i++){
			account.addAmount(1000);
		}
	}

}

public class Main {
	public static void main(String[] args){
		Account account = new Account();
		account.setBalance(1000);
		Company company = new Company(account);
		Thread companyThread = new Thread(company);
		Bank bank = new Bank(account);
		Thread bankThread = new Thread(bank);
		
		System.out.printf("账户:初始化金额: %f\n", account.getBalance());
		companyThread.start();
		bankThread.start();
		try{
			companyThread.join();
			bankThread.join();
			System.out.printf("账户:最终金额:%f\n", account.getBalance());
		} catch (InterruptedException e){
			e.printStackTrace();
		}
	}
}
日志:

第二章 Basic Thread Synchronization (基础线程同步) 【上】

总结:

  • 1、synchronized进行同步操作保证最后的结果是准确的。
  • 2、你可以删除synchronized测试结果。

2、在同步类分配独立属性

模拟电影院两个放映室和两个购票处

public class Cinema {
	private long vacanciesCinema1;
	private long vacanciesCinema2;
	
	private final Object controlCinema1, controlCinema2;
	
	/**
	 * 初始20张票
	 */
	public Cinema(){
		controlCinema1 = new Object();
		controlCinema2 = new Object();
		vacanciesCinema1 = 20;
		vacanciesCinema2 = 20;
	}
	
	/**
	 * 售票
	 * @param number
	 * @return
	 */
	public boolean sellTickets1(int number){
		synchronized (controlCinema1){
			if(number < vacanciesCinema1){
				vacanciesCinema1 -= number;
				return true;
			} else {
				return false;
			}
		}
	}
	
	/**售票
	 * @param number
	 * @return
	 */
	public boolean sellTickets2 (int number){
		synchronized (controlCinema2){
			if(number < vacanciesCinema2){
				vacanciesCinema2 -= number;
				return true;
			} else {
				return false;
			}
		}
	}
	
	/**
	 * 退票
	 * @param number
	 * @return
	 */
	public boolean returnTicket1 (int number){
		synchronized (controlCinema1){
			vacanciesCinema1 += number;
			return true;
		}
	}
	/**
	 * 退票
	 * @param number
	 * @return
	 */
	public boolean returnTicket2 (int number){
		synchronized (controlCinema2){
			vacanciesCinema2 += number;
			return true;
		}
	}
	
	/**
	 * 返回余票
	 * @return
	 */
	public long getVacanciesCinema1(){
		return vacanciesCinema1;
	}
	
	/**
	 * 返回余票
	 * @return
	 */
	public long getVacanciesCinema2(){
		return vacanciesCinema2;
	}
}

public class TicketOffice1 implements Runnable{
	
	private Cinema cinema;
	
	
	public TicketOffice1(Cinema cinema) {
		super();
		this.cinema = cinema;
	}


	@Override
	public void run() {
		cinema.sellTickets1(3);
		cinema.sellTickets1(2);
		cinema.sellTickets2(2);
		cinema.returnTicket1(3);
		/*cinema.sellTickets1(5);
		cinema.sellTickets2(2);
		cinema.sellTickets2(2);
		cinema.sellTickets2(2);*/
	}

}


public class TicketOffice2 implements Runnable{
	
	private Cinema cinema;
	
	
	public TicketOffice2(Cinema cinema) {
		super();
		this.cinema = cinema;
	}


	@Override
	public void run() {
		cinema.sellTickets2(3);
		cinema.sellTickets2(4);
		/*cinema.sellTickets1(2);
		cinema.sellTickets1(1);
		cinema.returnTicket2(2);
		cinema.sellTickets1(3);
		cinema.sellTickets2(2);
		cinema.sellTickets1(2);*/
	}

	
}

public class Main {
	public static void main(String[] args){
		Cinema cinema = new Cinema();
		TicketOffice1 ticketOffice1 = new TicketOffice1(cinema);
		Thread thread1 = new Thread(ticketOffice1, "TicketOffice1");
		TicketOffice2 ticketOffice2 = new TicketOffice2(cinema);
		Thread thread2 = new Thread(ticketOffice2, "TicketOffice2");
		
		thread1.start();
		thread2.start();
		
		try{
			thread1.join();
			thread2.join();
			
		}catch (InterruptedException e){
			e.printStackTrace();
		}
		System.out.printf("放映室1空闲的数量:%d\n", cinema.getVacanciesCinema1());
		System.out.printf("放映室2空闲的数量:%d\n", cinema.getVacanciesCinema2());
	}
}

总结:

1、创建的controlCinema1和controlCinema2并没有实际的作用,它只是作为关联锁定花括号的代码,

(说白了就是如果锁定controlCinema2代码中所有对象,每次只一个线程访问这些对象)。

3、在同步代码中使用条件

生产者-消费者模型(wait() notify() notifyAll())

package com.jack;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

public class EventStorage {
	private int maxSize;
	private List<Date> storage;

	public EventStorage(){
		maxSize=10;
		storage=new LinkedList<>();
	}

	public synchronized void set(){
		while (storage.size()==maxSize){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		storage.add(new Date());
		System.out.printf("++当然前仓库: %d\n",storage.size());
		notifyAll();
	}

	public synchronized void get(){
		while (storage.size()==0){
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.printf("--当然前仓库: %d: %s\n",storage.
				size(),((LinkedList<?>)storage).poll());
		notifyAll();
	}
}

package com.jack;

public class Producer implements Runnable{

	private EventStorage storage;
	
	
	public Producer(EventStorage storage) {
		super();
		this.storage = storage;
	}


	@Override
	public void run() {
		for (int i=0; i <100; i++){
			storage.set();
		}
	}

}

package com.jack;

public class Consumer implements Runnable{
	private EventStorage storage;
	
	
	public Consumer(EventStorage storage) {
		super();
		this.storage = storage;
	}


	@Override
	public void run() {
		for (int i=0; i<100; i++){
			storage.get();
		}
	}

}

package com.jack;

public class Main {
	public static void main(String[] args){
		EventStorage storage = new EventStorage();
		Producer producer = new Producer(storage);
		Thread thread1 = new Thread(producer);
		
		Consumer consumer = new Consumer(storage);
		Thread thread2 = new Thread(consumer);
		
		thread2.start();
		thread1.start();
		
		
	}
}

总结:

  • 1、创建一个仓库类,有添加和删除,创建生产线程和消费线程。当等于10时候等待,唤醒对方。
  • 2、wait等待,notifyAll()唤醒所有线程

4、采用Lock锁定同步块

Lock优点(ReentrantLock)

  • 1、允许同步块更加灵活。
  • 2、Lock接口比synchronized提供额外的功能,增加tryLock()返回同步块的状态
  • 3、Lock允许读写分离
  • 4、Lock性能比synchronized关键字更优。

例子:使用Lock同步代码块模拟打印队列

package com.jack;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class PrintQueue {
	private final Lock queueLock = new ReentrantLock();
	
	public void printJob(Object document){
		queueLock.lock();
		try {
			Long duration = (long) (Math.random()*10000);
			System.out.printf(Thread.currentThread().getName()
					+ ":打印队列:打印一个工作持续时间 %s ",(duration/1000)
					+ " seconds");
			Thread.sleep(duration);
		} catch (InterruptedException e){
			e.printStackTrace();
		}finally{
			queueLock.unlock();
		}
	}
}

package com.jack;

public class Job implements Runnable{

	private PrintQueue printQueue;
	
	
	public Job(PrintQueue printQueue) {
		super();
		this.printQueue = printQueue;
	}


	@Override
	public void run() {
		System.out.printf("%s:去打印一个文档\n", Thread.currentThread().getName());
		printQueue.printJob(new Object());
		System.out.printf("%s: 这个文档已经打印了\n", Thread.currentThread().getName());
	}

}

package com.jack;

public class Main {
	public static void main(String[] args){
		PrintQueue printQueue = new PrintQueue();
		Thread thread[] = new Thread[10];
		for (int i=0; i<10; i++){
			thread[i]= new Thread(new Job(printQueue), "线程  " + i);
		}
		
		for(int i=0; i<10; i++){
			thread[i].start();
		}
	}
}

总结:

  • 1、private final Lock queueLock = new ReentrantLock(); 是关键。首先为该类配一个锁
  • 2、lock()方法锁定的意思,第一个进来之后锁住,直到执行完,(类似卵子受精就是这个例子)
  • 3、ReentrantLock允许递归调用
  • 4、注意避免死锁