基本同步
同步概述
多个线程共享一个资源的场景非常常见,比如多个线程读或者写相同的数据等。为了防止共享资源可能出现的错误或数据不一致,需要有一些机制来防止错误的发生。
临界区(Critical Section)是指访问共享资源的代码块,这个代码块在同一时间只允许一个线程执行。Java提供了同步机制来实现临界区。当一个线程试图访问一个临界区时,它使用同步机制查看是不是已经有其他线程进入了临界区,如果美欧它就可以进入临界区;如果已经有线程进入临界区,它就被同步机制挂起,直到进入的线程离开这个临界区。如果在等待进入临界区的线程不止一个,JVM会选择其中的一个,其余将继续等待。
Java中的同步机制有两种:
- synchronized关键字机制
- Lock接口及其实现(JDK5.0引入)
本节讨论synchronized机制,这是最基本最常用的同步机制。后面的章节讨论Lock机制。
synchronized
JAVA中最基本的同步方式就是使用synchronized关键字。每一个使用synchronized关键字声明的方法都是临界区,一个对象的临界区在同一时间只有一个允许被访问。synchronized关键字也可以声明在静态方法上,用synchronized声明的静态方法同时只能够被一个执行线程访问,但是其他线程可以访问这个对象的非静态方法。这点要特别注意,因为两个线程可以同时访问一个对象的两个方法,一个是静态的,另一个是非静态的。如果两个方法都改变了相同的数据,将会出现数据不一致的错误。
synchronized是可重入的。
下面以银行转账的经典示例来说明synchronized关键字的用法,这个示例使用两个线程访问同一个账户对象,一个线程转钱到此账户,一个线程从此账户中取钱,如果没有同步机制那么最后账户的余额是不对的,同步机制保证账户的余额最终是正确的,下面的示例将在转入和转出时使用synchronized关键子进行同步。其实更本质的是在修改账户余额时需要同步。
public class AccoutSynchronized {
public static void main(String[] args){
final Account account = new Account(1000);
System.out.println("main:初始账户。账户余额:" + account.getBalance());
System.out.println("main:创建工作线程。");
Thread addThread = new Thread(new AddWorker(account));
Thread subThread = new Thread(new SubWorker(account));
System.out.println("main:启动工作线程。");
addThread.start();
subThread.start();
try {
System.out.println("main:等待工作线程执行完成。");
addThread.join();
subThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main:工作线程执行完成。账户余额:" + account.getBalance());
System.out.println("main:退出。");
}
}
/** 转入线程**/
class AddWorker implements Runnable{
private Account account;
public AddWorker(Account account){
this.account = account;
}
@Override
public void run(){
System.out.println("转入工作线程启动。");
for(int i=0; i<100; i++){
account.addAmount(1000);
}
System.out.println("转入工作线程完成。");
}
}
/** 转出线程**/
class SubWorker implements Runnable{
private Account account;
public SubWorker(Account account){
this.account = account;
}
@Override
public void run(){
System.out.println("转出工作线程启动。");
for(int i=0; i<100; i++){
account.subAmount(1000);
}
System.out.println("转出工作线程完成。");
}
}
/** 账户 **/
class Account{
private double balance;
public Account(double balance){
this.balance = balance;
}
/** 转入账户 */
public synchronized void addAmount(double amount){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance += amount;
}
/** 转出账户 */
public synchronized void subAmount(double amount){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
}
public double getBalance(){
return this.balance;
}
}
程序执行日志如下:
main:初始账户。账户余额:1000.0
main:创建工作线程。
main:启动工作线程。
main:等待工作线程执行完成。
转入工作线程启动。
转出工作线程启动。
转出工作线程完成。
转入工作线程完成。
main:工作线程执行完成。账户余额:1000.0
main:退出。