java线程同步(7种方式)讲解_第1页
java线程同步(7种方式)讲解_第2页
java线程同步(7种方式)讲解_第3页
java线程同步(7种方式)讲解_第4页
java线程同步(7种方式)讲解_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、关于线程同步(7种方式)为何要使用同步? java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。1.同步方法 即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。 代码如: public synchronized void save()注: synchronized关键字也可以修饰静态方法

2、,此时如果调用该静态方法,将会锁住整个类2.同步代码块 即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步 代码如: synchronized(object) 注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 代码实例:package com.xhj.thread;/* 线程同步的运用* author XIEHEJUN*/public class SynchronizedThread class Bank private int account = 10

3、0;public int getAccount() return account;/* 用同步方法实现* param money*/public synchronized void save(int money) account += money;/* 用同步代码块实现* param money*/public void save1(int money) synchronized (this) account += money;class NewThread implements Runnable private Bank bank;public NewThread(Bank bank) th

4、is.bank = bank;Overridepublic void run() for (int i = 0; i 10; i+) / bank.save1(10);bank.save(10);System.out.println(i + 账户余额为: + bank.getAccount();/* 建立线程,调用内部类*/public void useThread() Bank bank = new Bank();NewThread new_thread = new NewThread(bank);System.out.println(线程1);Thread thread1 = new Th

5、read(new_thread);thread1.start();System.out.println(线程2);Thread thread2 = new Thread(new_thread);thread2.start();public static void main(String args) SynchronizedThread st = new SynchronizedThread();st.useThread();3.使用特殊域变量(volatile)实现线程同步 a.volatile关键字为域变量的访问提供了一种免锁机制, b.使用volatile修饰域相当于告诉虚拟机该域可能会被

6、其他线程更新, c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 例如: 在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。 代码实例:/只给出要修改的代码,其余代码与上同class Bank /需要同步的变量加上volatileprivate volatile int account = 100;public int getAccount() return account;/这里不再需要synchronizedpublic void save(int money) ac

7、count += money;注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。 用final域,有锁保护的域和volatile域可以避免非同步的问题。4.使用重入锁实现线程同步 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力 ReenreantLock类的常用方法有: ReentrantLock() : 创建一个ReentrantLock实例 lo

8、ck() : 获得锁 unlock() : 释放锁 注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 例如: 在上面例子的基础上,改写后的代码为: 代码实例:/只给出要修改的代码,其余代码与上同class Bank private int account = 100;/需要声明这个锁private Lock lock = new ReentrantLock();public int getAccount() return account;/这里不再需要synchronizedpublic void save(int money) l

9、ock.lock();tryaccount += money;finallylock.unlock(); 注:关于Lock对象和synchronized关键字的选择: a.最好两个都不用,使用一种java.util.concurrent包提供的机制, 能够帮助用户处理所有与锁相关的代码。 b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁5.使用局部变量实现线程同步 如果使用ThreadLocal管理变量,则每一个

10、使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。 ThreadLocal 类的常用方法 ThreadLocal() : 创建一个线程本地变量 get() : 返回此线程局部变量的当前线程副本中的值 initialValue() : 返回此线程局部变量的当前线程的初始值 set(T value) : 将此线程局部变量的当前线程副本中的值设置为value 例如: 在上面例子基础上,修改后的代码为: 代码实例:/只改Bank类,其余代码与上同public class Bank/使用ThreadLocal类管理共享变量acc

11、ountprivate static ThreadLocal account = new ThreadLocal()Overrideprotected Integer initialValue()return 100;public void save(int money)account.set(account.get()+money);public int getAccount()return account.get(); 注:ThreadLocal与同步机制 a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 b.前者采用以空间换时间的方法,后者采用以时间换空间

12、的方式6.使用阻塞队列实现线程同步 前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 本小节主要是使用LinkedBlockingQueue来实现线程的同步 LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO),关于队列以后会详细讲解public classLinkedBlockingQueueextendsAbstractQueueimplementsBlockingQueu

13、e,Serializable一个基于已链接节点的、范围任意的blocking queue。此队列按FIFO(先进先出)排序元素。队列的头部是在队列中时间最长的元素。队列的尾部是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。LinkedBlockingQueue 类常用方法 LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在队尾添加一个元素,如果队列满则

14、阻塞 size() : 返回队列中的元素个数 take() : 移除并返回队头元素,如果队列空则阻塞代码实例: 实现商家生产商品和买卖商品的同步1package com.xhj.thread;import java.util.Random;import java.util.concurrent.LinkedBlockingQueue;/* 用阻塞队列实现线程同步 LinkedBlockingQueue的使用* author XIEHEJUN*/public class BlockingSynchronizedThread /* 定义一个阻塞队列用来存储生产出来的商品*/private Link

15、edBlockingQueue queue = new LinkedBlockingQueue();/* 定义生产商品个数*/private static final int size = 10;/* 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程*/private int flag = 0;private class LinkBlockThread implements Runnable Overridepublic void run() int new_flag = flag+;System.out.println(启动线程 + new_flag);if (ne

16、w_flag = 0) for (int i = 0; i size; i+) int b = new Random().nextInt(255);System.out.println(生产商品: + b + 号);try queue.put(b); catch (InterruptedException e) / TODO Auto-generated catch blocke.printStackTrace();System.out.println(仓库中还有商品: + queue.size() + 个);try Thread.sleep(100); catch (InterruptedE

17、xception e) / TODO Auto-generated catch blocke.printStackTrace(); else for (int i = 0; i size / 2; i+) try int n = queue.take();System.out.println(消费者买去了 + n + 号商品); catch (InterruptedException e) / TODO Auto-generated catch blocke.printStackTrace();System.out.println(仓库中还有商品: + queue.size() + 个);tr

