首页 > 代码库 > (二)线程同步_1---同步一个方法

(二)线程同步_1---同步一个方法

同步一个方法(Synchronizing a method)

在并发编程中,最常见的情景莫过于多线程共享同一资源的情景了,例如多个线程同时读写相同的数据或者同时访问相同的文件或数据库连接,如果不采取一个控制机制,那么就会造成数据的不一致甚至是错误发生;

为了解决这些问题,引入了临界区域(critical section)的概念,一个临界区域是指一个用来控制资源访问的代码块,以保证被访问的资源不会同时被多个线程访问,从而保证了数据的一致性;

java中提供了Synchronization机制,当一个线程访问一个临界区域时,会首先使用这些同步机制找出是否有多个线程在同时执行这个临界区域代码块,如果不是,那么这个线程正常访问,如果是,那么这个线程将会挂起,直到正在执行这个临界区域代码块的线程结束后才恢复,当有多个线程被挂起,在恢复后JVM采用一定算法选择哪个线程将会访问这个临界区域代码块;

Java中提供的最基本的同步机制有两个:

  • 关键字synchronized
  • Lock接口以及其实现

在接下来的实例中,将会展示最基本的方法同步,即利用synchronized关键字去控制一个方法的并发访问;如果一个对象有多个方法的声明都有synchronized关键字,那么在同一时刻只有一个线程能够访问这些方法中的其中一个;如果有另外一个线程在同一时刻试图访问这个对象的任何一个带有synchronized声明的方法,都将会被挂起,直到第一个线程执行完这个方法;换句话说,方法声明带有synchronized关键字的方法都是一个临界区域(critical section),对于同一个对象,同一时间只允许执行一个临界区域(critical section)。

举个通俗的例子:比如一个对象里面有两个都声明了synchronized关键字的方法,一个方法是读取文件A,并追加一行信息;另外一个方法是读取文件A,并追加两行信息;那么这两个方法都属于临界区域,不能够被两个线程同时访问的;如果同时访问了,那么两个方法读取相同文件A,并同时写入了不同的信息,返回的结果就错误了;从这个角度可以理解为什么同一时间只能有一个线程可以访问一个对象中其中一个带有synchronized关键字的方法;

然而对于静态方法,则有不同的行为;一个对象中有多个声明了synchronized的静态方法和多个非静态方法,在同一时刻,一个线程只能访问其中一个静态同步方法,然而另外一个线程可以访问其中一个非静态方法;对于这种情况需要非常小心,因为在同一时刻,两个线程可以访问一个对象的两种不同类型的同步方法:一个静态的,一个非静态的;

如果这两个方法控制的是相同的数据,这样有可能导致数据不一致;

下面实现一个例子:两个线程同时访问一个对象;一个线程向银行账户中转钱,一个线程从相同银行账户中取钱,分别观察利用同步机制和不同同步机制的区别

动手实现

1.创建一个账户对象

public class Account {
    private double balance;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public synchronized void addAmount(double amount){
        double tmp=balance;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp+=amount;
        balance=tmp;
    }

    public synchronized void subtractAmount(double amount) {
        double tmp=balance;
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp-=amount;
        balance=tmp;
    }
}
2.创建一个银行线程,用来取出钱

public 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);
        }
    }
}
3.创建一个公司线程用来转入钱

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.addAmount(1000);
        }
    }
}
4.创建一个监控线程,每隔2毫秒监控上面两个线程的状态,可以用来验证synchronized同步

public class StatusMonitor implements Runnable{
    private Thread[] threads;

    public StatusMonitor(Thread[] threads) {
        this.threads=threads;
    }

    private String status(){
        StringBuffer sb=new StringBuffer();
        sb.append("status list:\t\n");
        for (Thread thread : threads) {
            sb.append("\t").append(thread.getName()).append(" status:").append(thread.getState()).append("\n");
        }
        return sb.toString();
    }
    @Override
    public void run() {
        boolean flag=true;
        while(flag) {
            System.out.println(status());
            try {
                TimeUnit.MILLISECONDS.sleep(5);
            } catch (InterruptedException e) {
                flag=false;
            }
        }
    }
}
5.Main方法

public class Main {
    public static void main(String[] args) {
        Account account = new Account();
        account.setBalance(1000);

        Company company = new Company(account);
        final Thread companyThread = new Thread(company);

        final Bank bank = new Bank(account);
        final Thread bankThread = new Thread(bank);

        System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
        companyThread.start();
        bankThread.start();

        // Monitor bankThread and companyThread's states
        StatusMonitor statusMonitor = new StatusMonitor(new Thread[]{companyThread, bankThread});
        Thread monitor = new Thread(statusMonitor);
        monitor.start();

        //Waiting for threads finished
        try {
            companyThread.join();
            bankThread.join();
            //Ending monitor
            monitor.interrupt();
            System.out.printf("Account : Final Balance: %f\n", account.getBalance());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

可以观察监控线程的输出,这两个线程基本上都是一个处于阻塞,一个处于运行状态;

要点

理解synchronized的用法;synchronized关键字这种同步机制对性能上有一定的损耗;

synchronized关键字指定在方法签名上,同步的是整个方法;也可以指定更小的同步块,如在一个方法内部同步更小的部分:

public void method(){

// java code

synchornized(this){

// java code

}

}

这里有一个问题,一个方法里面sychronized同步块之前的代码会不会可以同时被多个线程执行呢?答案是可以被多个线程执行;下面的例子可以证实:

public class BlockTest extends Thread {
    private Block block;

    public BlockTest(Block block) {
        this.block=block;
    }

    @Override
    public void run() {
        block.read();
    }

    public static void main(String[] args) {
        Block block=new Block();
        BlockTest thread1=new BlockTest(block);
        BlockTest thread2=new BlockTest(block);
        thread1.start();
        thread2.start();

    }
    private static class Block{
        public void read(){
            System.out.printf("Thread %s start to read.\n", Thread.currentThread().getName());
            synchronized (this){
                System.out.printf("Current thread:%s\n",Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.printf("Thread %s read over.\n", Thread.currentThread().getName());
        }
    }
}
一次运行结果:

Thread Thread-0 start to read.
Thread Thread-1 start to read.
Current thread:Thread-0
Thread Thread-0 read over.
Current thread:Thread-1
Thread Thread-1 read over.
从运行结果的输出过程可以看到多个线程可以同时访问一个方法内部synchronized之前的部分,但是运行到synchronized部分,将只有一个线程继续运行,其它的线程将会被阻塞;







(二)线程同步_1---同步一个方法