谁的锁,锁的谁--关于synchronized、ReentrantLock的正确解锁姿势

线程安全是并发编程中的一个重大关注点,所谓线程安全就是要保证在多个线程间读写共享数据保证正确性,如果不能保证,那就不是线程安全的了。
synchronized、ReentrantLock都是用来实现线程间同步,访问同步的代码需要先获得锁,保证每次都只能有一个线程进入同步块,代码执行完毕后释放锁,从而保证了线程间的安全。

所以在实现线程安全中需要考虑的核心问题就是“谁的锁,锁的谁”,只要记住这条规则,就大体上能够达到线程安全性的要求。

锁藏在什么地方?

每个类都有一把锁,暂且叫类锁,所有这个类的实例都可以获取这个类的锁;

每个实例也都有一把锁,暂且叫实例锁,类锁和实例锁不是相同的,只有实例自己才能获得自己的实例锁,不同的实例获得实例锁是不一样的,这个很关键。

谁的锁,锁的谁?

先从synchronized开始分析,还是以代码示例方式讲讲更好理解。
关键字synchronized有多种用法:

1.用在实例方法上:

public synchornized someMethod(){
    ...
}

谁的锁:当前实例的实例锁,不同实例获得的实例锁在这里不是同一把锁;

锁的谁:锁的是这个方法代码块,即线程要进入这个方法就必须先获得这个实例的锁,同一个实例互斥。

2.用在静态方法上:

public static  synchornized someMethod(){
    ...
}

谁的锁:当前类的类锁,即该类的所有实例共享的锁

锁的谁:锁的是这个方法代码块,即线程要进入这个方法就必须先获得这个类的锁,该类的所有实例互斥。

3.用在表达式上:

synchronized(表达式){
    ...
}

谁的锁:表达式如果是个实例,则是该实例的实例锁,同一个实例互斥;如果表达式是this,则是当前类的实例的实例锁,等同于方式用在实例方法上,当前类的同一个实例互斥;如果表达式是个Class,则是这个类的锁,即线程要进入这个同步代码块需要先获取这个类的锁,该类的所有实例互斥;

锁的谁:锁的是synchronized里面的代码块,根据表达式的不同,实现不同的互斥目的。

所以以下几种情况都是错误的……………

先来个数据共享类吧:

public class Item {
    private int value;

    public Item(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

}

1.完全没有同步,这个不用说了。

public class LockerTest implements Runnable {
    private static final Item item = new Item(0);

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            increase();
        }
    }

    private void increase() {
        int value = item.getValue();
        value++;
        item.setValue(value);
    }

    public static void main(String[] args) throws InterruptedException {
        LockerTest test = new LockerTest();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(item.getValue());
    }

}

2.表面上是加锁了,但是….不是同一个实例,锁不住的,这相当于一个线程各种拿各自的实例锁,线程间不是同步互斥!
只需要把increase改成静态方法的就ok了,用类锁,达到线程互斥效果。

public class LockerTest implements Runnable {
    private static final Item item = new Item(0);

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            increase();
        }
    }

    private synchronized void increase() {
        int value = item.getValue();
        value++;
        item.setValue(value);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread( new LockerTest());
        Thread t2 = new Thread( new LockerTest());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(item.getValue());
    }

}

3.表面上是加锁了,但是….不是同一个实例,锁不住的!效果更上面的是一样的!修改为用同一个实例创建线程就好了,用实例锁搞定!

public class LockerTest implements Runnable {
    private static final Item item = new Item(0);

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            increase();
        }
    }

    private void increase() {
        synchronized (this) {
            int value = item.getValue();
            value++;
            item.setValue(value);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new LockerTest());
        Thread t2 = new Thread(new LockerTest());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(item.getValue());
    }

}

说说ReentrantLock,重入锁

ReentrantLock,重入锁,是synchronized的升级版,比synchronized功能更强大一些,在使用上是可以完全替代synchronized.
性能上,在JDK6开始,synchronized做了大量优化,二者性能相差不大了,但在JDK5重入锁要大大的高于synchronized。
实例说话:

public class LockerTest implements Runnable {
    private static final Item item = new Item(0);
    private static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            try {
                lock.lock();
                increase();
            } finally {
                lock.unlock();
            }
        }
    }

    private void increase() {
        int value = item.getValue();
        value++;
        item.setValue(value);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new LockerTest());
        Thread t2 = new Thread(new LockerTest());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(item.getValue());
    }

}

ReentrantLock是静态实例,则说明该lock是类的锁,所有该类的实例同步互斥;
否则是实例锁,只有同一个实例拥有这个锁,同实例访问lock和unlock之间的代码块是同步互斥的。

相比synchronized重入锁有如下几个优势:

1.可以反复进入,同一个线程获得几个锁,在释放的时候也需要同等释放,要不然会死锁的;

2.支持锁中断lockInterruptiblity(),防死锁,优先响应中断;

3.支持限时获取锁,lock.tryLock(int,TimeUnit);在给定的时间范围捏尝试获取锁;

4.支持尝试获取锁,tryLock不带参数,如果获取不到则立刻返回false,而不等待锁;

5.公平锁ReentrantLock(boolean fair),排队获取锁,性能相对低下;

6.配合Condition,可以让线程在合适的时间等待,得到通知后继续执行。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值