线程同步基础(一)

本文介绍了Java并发编程中的关键技巧,包括使用synchronized关键字实现同步方法、使用非依赖属性实现同步、在同步代码中使用条件、使用锁实现同步以及使用读写锁实现同步数据访问等五个方面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.使用synchronized实现同步方法

如果一个对象已用synchronized关键字声明,那么只有一个执行线程被允许访问它。如果其他某个线程试图访问这个对象的其它方法,它将被挂起,直到第一个线程执行完正在运行的方法。
静态方法则有不同的行为。用synchronized关键字声明的静态方法,同时只能够被一个执行线程访问,但是其它的线程可以访问这个对象的两个不同的synchronized方法,即其中一个是静态方法,另一个是非静态方法。如果两个方法都改变了相同的数据,将会出现数据不一致的错误。
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) {
		double tmp = balance;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		tmp += amount;
		balance = tmp;
	}
	public synchronized void  subtractAmount(double amount) {
		double tmp = balance;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		tmp -= amount;
		balance = tmp;
	}
}
public class Bank implements Runnable{

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

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			account.subtractAmount(1000);
			System.out.println((i+1)+" sub : "+account.getBalance());
		}
	}
}
class Company implements Runnable{

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

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			account.addAmount(1000);
			System.out.println((i+1)+" add : "+account.getBalance());
		}
	}
	
}
public class Main {
	public static void main(String[] args) {
		Account account = new Account();
		account.setBalance(1000);
		Company company = new Company(account);
		Thread thread = new Thread(company);
		
		Bank bank = new Bank(account);
		Thread thread2 = new Thread(bank);
		
		System.out.println("Account : Initial Balance: "+account.getBalance());
		
		thread.start();
		thread2.start();
		
		try {
			thread.join();
			thread2.join();
			System.out.println("Account : Final Balance: "+account.getBalance());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
synchronized关键字会降低应用程序的性能,因此只能在并发情景中需要修改共享数据的方法上使用它。当线程访问一个对象的同步方法时,他还可以调用这个对象的其他的同步方法,也包含正在执行的方法,而不必再次去获取这个方法的访问权。
我们可以通过synchronized关键字来保护代码块(而不是整个方法)的访问。应该这样利用synchronized关键字:方法的其余部分保持在synchronized代码块之外,以获得更好的性能。临界区(即同一时间只能被一个线程访问的代码块)的访问应该尽可能的短。当这样使用synchronized关键字时,必须把对象引用作为传入参数。同一时间只有一个线程被允许访问这个synchronized代码。通常来说,我们使用this关键字来引用正在执行的方法所属的对象。
synchronized(this) {
		
	}

2.使用非依赖属性实现同步

当使用synchronized关键字来保护代码块时,必须把对象引用作为传入参数。通常情况下,使用this关键字来引用执行方法所属的对象,也可以使用其他的对象对其进行引用。
public class Cinema {
	private long vacanciesCinema1;
	private long vacanciesCinema2;
	private final Object controlCinema1,controlCinema2;
	public Cinema() {
		this.vacanciesCinema1 = 20;
		this.vacanciesCinema2 = 20;
		this.controlCinema1 = new Object();
		this.controlCinema2 = new Object();
	}

	public boolean sellTickets1(int number) {
		synchronized (controlCinema1) {
			if (number<vacanciesCinema1) {
				vacanciesCinema1 -= number;
				return true;
			}else {
				return false;
			}
		}
	}

	public boolean sellTicket2(int number) {
		synchronized (controlCinema2) {
			if (number<vacanciesCinema2) {
				vacanciesCinema2 -= number;
				return true;
			}else {
				return false;
			}
		}
	}

	public boolean returnTickets1(int number) {
		synchronized (controlCinema1) {
			vacanciesCinema1 += number;
			return true;
		}
	}
	public boolean returnTicket2(int number) {
		synchronized (controlCinema2) {
			vacanciesCinema2 += number;
			return true;
		}
	}

	public long  getTickets1() {
		return vacanciesCinema1;
	}

	public long getTickets2() {
		return vacanciesCinema2;
	}
}
public class TicketOffice1  implements Runnable{

	private Cinema cinema;
	
	public TicketOffice1(Cinema cinema) {
		this.cinema = cinema;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		cinema.sellTickets1(3);
		cinema.sellTickets1(2);
		cinema.sellTicket2(2);
		cinema.returnTickets1(3);
		cinema.sellTickets1(5);
		cinema.sellTicket2(2);
		cinema.sellTicket2(2);
		cinema.sellTicket2(2);
	}

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

	@Override
	public void run() {
		// TODO Auto-generated method stub
		cinema.sellTicket2(2);
		cinema.sellTicket2(4);
		cinema.sellTickets1(2);
		cinema.sellTickets1(1);
		cinema.returnTicket2(2);
		cinema.sellTickets1(3);
		cinema.sellTicket2(2);
		cinema.sellTickets1(2);
	}

}
public class Main {
	public static void main(String[] args) {
		Cinema cinema = new Cinema();
		TicketOffice1 ticketOffice1 = new TicketOffice1(cinema);
		TicketOffice2 ticketOffice2 = new TicketOffice2(cinema);
		Thread thread1 = new Thread(ticketOffice1);
		Thread thread2 = new Thread(ticketOffice2);
		
		thread1.start();
		thread2.start();
		
		try {
			thread1.join();
			thread2.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("Room 1 Vacancies: "+cinema.getTickets1());
		System.out.println("Room 2 Vacancies: "+cinema.getTickets2());
	}
}
这个例子使用了一个对象来控制对vacanciesCinema1属性的访问,所以同一时间只有一个线程能够修改这个属性;使用了另一个对象来控制vacanciesCinema2属性的访问,所以同一时间只有一个线程能够修改这个属性。但是,这个例子允许同时运行两个线程:一个修改vacanciesCinema1属性,另一个修改vacanciesCinema2属性。

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

在并发编程中一个典型的问题是生产者-消费者问题。对于这些场景,Java在Object类中提供了wait()、notify()和notifyAll()方法。线程可以在同步代码块中调用wait()方法,JVM将这个线程置入休眠,并且释放控制这个同步代码块的对象,同时允许其他线程执行这个对象控制的其他同步代码块。为了唤醒这个线程,必须在这个对象控制的某个同步代码块中调用notify()或者notifyAll()方法。
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) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		storage.add(new Date());
		System.out.println("Set : "+storage.size());
		notifyAll();
	}
	public synchronized void get() {
		while(storage.size()==0) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("Get : "+storage.size()+" : "+((LinkedList<?>)storage).poll());
		notifyAll();
	}
}
public class Producer implements Runnable{
	private EventStorage storage;
	
