线程同步synchronized

来源:互联网 发布:js图片循环滚动代码 编辑:程序博客网 时间:2024/05/23 18:31

转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/70670024
本文出自:【顾林海的博客】

前言

在编写多线程应用时,读写相同的数据,最有可能发生数据的错误或不一致,为了防止这些错误的发生,我们引入了临界区概念,临界区是一个用以访问共享资源的代码块,这个代码块在同一时间内只允许一个线程运行。为了更好的实现临界区,Java提供了同步机制,所谓的同步机制是指:当一个线程试图访问一个临界区时,它将使用一种同步机制来查看是不是已经有其他线程进入临界区,如果没有其他线程进入临界区,它就可以进入临界区,否则它就被挂起,直到进入的线程离开这个临界区。关于线程的基础知识可以查看《有关线程的相关知识(1)》和《有关线程的相关知识(2)》


synchronized的使用

在Java中使用synchronized关键字来控制一个方法的并发访问。如果一个对象使用synchronized关键字声明,说明只有一个执行线程被允许访问它。如果synchronized关键字声明的是静态方法,同时只能够被一个执行线程访问,但是其他线程可以访问这个对象的非静态方法,当出现这种情况时就要注意两个方法是否改变了相同的数据,从而导致数据不一致。

接下来我们编写一个银行存取款应用,不用synchronized同步存取款方法,在存取款方法中每次进行操作时休眠50毫秒,在休眠50毫秒的这个时间段内,其他线程可能会执行这个方法,最终的账户余额会改变,从而引发错误。

