⽅法⼀:
使⽤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 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 注:BlockingQueue 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
本站由北京市万商天勤律师事务所王兴未律师提供法律服务