【java多线程学习】多线程的同步

本文深入探讨了Java多线程环境下同步的重要性及其经典实现方法。通过银行存款系统的实例,详细解析了使用synchronized关键字及ReentrantLock实现线程同步的具体步骤与原理,帮助读者掌握避免多线程数据冲突的关键技术。
一、多线程需要同步的原因

使用java线程时,如果是单个线程,就不存在同步问题。但在实际的多线程应用中,存在两个或两个以上的线程需要共享对同一数据的存取。如果两个线程读取相同的对象,并且每一个线程都调用了一个修改该对象状态的方法,那么就有可能出现问题: 在经典的银行存款问题中,对账户A,如果账户A有存款1000,有个线程甲访问A,并且从A中支出500元,然后要修改A中的余额为1000-500=500元。但是线程乙此时也访问账户A(在线程甲修改A的余额之前),向A中存入200,然后修改A中余额为1000+200=1200(在线程甲修改A的余额之前)。而这次的修改会被线程甲的修改覆盖,最终账户A中的余额为500,导致账户损失了200元。

出现上面的现象的本质问题是:账户A是一个临界资源,在访问A中初值,然后存入/支出一定金额,最后修改A的余额,这一系列操作不是原子操作 ---- 在执行的过程中很可能被其他的线程打断,特别是其他的线程对账户A(临界资源)也有修改状态的操作,这样就会出现问题。 联想的数据库中的丢失更新、读脏数据、不可重复读等。

同步线程的使用,主要在于一个进程中有多个线程的同步工作。线程的同步用于保护线程共享数据,控制和切换线程的执行,保证内存的一致性,其作用十分重要。


二、多线程同步的经典方法:关键字synchronized

一个类中的任何方法都可以定义为synchronized方法以防止多线程的数据崩溃。 当某个对象用synchronized关键字修饰时,说明该对象在任何时刻只能由一个线程访问。当一个线程进入synchronized方法后,能保证在任何其他线程访问这个方法之前完成自己的执行------因为在它自己执行的过程中,其他线程都不能访问这个用synchronized修饰的方法(对象),知道该线程执行完临界区代码,然后释放资源,其他线程得以有机会执行。

synchronized 关键字除了可以放在方法声明中表示整个方法为同步方法之外(放在类前的话,表示整个类中的所有方法都是同步方法,但是不建议这样使用),也可以放在一段代码之前限制它的执行。

	[modifier] synchronized returnType methodName([parameterList]){
		/*方法体*/
	}
	
	[modifier] returnType methodName([parameterList]){
		synchronized(this){
			/*some code*/		
		}
	}

银行存款中防止多个线程同时访问一个账户的:( synchronized 关键字放在方法名前)

//对临界区代码非同步控制
	//public void transfer(int from, int to, int amount){
	//多线程同步方法一:在各个线程访问的方法前用 synchronized 关键字控制访问
	public synchronized void transfer(int from, int to, int amount){
	     if(accounts[from] >= amount && (from != to)){
			int newAmountFrom  = accounts[from]-amount;
			int newAmountTo = accounts[to] + amount;
				
			wasteSomeTime();
				
			accounts[from] = newAmountFrom;
			accounts[to] = newAmountTo;	
				
			status.setText("Transfers completed: " + counter++);				
		}		
	}

银行存款中防止多个线程同时访问一个账户的:( synchronized 关键字放在一段代码之前)

	//对临界区代码非同步控制
	//public void transfer(int from, int to, int amount){
	//多线程同步方法一:在各个线程访问的方法前用 synchronized 关键字控制访问
	public  void transfer(int from, int to, int amount){
		synchronized(this){
			if(accounts[from] >= amount && (from != to)){
				int newAmountFrom  = accounts[from]-amount;
				int newAmountTo = accounts[to] + amount;
				
				wasteSomeTime();
				
				accounts[from] = newAmountFrom;
				accounts[to] = newAmountTo;	
				
				status.setText("Transfers completed: " + counter++);				
			}
		}
		
	}


