您好,欢迎来到华佗健康网。
搜索
您的当前位置:首页java中线程同步的几种方法

java中线程同步的几种方法

来源:华佗健康网
java中线程同步的⼏种⽅法

⽅法⼀:

使⽤synchronized关键字

由于java的每个对象都有⼀个内置锁,当⽤此关键字修饰⽅法时, 内置锁会保护整个⽅法。在调⽤该⽅法前,需要获得内置锁,否则就处于阻塞状态。

注: synchronized关键字也可以修饰静态⽅法,此时如果调⽤该静态⽅法,将会锁住整个类。

注:同步是⼀种⾼开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个⽅法,使⽤synchronized代码块同步关键代码即可。同步⽅法:给⼀个⽅法增加synchronized修饰符之后就可以使它成为同步⽅法,这个⽅法可以是静态⽅法和⾮静态⽅法,但是不能是抽象类的抽象⽅法,也不能是接⼝中的接⼝⽅法。

线程在执⾏同步⽅法时是具有排它性的。当任意⼀个线程进⼊到⼀个对象的任意⼀个同步⽅法时,这个对象的所有同步⽅法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意⼀个同步⽅法,直到这个线程执⾏完它所调⽤的同步⽅法并从中退出,从⽽导致它释放了该对象的同步锁之后。在⼀个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有⾮同步⽅法的。

同步块:同步块是通过锁定⼀个指定的对象,来对同步块中包含的代码进⾏同步;⽽同步⽅法是对这个⽅法块⾥的代码进⾏同步,⽽这种情况下锁定的对象就是同步⽅法所属的主体对象⾃⾝。如果这个⽅法是静态同步⽅法呢?那么线程锁定的就不是这个类的对象了,也不是这个类⾃⾝,⽽是这个类对应的java.lang.Class类型的对象。同步⽅法和同步块之间的相互制约只限于同⼀个对象之间,所以静态同步⽅法只受它所属类的其它静态同步⽅法的制约,⽽跟这个类的实例(对象)没有关系。

如果⼀个对象既有同步⽅法,⼜有同步块,那么当其中任意⼀个同步⽅法或者同步块被某个线程执⾏时,这个对象就被锁定了,其他线程⽆法在此时访问这个对象的同步⽅法,也不能执⾏同步块。

synchronized 关键字⽤于保护共享数据。请⼤家注意“共享数据”,你⼀定要分清哪些数据是共享数据实例:

package com.gcc.interview.synchro; /**

* 创建线程 * @author gcc *

* 2018年3⽉9⽇ */