	public Producer(EventStorage storage) {
		this.storage = storage;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			storage.set();
		}
	}

}
public class Consumer implements Runnable{

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

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			storage.get();
		}
	}

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

4.使用锁实现同步

Java提供了同步代码块的另一种机制,它是一种比synchronized关键字更强大也更灵活的机制。这种机制基于Lock接口及其实现类(例如ReentrantLock),提供了更多的好处。
  • 支持更灵活的同步代码块结构。使用synchronized关键字的获取和释放控制只能在同一个synchronized块结构中。Lock接口允许实现更复杂的临界区结构
  • Lock接口提供了更多的功能。其中一个新功能是tryLock()的实现。这个方法试图获取锁,如果锁已被其他线程获取,它将返回false并继续往下执行代码
  • Lock接口允许分离读和写操作,允许多个读线程和只有一个写线程
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 object) {
		queueLock.lock();
		try {
			long duration = (long)(Math.random()*10000);
			System.out.println(Thread.currentThread().getName()+":PrintQueue: Printing a Job during "+(duration/1000)+" seconds");
			Thread.sleep(duration);
		} catch (InterruptedException e) {
			// TODO: handle exception
		}finally {
			queueLock.unlock();
		}
	}
}
public class Job implements Runnable{
	private PrintQueue printQueue;
	

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


	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+": Going to print a document");
		printQueue.printJob(new Object());
		System.out.println(Thread.currentThread().getName()+": The document has been printed");
	}

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

5.使用读写锁实现同步数据访问

锁机制最大的改进之一就是ReadWriteLock接口和他的唯一实现类ReentrantReadWriteLock。这个类有两个锁,一个是读操作锁,另一个是写操作锁。使用读操作锁时可以允许多个线程同时访问,但是使用写操作锁时只允许一个线程运行。在一个线程执行写操作时,其他的线程不能够执行读操作。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class PricesInfo {
	private double price1;
	private double price2;
	private ReadWriteLock lock;
	public PricesInfo() {
		price1 = 1.0;
		price2 = 2.0;
		lock = new ReentrantReadWriteLock();
	}
	public double getPrice1() {
		lock.readLock().lock();
		double value = price1;
		lock.readLock().unlock();
		return value;
	}
	public double getPrice2() {
		lock.readLock().lock();
		double value = price2;
		lock.readLock().unlock();
		return value;
	}
	public void setPrices(double price1,double price2) {
		lock.writeLock().lock();
		this.price1 = price1;
		this.price2 = price2;
		lock.writeLock().unlock();
	}
	
}
public class Reader implements Runnable{

	private PricesInfo pricesInfo;
	
	public Reader(PricesInfo pricesInfo) {
		this.pricesInfo = pricesInfo;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+": Price1: "+pricesInfo.getPrice1() );
			System.out.println(Thread.currentThread().getName()+": Price2: "+pricesInfo.getPrice2());
		}
	}

}
public class Writer implements Runnable{
	private PricesInfo pricesInfo;
	
	public Writer(PricesInfo pricesInfo) {
		this.pricesInfo = pricesInfo;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 3; i++) {
			System.out.println("Writer: Attempt to modify the prices");
			pricesInfo.setPrices(Math.random()*10,Math.random()*8);
			System.out.println("Writer: Prices have been modified");
			try {
				Thread.sleep(2);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}
public class Main {
	public static void main(String[] args) {
		PricesInfo pricesInfo = new PricesInfo();
		Reader[] readers = new Reader[5];
		Thread[] threads = new Thread[5];
		for (int i = 0; i < 5; i++) {
			readers[i] = new Reader(pricesInfo);
			threads[i] = new Thread(readers[i]);
		}
		Writer writer = new Writer(pricesInfo);
		Thread thread = new Thread(writer);
		
		for (int i = 0; i < 5; i++) {
			threads[i].start();
		}
		thread.start();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值