在开始本篇前我们先来看个例子,大家可以自己先看看代码想想运行后的结果。
例子说明:
1、在本例中将模拟两个线程(公司、家庭)同时操作账户,公司负责往账户中存钱,家庭负责花钱,循环100次。
2、存钱和花钱时都先取出账户中的钱放入临时变量,然后再将当前线程休眠固定时间,最后将用临时变量的值加减金额后重新赋值给账户。
3、初始账户中有人民币1000元。
package com.sqczm.concurrent.sync;
import java.util.concurrent.TimeUnit;
/**
* <p>Description: 账户 </p>
* <p>Copyright: Copyright (c) 2014 </p>
* <p>Date: 2014年11月4日 </p>
* <p>Company: Ausware </p>
* @author ygczm
* @version V1.0
*/
public class Account {
private double money;
/**
* 账户资金
* @return 账户资金
*/
public double getMoney() {
return money;
}
/**
* 账户资金
* @param money 账户资金
*/
public void setMoney(double money) {
this.money = money;
}
/**
* 存钱
* @param num 数量
*/
public synchronized void addMoney(double num){
System.out.println("-->开始存钱......");
double temp = money;
try {
TimeUnit.MILLISECONDS.sleep(21l);
} catch (InterruptedException e) {
e.printStackTrace();
}
money = temp + num;
System.out.println("-->存钱结束......");
}
/**
* 取钱
* @param num 数量
*/
public synchronized void subMoney(double num){
System.out.println("-->开始取钱......");
double temp = money;
try {
TimeUnit.MILLISECONDS.sleep(19l);
} catch (InterruptedException e) {
e.printStackTrace();
}
money = temp - num;
System.out.println("-->取钱结束......");
}
}
package com.sqczm.concurrent.sync;
/**
* <p>Description: 家庭 </p>
* <p>Copyright: Copyright (c) 2014 </p>
* <p>Date: 2014年11月4日 </p>
* <p>Company: Ausware </p>
* @author ygczm
* @version V1.0
*/
public class Family implements Runnable {
private Account account;
public Family(Account account) {
this.account = account;
}
@Override
public void run() {
for(int i = 0; i < 100; i++){
account.subMoney(1000);
}
}
}
package com.sqczm.concurrent.sync;
/**
* <p>Description: 公司 </p>
* <p>Copyright: Copyright (c) 2014 </p>
* <p>Date: 2014年11月4日 </p>
* <p>Company: Ausware </p>
* @author ygczm
* @version V1.0
*/
public class Company implements Runnable {
private Account account;
public Company(Account account) {
this.account = account;
}
@Override
public void run() {
for(int i = 0; i < 100; i++){
account.addMoney(1000);
}
}
}
package com.sqczm.concurrent.sync;
/**
* <p>Description: synchronized的测试类 </p>
* <p>Copyright: Copyright (c) 2014 </p>
* <p>Date: 2014年11月4日 </p>
* <p>Company: Ausware </p>
* @author ygczm
* @version V1.0
*/
public class SynchronizedTest {
public static void main(String[] args) {
//模拟创建一个账户
Account account = new Account();
account.setMoney(1000);
//模拟创建两个线程(公司存钱、家庭花钱)
Company company = new Company(account);
Family family = new Family(account);
Thread companyThread = new Thread(company);
Thread familyThread = new Thread(family);
//在启动线程前先打印账户信息
System.out.println("-->账户中初始余额为 " + account.getMoney());
//启动两个线程
companyThread.start();
familyThread.start();
//等待两个线程运行完成
try {
companyThread.join();
familyThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程运行完毕后打印账户信息
System.out.println("-->账户中还剩 " + account.getMoney());
}
}
看完上面的例子后相信大家会有两种想法:一种就是账户中最后还剩1000元,另一种就是账户中剩余的钱不确定。咱们先不讨论上面的例子运行后的结果是多少,咱们先看看synchronized同步方法的说明:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。
看完了上面的解释后,我们应该都猜到了,在同一时间内不可能同时操作取钱和存钱的操作,也就是说在上面存钱和取钱的两个操作中,共享数据(账户金额)得到了保护,最终我们的账户金额不会受影响。
synchronized关键字会降低应用程序的性能,因此只能在并发情景中需要修改共享数据的方法上使用它。如果多个线程访问同一个synchronized方法,则只有一个线程可以访问,其他线程将等待。如果方法申明中没有使用synchronized关键字,所有的线程都能在同一时间执行这个方法(可将上面的Account类中的存钱或取钱的方法前的synchronized关键字去掉后测试),因而总运行时间将降低。如果已知一个方法不会被一个以上线程调用,则无需使用synchronized关键字申明之。
可以递归调用被synchronized申明的方法。当线程访问一个对象的同步方法时,它还可以调用这个方法对象的其他的同步方法,也包含正在执行的方法,而不必再去获取这个方法的访问权。