使用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 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方法的执行之后才能被再度激活。当第一个线程释放锁是,第二个线程才能开始执行。
- 条件对象
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 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();
}
}
}
本文深入探讨了Java多线程环境下同步的重要性及其经典实现方法。通过银行存款系统的实例,详细解析了使用synchronized关键字及ReentrantLock实现线程同步的具体步骤与原理,帮助读者掌握避免多线程数据冲突的关键技术。

被折叠的 条评论
为什么被折叠?



