线程同步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的条件。
- 线程同步---synchronized
- 线程同步synchronized
- 线程同步---synchronized
- cocoa线程同步synchronized
- 线程同步----synchronized
- synchronized线程同步
- java 线程同步 synchronized
- java 线程同步 synchronized
- java synchronized 线程同步
- Java 线程同步 synchronized
- Java线程同步synchronized
- synchronized 线程同步
- 线程同步synchronized
- 线程同步1-synchronized
- 线程同步(synchronized)
- 线程的同步synchronized
- 线程同步(synchronized)
- 线程同步---synchronized
- Java框架:Spring原理与环境搭建
- 【贪心】洛谷 P1031 均分纸牌
- 打开debug模式下的打印窗口output
- 【模拟】洛谷 P1035 级数求和
- Oracle12.2 多租户环境下的授权管理
- 线程同步synchronized
- JavaScript eval定义动态变量名及赋值教程
- 【搜索】洛谷 P1036 选数
- Java中将byte[]转为Blob对象
- objc -编译Runtime 源码
- java从入门到精通(java 核心技术卷,一些总结)
- QApplication的用法
- 第一篇博客——个人感悟
- 【模拟】洛谷 P1042 乒乓球