【java多线程学习】多线程的同步
来源:互联网 发布:java多线程同步实现 编辑:程序博客网 时间:2024/04/29 15:13
使用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的线程(就是说需要某些条件才能执行的线程,即使他已经进入了临界资源区)
- 锁对象
myLock.lock(); // a ReentrantLock objecttry{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方法的执行之后才能被再度激活。当第一个线程释放锁是,第二个线程才能开始执行。
- 条件对象
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 的解释和实例
作为一个示例,假定有一个绑定的缓冲区,它支持 put
和 take
方法。如果试图在空的缓冲区上执行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 blocke.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();}}}
- 【java多线程学习】多线程的同步
- 多线程同步的学习
- java的多线程同步
- Java 多线程的同步
- Java 多线程的同步
- Java多线程的同步
- java多线程学习2-同步
- Java 多线程(五) 多线程的同步
- java多线程(二) 多线程的同步
- Java多线程------多线程的创建与同步
- Java 多线程(五) 多线程的同步
- java的多线程同步初探
- Java多线程的同步机制
- java多线程的同步问题
- java的多线程同步初探
- Java多线程的同步问题
- java多线程之间的同步
- Java多线程的同步总结
- Tomcat的JNDI数据源
- #OIM查错#OIM add resource, result into updae failed error.
- 阅人无数,不如阅人有术!
- Ajax实现二级联动菜单
- MySql表分区
- 【java多线程学习】多线程的同步
- 与硬件通信
- Silverlight多线程技术Timer的应用,模拟心电图、模拟CPU、内存状态图
- Tomcat内存设置
- Asp.Net里面调用Microsoft Excel Application DCOM问题终极解决方法
- 黑马课上的 Http操作类
- 铁路订票系统的简单设计
- C# ListView用法详解
- cannot be cast to android.widget.HeaderViewListAdapter