java线程同步:synchronized关键字,Lock接口以及可重入锁ReentrantLock

多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修改,就会导致修改的状态不一致.

用一个实际的例子来说明线程同步的必要性:

package cn.outofmemory.locks;

public class LockDemo implements Runnable {
    private int counter = 0;

    public void run() {
        int loopTimes = 10000;
        while (loopTimes > 0) {
          counter ++;               
            loopTimes --;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LockDemo demo = new LockDemo();

        Thread[] threads = new Thread[]{
                new Thread(demo), new Thread(demo),new Thread(demo), new Thread(demo),new Thread(demo)
        };


        for (Thread t : threads) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("demo's counter is " + demo.counter);
    }
}

这段代码中的LockDemo类实现了Runnable接口,在run方法中对其私有变量counter递加了10000次。在main方法中我们首先初始化了一个LockDemo对象,然后初始化了5个线程,这5个线程公用一个LockDemo的实例。
然后我们一次启动这5个线程,然后通过join等待所有线程结束,最后输出demo实例的counter值来。
运行程序,我这儿得到这样一个输出结果:

demo’s counter is 44041

本来5个线程每个线程递加10000次,应该得到的结果是50000,而实际的结果是44041.
如果你也运行此程序有可能会得到不一样的结果。这取决于这5个线程造成了多少次的冲突。从我的输出结果看,这段程序的5个线程造成了大约6000次的内存争用冲突。
在实际应用中,这是不可用的。

改进程序,避免冲突

我们可以分析一下,这5个线程的冲突出现在什么地方,他们公用了demo对象,同时对demo对象的成员变量counter做递加,也就是说冲突出现在对counter递加这一步上。
我们在这一步操作上加上synchronzied关键字,让5个线程执行到对counter++这步代码时单独运行,应该就可以解决问题了。

修改后的run方法代码:

    public void run() {
        int loopTimes = 10000;
        while (loopTimes > 0) {
            synchronized (this) {
                counter ++;             
            }
            loopTimes --;
        }
    }

我们再次运行程序会得到如下确定的输出结果:

demo’s counter is 50000

这次得到的结果是符合我们的预期的,我们通过synchronized关键字解决了问题。

我们看下使用可重入锁ReentrantLock解决线程同步的方法:

    private final Lock lock = new ReentrantLock();

    public void run() {
        int loopTimes = 10000;
        while (loopTimes > 0) {
            try {
                lock.lock();
                counter ++;             
            } finally {
                lock.unlock();
            }
            loopTimes --;
        }
    }

我们在LockDemo中添加了一个final的成员变量lock,它是一个ReentrantLock的实例。 在run方法中,在counter++这行代码两边加上了try .. finally ..语句,
当线程执行到try块之后,首先通过lock.lock()获得锁,获得锁之后再执行counter++,最后在finally语句块中通过lock的unlock方法释放锁。
我们可以运行修改后的代码,输出如下:

demo’s counter is 50000

输出结果符合逻辑预期。

synchronized获得的内部锁存在一定的局限

  1. 不能中断一个正在试图获得锁的线程
  2. 试图获得锁时不能像trylock那样设定超时时间
  3. 每个锁只有单一的条件,不像condition那样可以设置多个

synchronzied关键字和可重入锁ReentrantLock选择的最佳实践:

  1. 如果synchronized关键字适合程序,尽量使用它,可以减少代码出错的几率和代码数量
  2. 如果特别需要Lock/Condition结构提供的独有特性时,才使用他们
  3. 许多情况下可以使用java.util.concurrent包中的一种机制,它会为你处理所有的加锁情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值