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

同步一个方法(Synchronizing a method)

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

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

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

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

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

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

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

然而对于静态方法,则有不一样的行为;一个对象中有多个声明了synchronized的静态方法和多个非静态方法,在同一时刻,一个线程只能访问其中一个静态同步方法,然而另一个线程能够访问其中一个非静态方法;对于这种状况须要很是当心,由于在同一时刻,两个线程能够访问一个对象的两种不一样类型的同步方法:一个静态的,一个非静态的;app

若是这两个方法控制的是相同的数据,这样有可能致使数据不一致;ide

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

动手实现

1.建立一个帐户对象this

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部分,将只有一个线程继续运行,其它的线程将会被阻塞;

相关文章
相关标签/搜索