三、关键字synchronized方法的理解

         理解synchronized方法之前,先来理解java中用锁对象条件对象实现多线程同步的过程。因为synchronized关键字的本质就是自动提供了一个锁以及相关的“条件”。可以这么理解,锁对象和条件对象是显示的调用锁来保证临界资源的同步,synchronized关键字实现了他们的功能。原因是每一个对象都有一个内部锁,并且该锁有一个内部条件,由锁来管理那些试图进入方法的线程,由条件来管理那些调用wait的线程(就是说需要某些条件才能执行的线程,即使他已经进入了临界资源区)

  • 锁对象
java 5.0开始引入了ReentrantLock类,显示的提供锁保证临界区资源实现同步互斥。框架是:

myLock.lock(); // a ReentrantLock object
try{
	some codes;
}
finally{
	myLock.unlock(); // make sure the lock is unlocked even if an exception is thrown
}

银行存款中防止多个线程同时访问一个账户的: (使用ReentrantLock类)

private ReentrantLock banklock = new ReentrantLock()

public  void transfer(int from, int to, int amount){
		banklock.lock();
		try{
			if(accounts[from] >= amount && (from != to)){
				int newAmountFrom  = accounts[from]-amount;
				int newAmountTo = accounts[to] + amount;
				
				wasteSomeTime();
				
				accounts[from] = newAmountFrom;
				accounts[to] = newAmountTo;	
				
				status.setText("Transfers completed: " + counter++);				
			}
		}finally{
			banklock.unlock();
		}
		
	}

上面可以理解为一个线程调用transfer,获得了临界区访问的权限(即锁,有且只有一个),其他线程也来调用transfer时,由于不能获得锁,将在调用lock方法时被阻塞。它必须在等待第一个线程完成transfer方法的执行之后才能被再度激活。当第一个线程释放锁是,第二个线程才能开始执行。


  • 条件对象
通常线程进入临界区,却发现在某一条件满足之后才能执行。要使用一个条件对象来管理那些已经获得锁但是却不能做有用工作的线程。就有了java库中提供的 条件对象

private ReentrantLock banklock = new ReentrantLock()
private Condition sufficientFunds;

sufficientFunds = banklock.newCondition();

public synchronized  void transfer(int from, int to, int amount){
			while(accounts[from] < amount || (from != to))
                              wait();
			int newAmountFrom  = accounts[from]-amount;
			int newAmountTo = accounts[to] + amount;
				
			wasteSomeTime();
				
			accounts[from] = newAmountFrom;
			accounts[to] = newAmountTo;	
				
			status.setText("Transfers completed: " + counter++);
				
			notifyAll();

		}finally{
			banklock.unlock();
		}
		
	}

  • 锁和条件对象的关键点

  • 锁用来保护代码的片段,任何时刻只有一个线程能执行被保护的代码;
  • 锁可以管理试图进入被保护代码段的线程;
  • 锁可以拥有一个活多个相关的条件对象;
  • 每个条件对象管理那些已经进入被保护的代码daunting但是还不能运行的线程;
以上代码都等价于

public  void transfer(int from, int to, int amount){
		banklock.lock();
		try{
			while(accounts[from] < amount || (from != to))
                              sufficientFunds.await();
			int newAmountFrom  = accounts[from]-amount;
			int newAmountTo = accounts[to] + amount;
				
			wasteSomeTime();
				
			accounts[from] = newAmountFrom;
			accounts[to] = newAmountTo;	
				
			status.setText("Transfers completed: " + counter++);
				
			sufficientFunds.signalAll();

		}finally{
			banklock.unlock();
		}
		
	}


四:Java 文档中关于 Condition 的解释和实例

       作为一个示例,假定有一个绑定的缓冲区,它支持 puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

java多线程详解   Java程序员面试中的多线程问题   关于Java的线程同步问题的总结 ava线程同步的几种方法?  Java多线程同步机制(synchronized)

附录:银行存款示例代码


package BankThreadTest;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Label;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;


