线程同步
多线程情况下,当一个线程使用某一资源时,其他线程无法同时使用此资源,直到当前线程释放对资源的占用。例如A线程使用对象X时,其他线程都无法操作X,直到A线程使用完毕。
实现方式:
1、通过synchronized关键字修饰方法或者代码块:
//同步普通方法
//同步静态方法时,会锁住整个类
public synchronized void testA() {
//do something...
}
2、使用synchronized变量锁定资源,修饰代码块 ###
例如:
public void add(int n) {
synchronized(obj){
sum = sum + n;
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("sum="+sum);
}
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
实例:银行取钱
http://blog.youkuaiyun.com/taoxiuxia/article/details/49157159
public class BankLockTest {
//账户余额
public int account = 1000 ;
//取钱,耗时操作(模拟
public void getMoney() {
account -= 100 ;
try {
Thread.sleep(10);
System.out.println("getMony后: " + account);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public class GetMoneyRun implements Runnable {
BankLockTest bankLockTest = new BankLockTest() ;
@Override
public void run() {
for (int i=0;i<4;i++) {
bankLockTest.getMoney();
}
}
}
public static void main(String[] arg ) {
GetMoneyRun runMoney = new BankLockTest().new GetMoneyRun();
Thread a = new Thread(runMoney);
Thread b = new Thread(runMoney) ;
a.start();
b.start();
}
}
3、使用特殊域变量volatile
实现线程同步
volatile为域变量提供了一种免锁机制,相当于告诉虚拟机这个域可能会被其他线程更新,因此每次使用该域时就要重新计算,而不是取寄存器里的值,volatile不提供任何原子操作,也不能修饰final变量
4、使用ReentrantLock实现Lock接口的锁
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
public Lock mLock = new ReentrantLock() ;
//取钱,耗时操作(模拟
public void getMoney() {
mLock.lock();
account -= 100 ;
try {
Thread.sleep(10);
System.out.println("getMony后: " + account);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
}
ReentrantLock和synchronized关键字有相同的基本行为和语义,并提供了扩展,通过lock和unlock来锁住/解锁代码块
注:关于Lock对象和synchronized关键字的选择:
a.最好两个都不用,使用一种java.util.concurrent包提供的机制,
能够帮助用户处理所有与锁相关的代码。
b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
5、使用局部变量实现线程同步
ThreadLocal管理变量,每个线程中都有个ThreadLocalMap,保存当前的Thread和变量值,线程使用的是变量的副本值,随意修改都不会影响其他线程。
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
注:ThreadLocal与同步机制
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
b.前者采用以”空间换时间”的方法(拿到副本即可更新,不需等待其他线程),后者采用以”时间换空间”的方式(其他线程等待变量被用完)
6、使用阻塞队列
LinkedBlockingQueue是一个基于已连接节点的,范围任意的阻塞队列,先进先出FIFO
LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue
put(E e) : 在队尾添加一个元素,如果队列满则阻塞
size() : 返回队列中的元素个数
take() : 移除并返回队头元素,如果队列空则阻塞
注:BlockingQueue定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:
add()方法会抛出异常
offer()方法返回false
put()方法会阻塞
7、使用原子变量实现线程同步
原子操作指将对变量的读取、修改、保存操作看做一个整体,要么不完成,要么同时完成。例如可以用AtomicInteger实现以原子方式更新int的值。
对于引用变量和大多数原始变量(long和double除外)的读写操作;
对于所有使用volatile修饰的变量(包括long和double)的读写操作。