public class MybanRunnable implements Runnable{

private Bank bank;

public MybanRunnable(Bank bank) { this.bank = bank; }

@Override

public void run() { for(int i=0;i<10;i++) { bank.save1(100);

System.out.println(\"账户余额是---\"+bank.getAccount()); } } }

  

package com.gcc.interview.synchro;/**

* 银⾏存款实例 * @author gcc *

* 2018年3⽉9⽇ */

class Bank{

private int account = 100;

public int getAccount() { return account; }

//同步⽅法

public synchronized void save(int money) { account+=money; }

public void save1(int money) {

//同步代码块

synchronized(this) { account+=money; } }

public void userThread() { Bank bank = new Bank();

MybanRunnable my1 = new MybanRunnable(bank); System.out.println(\"线程1\"); Thread th1 = new Thread(my1); th1.start();

System.out.println(\"线程2\"); Thread th2 = new Thread(my1); th2.start(); } }

  ⽅法⼆:wait和notify

wait():使⼀个线程处于等待状态,并且释放所持有的对象的lock。

sleep():使⼀个正在运⾏的线程处于睡眠状态,是⼀个静态⽅法,调⽤此⽅法要捕捉InterruptedException异常。

notify():唤醒⼀个处于等待状态的线程,注意的是在调⽤此⽅法的时候,并不能确切的唤醒某⼀个等待状态的线程,⽽是由JVM确定唤醒哪个线程,⽽且不是按优先级。

Allnotity():唤醒所有处⼊等待状态的线程,注意并不是给所有唤醒线程⼀个对象的锁,⽽是让它们竞争。⽅法三:

使⽤特殊域变量volatile实现线程同步

a.volatile关键字为域变量的访问提供了⼀种免锁机制

b.使⽤volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新 c.因此每次使⽤该域就要重新计算,⽽不是使⽤寄存器中的值

d.volatile不会提供任何原⼦操作,它也不能⽤来修饰final类型的变量

例如:

在上⾯的例⼦当中,只需在account前⾯加上volatile修饰,即可实现线程同步。

//只给出要修改的代码,其余代码与上同 class Bank {

//需要同步的变量加上volatile

private volatile int account = 100;

public int getAccount() { return account; }

//这⾥不再需要synchronized public void save(int money) { account += money; } }

  

注:多线程中的⾮同步问题主要出现在对域的读写上,如果让域⾃⾝避免这个问题,则就不需要修改操作该域的⽅法。 ⽤final域,有锁保护的域和volatile域可以避免⾮同步的问题。⽅法四:

使⽤重⼊锁实现线程同步

在JavaSE5.0中新增了⼀个java.util.concurrent包来⽀持同步。

ReentrantLock类是可重⼊、互斥、实现了Lock接⼝的锁,它与使⽤synchronized⽅法和快具有相同的基本⾏为和语义,并且扩展了其能⼒。

ReenreantLock类的常⽤⽅法有:

ReentrantLock() : 创建⼀个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁

注:ReentrantLock()还有⼀个可以创建公平锁的构造⽅法,但由于能⼤幅度降低程序运⾏效率,不推荐使⽤

private int account = 100;

private ReentrantLock lock = new ReentrantLock(); public int getAccount() { return account; }

//同步⽅法

public void save(int money) { lock.lock(); try {

account+=money; } finally {

lock.unlock(); } }

  

注:关于Lock对象和synchronized关键字的选择:

a.最好两个都不⽤,使⽤⼀种java.util.concurrent包提供的机制,能够帮助⽤户处理所有与锁相关的代码。 b.如果synchronized关键字能满⾜⽤户的需求,就⽤synchronized,因为它能简化代码

c.如果需要更⾼级的功能,就⽤ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 ⽅法五:

使⽤局部变量来实现线程同步

如果使⽤ThreadLocal管理变量,则每⼀个使⽤该变量的线程都获得该变量的副本,副本之间相互独⽴,这样每⼀个线程都可以随意修改⾃⼰的变量副本,⽽不会对其他线程产⽣影响。 ThreadLocal 类的常⽤⽅法

ThreadLocal() : 创建⼀个线程本地变量

get() : 返回此线程局部变量的当前线程副本中的值

initialValue() : 返回此线程局部变量的当前线程的\"初始值\"

set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

//只改Bank类,其余代码与上同 public class Bank{

//使⽤ThreadLocal类管理共享变量account

private static ThreadLocal account = new ThreadLocal(){ @Override

protected Integer initialValue(){ return 100; } };

public void save(int money){

account.set(account.get()+money); }

public int getAccount(){ return account.get(); } }

  

注:ThreadLocal与同步机制

a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 b.前者采⽤以\"空间换时间\"的⽅法,后者采⽤以\"时间换空间\"的⽅式⽅法六:

使⽤阻塞队列实现线程同步

前⾯5种同步⽅式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使⽤javaSE5.0版本中新增的

java.util.concurrent包将有助于简化开发。 本⼩节主要是使⽤LinkedBlockingQueue来实现线程的同步 LinkedBlockingQueue是⼀个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~LinkedBlockingQueue 类常⽤⽅法 LinkedBlockingQueue() : 创建⼀个容量为Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在队尾添加⼀个元素,如果队列满则阻塞 size() : 返回队列中的元素个数 take() : 移除并返回队头元素,如果队列空则阻塞代码实例: 实现商家⽣产商品和买卖商品的同步

注:BlockingQueue定义了阻塞队列的常⽤⽅法,尤其是三种添加元素的⽅法,我们要多加注意,当队列满时:  add()⽅法会抛出异常  offer()⽅法返回false  put()⽅法会阻塞

7.使⽤原⼦变量实现线程同步

需要使⽤线程同步的根本原因在于对普通变量的操作不是原⼦的。

那么什么是原⼦操作呢?原⼦操作就是指将读取变量值、修改变量值、保存变量值看成⼀个整体来操作即-这⼏种⾏为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原⼦类型变量的⼯具类,使⽤该类可以简化线程同步。其中AtomicInteger 表可以⽤原⼦⽅式更新int的值,可⽤在应⽤程序中(如以原⼦⽅式增加的计数器),但不能⽤于替换Integer;可扩展Number,允许那些处理机遇数字类的⼯具和实⽤⼯具进⾏统⼀访问。AtomicInteger类常⽤⽅法:

AtomicInteger(int initialValue) : 创建具有给定初始值的新的

AtomicIntegeraddAddGet(int dalta) : 以原⼦⽅式将给定值与当前值相加get() : 获取当前值代码实例:

只改Bank类,其余代码与上⾯第⼀个例⼦同

class Bank {

private AtomicInteger account = new AtomicInteger(100); public AtomicInteger getAccount() { return account; }

public void save(int money) { account.addAndGet(money); }}

  

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo0.com 版权所有 湘ICP备2023021991号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务