实现同步的几种方式
Posted yuanfei1110111
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现同步的几种方式相关的知识,希望对你有一定的参考价值。
1.同步方法
即有synchronized关键字修饰的方法;
由于Java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法之前,需要获取内置锁,否则就处于阻塞状态。
2.同步代码块
即有synchronized关键字修饰的语句块;
代码如:
synchronized(object) {
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
3.使用volatile实现线程同步
(1)volatile关键字为域变量的访问提供了一种免锁机制;
(2)使用volatile修饰域,相当于告诉JVM,该域可能会被其它线程更新,因此每次使用该域时就要重新计算,而不是使用寄存器中的值;
(3)volatile不会提供任何原子操作,也不能用来修饰final类型的变量。
实例:如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?
代码示例:
public class Test {
private volatile static int count = 0; //账户余额
public static void main(String[] args) throws InterruptedException {
new TAdd().start();
new TSub().start();
}
public static void addMoney(int money) { //存钱
synchronized(Test.class) {
count+=money;
}
System.out.println(System.currentTimeMillis() + "存进" + money);
}
public static void subMoney(int money) { //取钱
synchronized(Test.class) {
if(count < money) {
System.out.println("余额不足");
return;
}
count -= money;
}
System.out.println(System.currentTimeMillis() + "取出" + money);
}
public static void lookMoney() {
System.out.println("账户余额:"+count);
}
static class TAdd extends Thread {
public void run() {
while(true) {
try {
Thread.sleep(1000);
addMoney(100);
lookMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class TSub extends Thread {
public void run() {
while(true) {
try {
Thread.sleep(1000);
subMoney(100);
lookMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
1545704383398存进100
1545704383398取出100
账户余额:0
账户余额:0
余额不足
账户余额:100
1545704384399存进100
账户余额:100(出错了)
。。。。。。。。
出错原因是输出语句并没有被synchronized同步。另外,volatile不能保证原子性,因此volatile并不能代替synchronized,而在这个例子中,通过synchronized将count包裹起来解决了原子性的问题。volatile的原理是在每次线程要访问volatile修饰的变量时都是从内存中读取,而不是从缓存中读取,因此每个线程访问到的变量值都是一样的。
4.使用重入锁实现线程同步
在Java5中新增了java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力。
ReentrantLock类的常用方法有:
ReentrantLock():创建一个ReentrantLock实例;
lock():获得锁;
unlock():释放锁;
注:ReentrantLock还有一个可以创建公平锁的构造方法,但由于会大幅降低程序运行效率,不推荐使用。
代码示例:
public class Test {
private volatile static int count = 0; //账户余额
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new TAdd().start();
new TSub().start();
}
public static void addMoney(int money) { //存钱
lock.lock();
try {
count+=money;
System.out.println(System.currentTimeMillis() + "存进" + money);
} finally {
lock.unlock();
}
}
public static void subMoney(int money) { //取钱
lock.lock();
try {
if(count < money) {
System.out.println("余额不足");
return;
}
count -= money;
System.out.println(System.currentTimeMillis() + "取出" + money);
} finally {
lock.unlock();
}
}
public static void lookMoney() {
System.out.println("账户余额:"+count);
}
static class TAdd extends Thread {
public void run() {
while(true) {
try {
Thread.sleep(1000);
addMoney(100);
lookMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class TSub extends Thread {
public void run() {
while(true) {
try {
Thread.sleep(1000);
subMoney(100);
lookMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果正常。
注:关于Lock对象和synchronized关键字的选择
(1)如果synchronized能满足用户需求,就用synchronized,因为它能简化代码;
(2)如果需要更高级的功能,就用ReentrantLock,此时需要注意及时释放锁,否则会出现死锁,通常在finally中释放锁。
5.使用ThreadLocal实现线程同步
public class Test {
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) throws InterruptedException {
new TAdd().start();
new TSub().start();
}
public static void addMoney(int money) { //存钱
count.set(count.get() + money);
System.out.println(System.currentTimeMillis() + "存进" + money);
}
public static void subMoney(int money) { //取钱
if(count.get() < money) {
System.out.println("余额不足");
return;
}
count.set(count.get() - money);
System.out.println(System.currentTimeMillis() + "取出" + money);
}
public static void lookMoney() {
System.out.println("账户余额:"+count.get());
}
static class TAdd extends Thread {
public void run() {
while(true) {
try {
Thread.sleep(1000);
addMoney(100);
lookMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class TSub extends Thread {
public void run() {
while(true) {
try {
Thread.sleep(1000);
subMoney(100);
lookMoney();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
余额不足
账户余额:0
1545709620876存进100
账户余额:100
余额不足
账户余额:0
1545709621876存进100
账户余额:200
余额不足
账户余额:0
1545709622876存进100
账户余额:300
余额不足
账户余额:0
1545709623878存进100
账户余额:400
如果使用ThreadLocal管理变量,则每个使用该变量的线程都获得该变量的一个副本,副本之间相互独立,这样每个线程都可以随意更改自己的变量副本,而不会对其它线程产生影响。
ThreadLocal类的常用方法:
ThreadLocal():创建一个线程本地变量;
get():返回此线程局部变量的当前线程副本中的值;
initialValue():返回此线程局部变量的当前线程的初始值;
set(T value):将此线程局部变量的当前线程副本中的值设置为value。
以上是关于实现同步的几种方式的主要内容,如果未能解决你的问题,请参考以下文章