Java中并发编程中的锁机制(synchronizd和Lock)

1.三个概念
1.1.原子性

原子性是指几个操作要么全部执行,要么全部不执行。比如在支付问题中,往A给B转1000元,那么A账户减去1000和B账户加上1000就是一个原子操作,如果A减去1000成功了,但是B加上1000失败了,那么应当回滚。

1.2.可见性

可见性是指当前线程A对于共享变量所做的更改对于线程B是可见的。

1.3.有序性

在正常的单线程中,对于下面一段代码的执行:

int i = 10 //语句1
int j = 20 //语句2
i = i + 10 //语句3
j = j*j    //语句4

在cpu中指令的执行是会重排序的,比如上面的一段代码可能的一段执行的顺序就是:
语句2–>语句1–>语句3–>语句4
但是不可能是:语句2–>语句3–>语句1–>语句4
因为语句3的执行依赖于语句1。
但是如果是在多线程中就不一定了,比如下面这个例子:

//线程1的代码
p = new Person() //语句1
b = true //语句2

//线程2的代码
while(b){ //语句3
    p do something
}

由于语句1 和 2没有依赖关系,可能被倒置,那么多线程中可能出现的执行顺序是:
语句2 –> 语句3 –> 语句1
那么在语句3中使用变量p做一些事情,就会报空指针了,因为p的实例化在后面。

1.4.总结

也就是说,要想并发程序正确地执行,必须要保证原子性可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。

2.validate和synchronized以及lock和以上三个性质的关系
2.1.如何保证原子性

synchronized和lock关键字保证了同一时刻只有一个线程能够访问被锁起来的代码块。故该代码块内部可以保证原子性。
那么validate可以保证原子性吗?
看下面着一段代码:

public class ValidateTest {

    private volatile int inc = 0;

    private void increase(String threadName) {
        System.out.println("当前线程为:" + threadName+"当前inc为:"+inc);
        inc++;
    }

    public static void main(String[] args) {
        final ValidateTest test = new ValidateTest();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase(this.getName());
                }
            }.start();
        }

        while(Thread.activeCount()>2) {  //保证前面的线程都执行完
            System.out.println("当前线程数:" + Thread.activeCount());
            Thread.yield();
        }
        System.out.println("最终结果为:" + test.inc);
    }

}

按照逻辑,这段代码的执行结果为10000,但是由于increase自增操作并不是一个原子操作,所以执行的结果有时候是小于10000的。可见validate关键字是没有原子性的

2.2.如何保证可见性

validate可以保证变量的可见性,使用validate修饰的共享变量,当被修改时,会立即更新到计算机主存中,其他的线程读取时会去内存中读取(关于主存和内存如何存取值,可以参考这篇文章),synchronizedlock也可以保证可见性,同一时刻只有一个线程可以访问同步代码块,并且在锁被释放的时候会立即将共享变量修改的值刷新过去。

2.3.如何保证有序性

synchronized和lock显然能够保证有序性,validate可以一定程度的保证有序性。为什么说是一定程度呢?首先validate可以禁止指令重排序。那么是如何进行的呢,看下面一个例子:

int x; //语句1
int y; //语句2
validate boolean b = false; //语句3
int z = 1; //语句4
y = 2; //语句5

以前5个语句,由于validate修饰了b,所以语句1、2只能在3前面,4、5只能在3后面,但是1、2和4、5语句执行顺序不定。所以是一定程度上保证时序性。
那么对于之前的例子:

//线程1的代码
p = new Person() //语句1
b = true //语句2

//线程2的代码
while(b){ //语句3
    p do something
}

如果给p加上一个validate修饰,就不会出现问题了。

3.目前中常用的一些锁使用及优缺点
3.1 synchronizd

**原理:**Java中的每一个对象都可以作为一个锁
synchronized 获取锁常见有两种,一种是实例化锁也就是对象锁,一种是字节码锁,可以参考下面这段代码:

public class SynTest {
    private static List<String> list = new ArrayList<String>();
    //当前实例的锁
    public synchronized void add1(String s){
        list.add(s);
    }
    //SynTest.class 锁
    public static synchronized void add2(String s){
        list.add(s);
    }
    //SynTest.class 锁
    public void add3(String s){
        synchronized(SynTest.class){
            list.add(s);
        }
    }
    //当前实例的锁
    public void add4(String s){
        synchronized(this){
            list.add(s);
        }
    }
}

上面的方法中,1、4是同一把锁,2、3是同一把锁,只有同一把锁才可以实现锁互斥。所谓互斥就是同时只有一个线程可以访问1和4。

3.2. lock

为什么会出现lock呢,想象一下这种情况,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

  2)线程执行发生异常,此时JVM会让线程自动释放锁。

  那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

  因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
  另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。

  总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:

  1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

  2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

那么lock如何使用呢?

Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){

}finally{
    lock.unlock();   //释放锁
}

和synchronized相比就是需要自己手动去释放锁,稍微麻烦一些。

END
参考

【Java线程】锁机制:synchronized、Lock、Condition
Java并发编程:volatile关键字解析
深入理解synchronized
Java并发编程:Lock

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值