码迷,mamicode.com
首页 > 编程语言 > 详细

线程同步基础之使用synchronized实现同步方法

时间:2015-05-04 01:18:26      阅读:175      评论:0      收藏:0      [点我收藏+]

标签:

Java的最基本的同步方式,即使用synchronized关键字来控制一个方法的并发访问。 每一个用synchronized关键字声明的方法都是临界区。在Java中,同一个对象的临界区,在同一时间只有一个允许被访问。

静态方法则有不同的行为。用synchronized关键字声明的静态方法,同时只能够被一个执行线程访问,但是其他线程可以访问这个对象的非静态的synchronized方法。必须非常谨慎这一点,因为两个线程可以同时访问一个对象的两个不同的synchronized方法,即其中一个是静态synchronized方法,另一个是非静态synchronized方法。如果两个方法都改变了相同的数据,将会出现数据不一致的错误。

先来看第一个示例,在java中,同一个对象的临界区,在同一时间只有一个允许被访问(都是非静态的synchronized方法):

package concurrency;

public class Main8 {
    public static void main(String[] args) {
        Account account = new Account();
        account.setBalance(1000);
        Company company = new Company(account);
        Thread companyThread = new Thread(company);
        Bank bank = new Bank(account);
        Thread bankThread = new Thread(bank);
        System.out.printf("Account : Initial Balance:  %f\n", account.getBalance());
        companyThread.start();
        bankThread.start();
        try {
            //join()方法等待这两个线程运行完成
            companyThread.join();
            bankThread.join();
            System.out.printf("Account : Final Balance:  %f\n", account.getBalance());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/*帐户*/
class Account{
    private double balance;
    /*将传入的数据加到余额balance中*/
    public synchronized void addAmount(double amount){
        double tmp = balance;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp += amount;
        balance = tmp;
    }
    /*将传入的数据从余额balance中扣除*/
    public synchronized void subtractAmount(double amount){
        double tmp = balance;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp -= amount;
        balance = tmp;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
}

/*银行*/
class Bank implements Runnable{
    private Account account;
    public Bank(Account account){
        this.account = account;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            account.subtractAmount(1000);
        }
    }
}

/*公司*/
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.addAmount(1000);
        }
    }
}

你已经开发了一个银行账户的模拟应用,它能够对余额进行充值和扣除。这个程序通过调用100次addAmount()方法对帐户进行充值,每次存入1000;然后通过调用100次subtractAmount()方法对帐户余额进行扣除,每次扣除1000;我们期望帐户的最终余额与起初余额相等,我们通过synchronized关键字实现了。

如果想查看共享数据的并发访问问题,只需要将addAmount()和subtractAmount()方法声明中的synchronized关键字删除即可。在没有synchronized关键字的情况下,打印出来的余额值并不一致。如果多次运行这个程序,你将获取不同的结果。因为JVM并不保证线程的执行顺序,所以每次运行的时候,线程将以不同的顺序读取并且修改帐户的余额,造成最终结果也是不一样的。

一个对象的方法采用synchronized关键字进行声明,只能被一个线程访问。如果线程A正在执行一个同步方法syncMethodA(),线程B要执行这个对象的其他同步方法syncMethodB(),线程B将被阻塞直到线程A访问完。但如果线程B访问的是同一个类的不同对象,那么两个线程都不会被阻塞。

示例2,演示同一个对象上的静态synchronized方法与非静态synchronized方法可以在同一时间点被多个线程访问的问题,验证一下。

package concurrency;

public class Main8 {
    public static void main(String[] args) {
        Account account = new Account();
        account.setBalance(1000);
        Company company = new Company(account);
        Thread companyThread = new Thread(company);
        Bank bank = new Bank(account);
        Thread bankThread = new Thread(bank);
        System.out.printf("Account : Initial Balance:  %f\n", account.getBalance());
        companyThread.start();
        bankThread.start();
        try {
            //join()方法等待这两个线程运行完成
            companyThread.join();
            bankThread.join();
            System.out.printf("Account : Final Balance:  %f\n", account.getBalance());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/*帐户*/
class Account{
    /*这里改为静态变量*/
    private static double balance = 0;
    /*将传入的数据加到余额balance中,注意是用static修饰过的*/
    public static synchronized void addAmount(double amount){
        double tmp = balance;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp += amount;
        balance = tmp;
    }
    /*将传入的数据从余额balance中扣除*/
    public synchronized void subtractAmount(double amount){
        double tmp = balance;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp -= amount;
        balance = tmp;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
}

/*银行*/
class Bank implements Runnable{
    private Account account;
    public Bank(Account account){
        this.account = account;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            account.subtractAmount(1000);
        }
    }
}

/*公司*/
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.addAmount(1000);
        }
    }
}

我只是把上个例子中的,balance加了static关键字修改,addAmount()方法也可以static关键字修饰。执行结果大家可以自己测试一下,每次执行都是不一样的结果!

synchronized关键字会降低应用程序的性能,因此只能在并发情景中需要修改共享数据的方法上使用它。如果多个线程访问同一个synchronized方法,则只有一个线程可以访问,其他线程将等待。如果方法声明没有使用synchronized关键字,所有的线程都能在同一时间执行这个方法,因而总运行时间降低。如果已知一个方法不会被一个以上线程调用,则无需使用synchronized关键字声明之。

可以递归调用被synchronized声明的方法。当线程访问一个对象的同步方法时,它还可以调用这个对象的其他的同步方法,也包含正在执行的方法,而不必再次去获取这个方法的访问权。

我们可以通过synchronized关键字来保护代码块(而不是整个方法)的访问。应该这样利用synchronized关键字:方法的其余部分保持在synchronized代码块之外,以获取更好的性能。临界区(即同一时间只能被一个线程访问的代码块)的访问应该尽可能的短。例如在获取一幢楼人数的操作中,我们只使用synchronized关键字来保护对人数更新的指令,并让其他操作不使用共享数据。当这样使用synchronized关键字时,必须把对象引用作为传入参数。同一时间只有一个线程被允许访问这个synchronized代码。通常来说,我们使用this关键字来引用正在执行的方法所属的对象:

synchronized(this){
    //Java code
}


线程同步基础之使用synchronized实现同步方法

标签:

原文地址:http://my.oschina.net/fhd/blog/410211

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!