Thread中的同步锁__synchronized

同步锁常用于在多线程安全处理中。说道线程安全就需要提及多个专业概念。同步锁分为两种sychronized和Lock。介绍同步锁时需要介绍介个概念。

同步:在高并发的情况下,为了防止数据出错,一个线程对于共享资源执行操作的时候,另外的线程要执行操作此共享资源需要等待前一个线程释放此共享资源,才能操作。

同步监视器:共享资源。

同步函数:synchronized修饰的方法,同步监视器为当前this对象。

同步代码块:synchronized修饰的代码块,同步监视器可以自定义。

同步锁Lock:另一种更加强大的线程安全机制:通过显示的定义同步锁对象来实现同步,同步锁对象由Lock对象充当。

两者同步机制的区别:Lock是显示的使用Lock对象作为同步监视器,同步方法隐式的使用当前对象作为同步监视器。

Lock开启后必须关闭:lock()//加锁    unlock()//释放锁

sychronized:

        同步关键字,被该关键字修饰的代码块具有原子性,即使发生线程抢占该代码块的情况,首次进入的线程会给该代码块加锁,其他线程进入阻塞状态。

     sychronized主要有两种方式,[synchronized 方法]和[synchronized 块],其实就是他们的作用域不一样。如下:

class MyTest {
    public int count = 0;
    //print1 方法   -synchronized块(对象级,这里是对象不是地址引用)
    public int print1() {
        synchronized (this) {
            count++;
        }
        return count;
    }

    //print2 方法  -synchronized块(类级别)
    public int print2() {
        synchronized (com.xuanyuan.makefun.codingstudy.Test.class) {
            count++;
        }
        return count;
    }

    //print3 方法  -synchronized 方法
    public synchronized int print3() {
        count++;
        return count;
    }

    //普通方法
    public int commonPrint() {
        return count;
    }
}

采用了synchronized机制的类都有自己的类锁(只有一个),该类的任何对象实例都有独立的对象锁。任何类。当在一个线程中通过某对象的实例调用了它的synchronized方法或者synchronized代码块时,这个实例(对象)或者类就会被加锁,直至synchronized方法返回或者代码块中代码执行完才会释放锁,期间无法调用该类或者实例的属性或方法(根据锁的对象不一样,限制范围不一样)。加锁期间,线程持有hold对应实例或者类的锁。

注意,synchronized方法或synchronized代码块有如下特点:

1. 如某个类或实例中,线程想要调用synchronized方法或者synchronized代码块必须要获得类或者实例的锁。

2. 多个线程并发访问synchronized时,任何时刻只有一个线程能够持有锁,只有synchronized修饰的方法或者代码块执行完毕才会释放锁,其他线程此时会被阻塞。

3. 多线程并发中,访问synchronized方法或者synchronized(this)块时,其他线程可以访问非synchronized代码块或者synchronized方法,不可以访问其他synchronized修饰的方法或者代码块

4. 多线程并发访问synchronized(A.class)锁定是A.class这个类,此时其他线程不能访问A.class中的任何方法和代码块,不论是否经synchronized 关键字修饰或者是否是静态方法及变量。

5. 在含有静态方法和静态变量的代码块,类锁的严格约束在多线程场景下非常实用,应用也较多。

在众多写法中 这两个方法常常是等价,如下代码:

public synchronized void m () {
}
// 这两个方法事实上是等价的
public  void m1 () {
   synchronized (this){
    }
}

注意:synchronized 方法相对于synchronized 代码块最明显的是其作用域更大一些,事实上作用域更大一些在多线程情况下会降低效率,因为如果一个方法代码量过大,不涉及共享资源读写的代码可通过多线程快速Ready,只等锁释放后进行同步代码的执行。所以在多线程开发中尽量缩小需要同步锁机制保障的代码的范围。

 

在同步锁的使用中,“类锁”虽然可以最大限度的避免并发场景下的冲突,但是,其过于严格:多线程场景下,当某一线程获得类锁(注意:不再是对象锁),其它线程将被阻塞,将无法调用或访问该类的所有方法和域,包括静态方法和静态变量。一个类,不可能所有属性和方法都涉及并发问题,因此,类锁过于严格的限制会极大的影响性能。下面说说互斥锁mutex。

class TestSynchronized   {
    //定义一个静态对象
    private static Object mutex = new Object();
    public void Test() {
        synchronized (mutex) {
            //涉及并发问题的代码块
        }
    }
}

由于mutex为静态类型,对于TestForSynchronized类的所有对象,当需要访问TestMethod的同步块时都必须获得mutex对象的锁,然而,mutex属于类,有且仅有一个锁,从而可以保证任意一个时刻只有一个线程可以访问这个同步块。

Lock:

        是java.util.concurrent.locks包下的接口。在使用时使用ReentrantLock这个实现类来创建Lock对象,调用lock()为代码块加锁,记住需要使用再次调用unLock()为代码块解锁。从而实现同步锁操作。

 

补充一下额外知识

死锁

死锁简单来说就是两个没有释放的同步进程都想取得对方共享资源(同步监视器)的操作权。

死锁分类

1. 等待死锁

举例:有同步函数A,B 有资源a1,b1 问题:A,B都想取得a1,b1的操作权。

假设:A先取的a1的操作权,同时B取得了b1的操作权,此时,A再想取得b1的操作权必须等同步函数B先释放资源,但是B由于没有拿到a1的操作权所以一直没有释放资源。此时就会造成线程得等待死锁(阻塞死锁)

 

2.递归死锁(很少有人这样做吧)

举例:A同步函数,B非同步函数。A同步函数里调用了B非同步函数,但是B非同步进程里又调用了A同步进程。

假设:运行A同步函数时,由于A中同步函数调用了B函数,所以进入B函数,B函数再次调用了A同步函数,因为之前得A同步函数资源还没有释放,再从B函数中调用A同步函数会造成,再次调用得A同步函数进入递归死锁状态。

 

避免死锁技术

1. 加锁顺序

所有线程按照一定顺序获取锁,避免按照不同顺序加锁时出现死锁。按顺序加锁或者获取锁资源,这种方式的缺点需要知道所有加锁情况,全局分析情况难以避免未知情况。

2. 加锁时限

在获取锁的过程中加上超时时间,超时则释放资源并回退,稍后再尝试加锁。

缺点:当线程多了之后,各线程的限时时间越接近,也会发生死锁。

3. 死锁检测

死锁检测机制,主要针对顺序加锁和加锁限时都无法实现的情况下使用。

原理:每当一个线程获得或者请求锁后,获得或请求的锁信息会在锁和线程的数据结构中记录下来。当一个线程请求锁失败时,会遍历锁的关系图,查看是否发生死锁。

例如:有线程A,B, 有锁lock1,lock2。A{lock1,lock2} 和B{lock2,lock1}括号中是想要获取的锁。此时当A请求lock2失败时,会检测自身是否加锁了B请求的锁。如果有就发生了死锁。

 

如有描述错误,敬请斧正,期待各位的指点。

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值