public class Bank extends JFrame{
	private final static int initialBalance = 100000;
	protected final static int NUM_ACCOUNTS = 8;
	private final static int WASTE_TIME	= 1;
	private int accounts[] = new int[NUM_ACCOUNTS];
	private Customer customer[] = new Customer[NUM_ACCOUNTS]; 
	private int counter = 0;
	
	private Label status = new Label("Transfers Completed: 0");
	private TextArea display = new TextArea();
	private Button show = new Button("Show Account");
	private Button start = new Button("Restart");
	private Button stop = new Button("Stop");
	
	private Lock banklock = new ReentrantLock();
	
	public Bank(){
		setTitle("MultiThreadTest of Bank");
		setSize(300,300);
		show.addActionListener(new bankFrameAction());
		start.addActionListener(new bankFrameAction());
		stop.addActionListener(new bankFrameAction());
		
		Panel buttons = new Panel();
		buttons.setLayout(new FlowLayout());
		buttons.add(show);
		buttons.add(start);
		buttons.add(stop);
		
		setLayout(new BorderLayout());
		add(buttons, BorderLayout.SOUTH);
		add(status, BorderLayout.NORTH);
		add(display, BorderLayout.CENTER);
		
		for(int i=0; i<accounts.length; i++){
			accounts[i] = initialBalance;
		}
		
		start();
	}
	
	//普通的start方法,custom的构造方法中有线程启动的start()方法
	private void start(){
		stop();
		for(int i=0;i<accounts.length; i++){
			customer[i] = new Customer(i, this);
		}
	}
	
	private void stop(){
		for(int i=0; i<accounts.length; i++){
			if(customer[i] != null){
				customer[i].halt();
			}
		}
	}
	
	private void wasteSomeTime(){
		try
		{
			Thread.sleep(WASTE_TIME);
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
 	
	//对临界区代码非同步控制
	//public void transfer(int from, int to, int amount){
	//多线程同步方法一:在各个线程访问的方法前用 synchronized 关键字控制访问
	//public synchronized void transfer(int from, int to, int amount){
	//多线程同步方法二:使用锁对象,先设置一个锁,在需要同步的代码(临界区代码)执行前加锁,
	//然后在释放锁(释放锁的操作放在finally中,以便前面出现异常也能执行锁的释放操作)
	public  void transfer(int from, int to, int amount){
		banklock.lock();
		try{
			if(accounts[from] >= amount && (from != to)){
				int newAmountFrom  = accounts[from]-amount;
				int newAmountTo = accounts[to] + amount;
				
				wasteSomeTime();
				
				accounts[from] = newAmountFrom;
				accounts[to] = newAmountTo;	
				
				status.setText("Transfers completed: " + counter++);				
			}
		}finally{
			banklock.unlock();
		}
		
	}
	
	private void showAccounts(){
		int sum = 0;
		for(int i=0; i<accounts.length; i++){
			sum += accounts[i];
			display.append("\nAccount " + i + ": $" + accounts[i]);
		}
		
		display.append("\nTotal Account : $" + sum);
		display.append("\nTotal Transfer : $" + counter +"\n");			
	}
	
	
	
	public static void main(String[] argStrings){
		Bank bank = new Bank();
		bank.setVisible(true);
		bank.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}	
	
	
	class bankFrameAction implements ActionListener{
		public void actionPerformed(ActionEvent event){
			if(event.getSource() == show)
				showAccounts();
			else if(event.getSource() == start)
				start();
			else if(event.getSource() == stop)
				stop();
		}
	}
	
}


package BankThreadTest;

//线程类
public class Customer extends Thread{
	private Bank bank = null;
	private int id = -1;
	private boolean running = false;
	
	public Customer(int _id, Bank _bank){
		id = _id;
		bank = _bank;
		start();		
	}
	
	public void start(){
		running = true;
		super.start();
	}
	
	public void halt(){
		running = false;
	}
	
	//覆盖Thread类中的run方法
	public void run(){
		while(running){
			int into = (int)(Bank.NUM_ACCOUNTS * Math.random());  // Math.random返回0-1(double类型)
			int amount = (int)(1000 * Math.random());
			bank.transfer(id, into, amount);
			yield();
		}
	}
}




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值