多线程学习之synchronized的理解、synchronized锁的到底是什么《五》

1. synchronized的缺陷:
 当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,
 必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。
 如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。
 
 当然同步方法和同步代码块都会有这样的缺陷,只要用了synchronized关键字就会有这样的风险和缺陷。
 既然避免不了这种缺陷,那么就应该将风险降到最低。
这也是同步代码块在某种情况下要优于同步方法的方面。
例如在某个类的方法里面:
这个类里面声明了一个对象实例,SynObject so=new SynObject();
8. 在某个方法里面调用了这个实例的方法so.testsy();
9. 但是调用这个方法需要进行同步,不能同时有多个线程同时执行调用这个方法
10. 这时如果直接用synchronized修饰调用了so.testsy();代码的方法,
	那么当某个线程进入了这个方法之后,这个对象其他同步方法都不能给其他线程访问了。
	假如这个方法需要执行的时间很长,那么其他线程会一直阻塞,影响到系统的性能。
	
11. 如果这时用synchronized来修饰代码块:synchronized(so){so.testsy();},
那么这个方法加锁的对象是so这个对象,跟执行这行代码的对象没有关系,
当一个线程执行这个方法时,这对其他同步方法时没有影响的,因为他们持有的锁都完全不一样。

 

2. 锁的形象化

打个比方:一个object就像一个大房子,大门永远打开。房子里有 很多房间(也就是方法)。

这些房间有上锁的(synchronized方法), 和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙可以打开所有上锁的房间。

另外我把所有想调用该对象方法的线程比喻成想进入这房子某个 房间的人。所有的东西就这么多了,下面我们看看这些东西之间如何作用的。

在此我们先来明确一下我们的前提条件。该对象至少有一个synchronized方法,否则这个key还有啥意义。当然也就不会有我们的这个主题了。

一个人想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时还没有其他人要使用上锁的 房间)。于是他走上去拿到了钥匙,并且按照自己 的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会马上把钥匙还回去。即使他要连续使用两间上锁的房间,中间他也要把钥匙还回去,再取回来。

因此,普通情况下钥匙的使用原则是:“随用随借,用完即还。”

这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,就只能等了。

要是很多人在等这把钥匙,等钥匙还回来以后,谁会优先得到钥匙?Not guaranteed。(谁也不能保证)像前面例子里那个想连续使用两个上锁房间的家伙,他中间还钥匙的时候如果还有其他人在等钥匙,那么没有任何保证这家伙能再次拿到。 

再来看看同步代码块。和同步方法有小小的不同。

1.从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风隔开的空间。

2.同步代码块还可以人为的指定获得某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,你可以用本房的钥匙;你也可以指定用另一个房子的钥匙才能开,这样的话,你要跑到另一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。

         记住你获得的那另一栋房子的钥匙,并不影响其他人进入那栋房子没有锁的房间。

         为什么要使用同步代码块呢?我想应该是这样的:首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变量,再对这些变量做一些 操作,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响范围。

如何做?同步代码块。我们只把一个方法中该同 步的地方同步,比如运算。

         另外,同步代码块可以指定钥匙这一特点有个额外的好处,是可以在一定时期内霸占某个对象的key。还记得前面说过普通情况下钥匙的使用原则吗。现在不是普通情况了。你所取得的那把钥匙不是永远不还,而是在退出同步代码块时才还。

          还用前面那个想连续用两个上锁房间的家伙打比方。怎样才能在用完一间以后,继续使用另一间呢。用同步代码块吧。先创建另外一个线程,做一个同步代码 块,把那个代码块的锁指向这个房子的钥匙。然后启动那个线程。只要你能在进入那个代码块时抓到这房子的钥匙,你就可以一直保留到退出那个代码块。也就是说 你甚至可以对本房内所有上锁的房间遍历,甚至再sleep(10*60*1000),而房门口却还有1000个线程在等这把钥匙呢。很过瘾吧。
即使有一个房间排队的人已经超过100个了,但是这排队的一百个人也是随时可以访问不加锁的放假
因为锁,锁住的只是某一个房间或者某一个房间的柜子
而且一个房间排队不会影响另一个房间

3. synchronized 锁的是什么?

synchronized能够锁当前对象,也能够锁类。

//锁住当前对象

1. public synchronized void a(){ }

2. public void ab(){ 
	  synchronized (this){ 
		} 
	}
	
	
1.  实际上如果是锁住的对象而不是类,只能防止多个线程同时执行同一个对象的同步代码段。
2.  而不是一个进去之后,其它的不能进去了
3.  对于非static的synchronized方法,锁的就是对象本身也就是this。
4.  其实也可以多个线程按照顺序进出,只要三个线程用的是一个对象


//锁住当前类

1. public synchronized static void a(){ } 
2. public static void a(){ 
	synchronized (类名){ 
      } 
   } 
3. public void ab(){ 
	synchronized (类名){ 
		} 
} 

1. 被锁住的方法同时只能被一个线程访问,无论是不是一个对象还是多个不同的对象
2. 但是如果当前类里面有其它方法,锁对象的方法或者普通方法,都是可以不按照顺序访问的
static synchronized方法,static方法可以直接类名加方法名调用,方法中无法使用this,
所以它锁的不是this,而是类的Class对象,
所以,static synchronized方法也相当于全局锁,相当于锁住了当前类的这个代码段,
但是对其它代码段没有影响。

1. 当synchronized作用在对象时候,同一个对象中的线程是相互排斥的,
    仅仅有一个线程运行完毕后。另外一个线程才干获得对象锁得到运行。

2. 假设不是同一个对象,则不会产生相互排斥 当synchronized作用在类时,
    对于同一个jvm中不同对象的多个线程调用同一个synchronized修饰的方法都是相互排斥的

4. 测试类

package com.pf.org.cms.syn;

//测试类

public class MyThread extends Thread {

    public void run() {
        Sync sync = new Sync();
        sync.pu();
        sync.fei();
        sync.test();
    }




    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread thread = new MyThread();
            thread.setName("线程"+i);
            thread.start();
        }
    }
}


class Sync {

    public void test() {
        synchronized (Sync.class) {
            System.out.println(Thread.currentThread().getName()+"静态锁A开始..");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"静态锁A结束..");
        }
    }

    public void pu(){
        System.out.println(Thread.currentThread().getName()+"---------   一般的方法");
    }


    public synchronized void fei() {
            System.out.println(Thread.currentThread().getName()+"========普通锁B开始..");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"=========普通锁B结束..");
        }

}

5. 结果分析:

//测试结果:

线程0---------   一般的方法
线程2---------   一般的方法
线程2========普通锁B开始..
线程1---------   一般的方法
线程1========普通锁B开始..
线程0========普通锁B开始..
线程0=========普通锁B结束..
线程1=========普通锁B结束..
线程2=========普通锁B结束..
线程0静态锁A开始..
线程0静态锁A结束..
线程2静态锁A开始..
线程2静态锁A结束..
线程1静态锁A开始..
线程1静态锁A结束..
结果分析:
像我们上面说的一样,当某一个锁类的方法执行 时,其实对锁对象的方法和普通方法都没有影响

猜你喜欢

转载自blog.csdn.net/qq_39455116/article/details/86634362