18、y Thread.sleep(100); catch (Exception e) / TODO: handle exceptionpublic static void main(String args) BlockingSynchronizedThread bst = new BlockingSynchronizedThread();LinkBlockThread lbt = bst.new LinkBlockThread();Thread thread1 = new Thread(lbt);Thread thread2 = new Thread(lbt);thread1.start();th

19、read2.start();注:BlockingQueue定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:add()方法会抛出异常offer()方法返回falseput()方法会阻塞7.使用原子变量实现线程同步需要使用线程同步的根本原因在于对普通变量的操作不是原子的。那么什么是原子操作呢?原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger表可以用原子方式更

20、新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。AtomicInteger类常用方法:AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicIntegeraddAddGet(int dalta) : 以原子方式将给定值与当前值相加get() : 获取当前值代码实例:只改Bank类,其余代码与上面第一个例子同class Bank private AtomicInteger account = new AtomicInteger(100

21、);public AtomicInteger getAccount() return account;public void save(int money) account.addAndGet(money);补充-原子操作主要有:对于引用变量和大多数原始变量(long和double除外)的读写操作;对于所有使用volatile修饰的变量(包括long和double)的读写操作。知识重在总结和梳理,只有不断地去学习并运用,才能化为自己的东西。一、引言前几天面试,被大师虐残了,好多基础知识必须得重新拿起来啊。闲话不多说,进入正题。二、为什么要线程同步因为当我们有多个线程要同时访问一个变量或对象时,

22、如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。三、不同步时的代码packagethreadTest;/*authorww*/publicclassBankprivateintcount=0;/账户余额/存钱publicvoidaddMoney(intmoney)count+=money;S

23、ystem.out.println(System.currentTimeMillis()+存进:+money);/取钱publicvoidsubMoney(intmoney)if(count-money0)System.out.println(余额不足);return;count-=money;System.out.println(+System.currentTimeMillis()+取出:+money);/查询publicvoidlookMoney()System.out.println(账户余额:+count);packagethreadTest;publicclassSyncThrea

24、dTestpublicstaticvoidmain(Stringargs)finalBankbank=newBank();Threadtadd=newThread(newRunnable()Overridepublicvoidrun()/TODOAuto-generatedmethodstubwhile(true)tryThread.sleep(1000);catch(InterruptedExceptione)/TODOAuto-generatedcatchblocke.printStackTrace();bank.addMoney(100);bank.lookMoney();System.

25、out.println(n););Threadtsub=newThread(newRunnable()Overridepublicvoidrun()/TODOAuto-generatedmethodstubwhile(true)bank.subMoney(100);bank.lookMoney();System.out.println(n);tryThread.sleep(1000);catch(InterruptedExceptione)/TODOAuto-generatedcatchblocke.printStackTrace(););tsub.start();tadd.start();四

26、、使用同步时的代码(1)同步方法:即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。修改后的Bank.javapackagethreadTest;/*authorww*/publicclassBankprivateintcount=0;/账户余额/存钱publicsynchronizedvoidaddMoney(intmoney)count+=money;System.out.println(System.currentTimeMillis()+存进:+money

27、);/取钱publicsynchronizedvoidsubMoney(intmoney)if(count-money0)System.out.println(余额不足);return;count-=money;System.out.println(+System.currentTimeMillis()+取出:+money);/查询publicvoidlookMoney()System.out.println(账户余额:+count);瞬间感觉可以理解了吧。注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类(2)同步代码块即有synchronized

28、关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步Bank.java代码如下:packagethreadTest;/*authorww*/publicclassBankprivateintcount=0;/账户余额/存钱publicvoidaddMoney(intmoney)synchronized(this)count+=money;System.out.println(System.currentTimeMillis()+存进:+money);/取钱publicvoidsubMoney(intmoney)synchronized(this)if(count-mone

29、y0)System.out.println(余额不足);return;count-=money;System.out.println(+System.currentTimeMillis()+取出:+money);/查询publicvoidlookMoney()System.out.println(账户余额:+count);效果和方法一差不多。注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。(3)使用特殊域变量(volatile)实现线程同步 a.volatile关键字为域变量的访问提供了一种免锁机制 b.使用

30、volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新 c.因此每次使用该域就要重新计算,而不是使用寄存器中的值 d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量packagethreadTest;/*authorww*/publicclassBankprivatevolatileintcount=0;/账户余额/存钱publicvoidaddMoney(intmoney)count+=money;System.out.println(System.currentTimeMillis()+存进:+money);/取钱publicvoidsubMoney(i

31、ntmoney)if(count-money0)System.out.println(余额不足);return;count-=money;System.out.println(+System.currentTimeMillis()+取出:+money);/查询publicvoidlookMoney()System.out.println(账户余额:+count);是不是又看不懂了,又乱了。这是为什么呢?就是因为volatile不能保证原子操作导致的,因此volatile不能代替synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。它的原理是每次要线程

32、要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。(4)使用重入锁实现线程同步 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。 ReenreantLock类的常用方法有: ReentrantLock() : 创建一个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁 注:ReentrantL

33、ock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用Bank.java代码修改如下:packagethreadTest;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;/*authorww*/publicclassBankprivateintcount=0;/账户余额/需要声明这个锁privateLocklock=newReentrantLock();/存钱publicvoidaddMoney(intmoney)lock.lock();/上锁trycount+=money;System.out.println(System.currentTimeMillis()+存进:+money);finallylock.unlock();/解锁/取钱publicvoidsubMoney(intmoney)lock.lock();tryif(count-money0)Syst

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论