/** * 用户账户类 *  * <pre> * 含有新增余额和扣除余额操作 * </pre> *  * @author gulinhai * */public class Account {    //账户余额    private double balance;    public double getBalance(){        return balance;    }    public void setBalance(double balance){        this.balance=balance;    }    /**     * 增加余额     * @param amount     */    public void addAmount(double amount){        double tmp=balance;        try {            Thread.sleep(50);        } catch (InterruptedException e) {            e.printStackTrace();        }        tmp+=amount;        balance=tmp;    }    /**     * 扣除余额     * @param amount     */    public void subtractAmount(double amount){        double tmp=balance;        try {            Thread.sleep(50);        } catch (InterruptedException e) {            e.printStackTrace();        }        tmp-=amount;        balance=tmp;    }}



创建一个新增余额的线程,通过for循环100次,每次增加1000:

public class AddAmount implements Runnable{    private Account mAccount;    public AddAmount(Account account){        this.mAccount=account;    }    @Override    public void run() {        for(int i=0;i<100;i++){            mAccount.addAmount(1000);        }    }}



创建一个减少余额的线程,通过for循环100次,每次减少1000:

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



创建银行类:

public class Bank {    public static void main(String[] args) {        Account mAccount=new Account();        mAccount.setBalance(1000);        AddAmount addAmount=new AddAmount(mAccount);        Thread addThread=new Thread(addAmount);        SubtractAmount subtractAmount=new SubtractAmount(mAccount);        Thread subtractThread=new Thread(subtractAmount);        System.out.println("余额:"+mAccount.getBalance());                addThread.start();        subtractThread.start();        try {            subtractThread.join();            addThread.join();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("余额:"+mAccount.getBalance());    }}

在主类中创建Account对象mAccount,并进行初始化1000,随后创建新增余额和减少余额线程,并执行这两个线程,最后输出余额,按照日常生活中,新增100次余额,每次1000,随后扣除100次,每次扣1000,最后的结果应该是初始化金额1000,但我们运行上面的程序。

余额:1000.0余额:-5000.0

可以看到每次运行的结果大部分是不想同的,也就是多个线程同时操作了同一个数据,造成数据不一致,这里我们用synchronized关键字来同步新增和扣除方法,使得每次只有一个线程执行新增或扣除操作:

/**     * 增加余额     * @param amount     */    public synchronized void addAmount(double amount){        double tmp=balance;        try {            Thread.sleep(50);        } catch (InterruptedException e) {            e.printStackTrace();        }        tmp+=amount;        balance=tmp;    }    /**     * 扣除余额     * @param amount     */    public synchronized void subtractAmount(double amount){        double tmp=balance;        try {            Thread.sleep(50);        } catch (InterruptedException e) {            e.printStackTrace();        }        tmp-=amount;        balance=tmp;    }
运行的结果:余额:1000.0余额:1000.0

使用synchronized关键字,保证了在并发程序中对共享数据的正确访问。这里要注意了synchronized关键字会降低应用程序的性能,因此只能在并发场景中需要修改共享数据时使用,我们推荐这样使用synchronized:方法的其余部分保持在synchronized代码块之外,以获取更好的性能。这样使用就需要把对象引入作为传入参数。

接下来在编写一个程序,并在类中添加两个非依赖属性,用于多线程共享,同一时刻只允许一个线程访问一个属性变量,其他某个线程访问另一个变量。程序中创建两个数字,初始化各20,在两个线程中对这两个数字进行添加和减少操作。

public class Test {    private int number1;    private int number2;    private final Object numberObject1;    private final Object numberObject2;    public Test(){        numberObject1=new Object();        numberObject2=new Object();        number1=20;        number2=20;    }    public int getNumber1(){        return number1;    }    public int getNumber2(){        return number2;    }    public void substractNmbuer1(int number){        synchronized (numberObject1) {            if(number<number1){                number1-=number;            }        }    }    public void substractNmbuer2(int number){        synchronized (numberObject2) {            if(number<number2){                number2-=number;            }        }    }    public void addNumber1(int number){        synchronized (numberObject1) {            number1+=number;        }    }    public void addNumber2(int number){        synchronized (numberObject2) {            number1+=number;        }    }}
public class Test1 implements Runnable{    private Test mTest;    public Test1(Test test){        this.mTest=test;    }    @Override    public void run() {        mTest.substractNmbuer1(2);        mTest.substractNmbuer1(5);        mTest.addNumber1(3);        mTest.substractNmbuer2(2);        mTest.substractNmbuer2(4);        mTest.substractNmbuer1(3);    }}
public class Test2 implements Runnable{    private Test mTest;    public Test2(Test test){        this.mTest=test;    }    @Override    public void run() {        mTest.substractNmbuer2(2);        mTest.substractNmbuer2(5);        mTest.addNumber1(3);        mTest.substractNmbuer2(2);        mTest.substractNmbuer1(4);        mTest.addNumber2(6);        mTest.substractNmbuer1(3);        mTest.substractNmbuer1(3);    }}
public class Client {    public static void main(String[] args) {        Test test=new Test();        Test1 test1=new Test1(test);        Thread thread1=new Thread(test1);        Test2 test2=new Test2(test);        Thread thread2=new Thread(test2);        thread1.start();        thread2.start();        try {            thread1.join();            thread2.join();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("number1剩余:"+test.getNumber1()+"\nnumber2剩余:"+test.getNumber2());    }}
运行程序:number1剩余:12number2剩余:5

在并发编程中有一个典型的问题就是生产者-消费者问题,比如我们有一个数据缓冲区,一个或者多个数据生产者,一个或者多个数据消费者将从数据缓冲区中取走数据,这个缓冲区是一个共享结构,因此需要使用同步机制,但这里有个问题,如果使用synchronized关键字,如果缓冲区满了,生产者就不能再放入数据,如果缓冲区空了,消费者也不能读取数据。

为了解决这个问题,Java在Object类中提供了wait()、notify()和notifyAll()方法。我们可以在同步代码块中调用wait()方法,这时JVM将这个线程置入休眠,并且释放控制这个同步代码块的对象,同时允许其他线程执行这个对象控制的其他同步代码块,这时在这个对象控制的某个同步代码块中调用notify()或者notifyAll()方法,来唤醒这个线程。

接下来实现一个生产者-消费者问题。

/** * 创建数据存储类EventStorage,并保存一个最大值maxSize和数据集合 * LinkedList<Date>来保存存入的日期。 * @author gulinhai * */public class EventStorage {    private int maxSize;    private LinkedList<Date> storage;    public EventStorage(){        this.maxSize=10;        this.storage=new LinkedList<>();    }    public int size(){        return storage.size();    }    /**     * 同步方法set(),保存数据到存储列表storage。     * 首先检查列表是不是满了,如果满了,就调用wait()方法挂起线程并等待空余空间的出现,     * 最后调用notifyAll()方法唤醒所有因调用wait()方法进入休眠的线程。     */    public synchronized void set(){        while(storage.size()==maxSize){            try {                wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        storage.add(new Date());        notifyAll();    }    /**     * 同步方法set(),从存储列表storage中获取数据。     * 首先检查列表是不是空了,如果空了,就调用wait()方法挂起线程并等待列表中数据的出现,     * 最后调用notifyAll()方法唤醒所有因调用wait()方法进入休眠的线程。     */    public synchronized void get(){        while(storage.size()==0){            try {                wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        storage.poll();        notifyAll();    }}
/** * 生产者 * @author gulinhai * */public class Producer implements Runnable{    private EventStorage storage;    public Producer(EventStorage storage){        this.storage=storage;    }    @Override    public void run() {        for(int i=0;i<100;i++){            storage.set();        }    }}
/** * 消费者 * @author gulinhai * */public class Consumber implements Runnable{    private EventStorage storage;    public Consumber(EventStorage storage){        this.storage=storage;    }    @Override    public void run() {        for(int i=0;i<100;i++){            storage.get();        }    }}
public class Client {    public static void main(String[] args) {        EventStorage storage=new EventStorage();        Producer producer=new Producer(storage);        Thread producerThread=new Thread(producer);        Consumber consumber=new Consumber(storage);        Thread consumberThread=new Thread(consumber);        producerThread.start();        consumberThread.start();        try {            producerThread.join();            consumberThread.join();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("缓冲区数据个数:"+storage.size());    }}
输出:缓冲区数据个数:0

注意:必须在while循环中调用wait(),并且不断查询while的条件。

3 0
原创粉丝点击