博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
synchronized锁机制 之 代码块锁(转)
阅读量:5094 次
发布时间:2019-06-13

本文共 12577 字,大约阅读时间需要 41 分钟。

synchronized同步代码块

  用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间。这种情况下可以尝试使用synchronized同步语句块来解决问题。看一下例子:

  下面例子是优化后的例子使用代码块锁,原先例子是方法锁,就是同步必须要执行2个for。

public class ThreadDomain18{    public void doLongTimeTask() throws Exception    {        for (int i = 0; i < 100; i++)        {            System.out.println("nosynchronized threadName = " +                     Thread.currentThread().getName() + ", i = " + (i + 1));        }        System.out.println();        synchronized (this)        {            for (int i = 0; i < 100; i++)            {                System.out.println("synchronized threadName = " +                         Thread.currentThread().getName() + ", i = " + (i + 1));            }        }    }}

 

public class MyThread18 extends Thread{    private ThreadDomain18 td;        public MyThread18(ThreadDomain18 td)    {        this.td = td;    }        public void run()    {        try        {            td.doLongTimeTask();        }         catch (Exception e)        {            e.printStackTrace();        }    }}
public static void main(String[] args){    ThreadDomain18 td = new ThreadDomain18();    MyThread18 mt0 = new MyThread18(td);    MyThread18 mt1 = new MyThread18(td);    mt0.start();    mt1.start();}

  运行结果,分两部分来看:

synchronized threadName = Thread-1, i = 1synchronized threadName = Thread-1, i = 2nosynchronized threadName = Thread-0, i = 95synchronized threadName = Thread-1, i = 3nosynchronized threadName = Thread-0, i = 96synchronized threadName = Thread-1, i = 4nosynchronized threadName = Thread-0, i = 97synchronized threadName = Thread-1, i = 5nosynchronized threadName = Thread-0, i = 98synchronized threadName = Thread-1, i = 6nosynchronized threadName = Thread-0, i = 99synchronized threadName = Thread-1, i = 7nosynchronized threadName = Thread-0, i = 100...synchronized threadName = Thread-1, i = 98synchronized threadName = Thread-1, i = 99synchronized threadName = Thread-1, i = 100synchronized threadName = Thread-0, i = 1synchronized threadName = Thread-0, i = 2synchronized threadName = Thread-0, i = 3...

  这个实验可以得出以下两个结论:

  1、当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分,第一部分的执行结果证明了这一点。

  2、当A线程进入对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞,第二部分的执行结果证明了这一点。

  所以,从执行效率的角度考虑,有时候我们未必要把整个方法都加上synchronized,而是可以采取synchronized块的方式,对会引起线程安全问题的那一部分代码进行synchronized就可以了。

两个synchronized块之间具有互斥性

  如果线程1访问了一个对象A方法的synchronized块,那么线程B对同一对象B方法的synchronized块的访问将被阻塞,写个例子来证明一下:

public class ThreadDomain19{    public void serviceMethodA()    {        synchronized (this)        {            try            {                System.out.println("A begin time = " + System.currentTimeMillis());                Thread.sleep(2000);                System.out.println("A end time = " + System.currentTimeMillis());            }             catch (InterruptedException e)            {                e.printStackTrace();            }                    }    }        public void serviceMethodB()    {        synchronized (this)        {            System.out.println("B begin time = " + System.currentTimeMillis());            System.out.println("B end time = " + System.currentTimeMillis());        }    }}

  写两个线程分别调用这两个方法:

public class MyThread19_0 extends Thread{    private ThreadDomain19 td;        public MyThread19_0(ThreadDomain19 td)    {        this.td = td;    }        public void run()    {        td.serviceMethodA();    }}
public class MyThread19_1 extends Thread{    private ThreadDomain19 td;        public MyThread19_1(ThreadDomain19 td)    {        this.td = td;    }        public void run()    {        td.serviceMethodB();    }}

  写个main函数:

public static void main(String[] args){    ThreadDomain19 td = new ThreadDomain19();    MyThread19_0 mt0 = new MyThread19_0(td);    MyThread19_1 mt1 = new MyThread19_1(td);    mt0.start();    mt1.start();}

  运行结果:

A begin time = 1443843271982A end time = 1443843273983B begin time = 1443843273983B end time = 1443843273983

  看到对于serviceMethodB()方法synchronized块的访问必须等到对于serviceMethodA()方法synchronized块的访问结束之后。那其实这个例子,我们也可以得出一个结论:

  synchronized块获得的是一个对象锁,换句话说,synchronized块锁定的是整个对象

synchronized块和synchronized方法

  既然上面得到了一个结论synchronized块获得的是对象锁,那么如果线程1访问了一个对象方法A的synchronized块,线程2对于同一对象同步方法B的访问应该是会被阻塞的,因为线程2访问同一对象的同步方法B的时候将会尝试去获取这个对象的对象锁,但这个锁却在线程1这里。写一个例子证明一下这个结论:

public class ThreadDomain20{    public synchronized void otherMethod()    {        System.out.println("----------run--otherMethod");    }        public void doLongTask()    {        synchronized (this)        {            for (int i = 0; i < 1000; i++)            {                System.out.println("synchronized threadName = " +                         Thread.currentThread().getName() + ", i = " + (i + 1));                try                {                    Thread.sleep(5);                }                catch (InterruptedException e)                {                    e.printStackTrace();                }            }        }    }}

  写两个线程分别调用这两个方法:

public class MyThread20_0 extends Thread{    private ThreadDomain20 td;        public MyThread20_0(ThreadDomain20 td)    {        this.td = td;    }        public void run()    {        td.doLongTask();    }}
public class MyThread20_1 extends Thread{    private ThreadDomain20 td;        public MyThread20_1(ThreadDomain20 td)    {        this.td = td;    }        public void run()    {        td.otherMethod();    }}

  写个main函数调用一下,这里"mt0.start()"后sleep(100)以下是为了确保mt0线程先启动:

public static void main(String[] args) throws Exception    {        ThreadDomain20 td = new ThreadDomain20();        MyThread20_0 mt0 = new MyThread20_0(td);        MyThread20_1 mt1 = new MyThread20_1(td);        mt0.start();        Thread.sleep(100);        mt1.start();    }

  看一下运行结果:

...synchronized threadName = Thread-0, i = 995synchronized threadName = Thread-0, i = 996synchronized threadName = Thread-0, i = 997synchronized threadName = Thread-0, i = 998synchronized threadName = Thread-0, i = 999synchronized threadName = Thread-0, i = 1000----------run--otherMethod

  证明了我们的结论。为了进一步完善这个结论,把"otherMethod()"方法的synchronized去掉再看一下运行结果:

...synchronized threadName = Thread-0, i = 16synchronized threadName = Thread-0, i = 17synchronized threadName = Thread-0, i = 18synchronized threadName = Thread-0, i = 19synchronized threadName = Thread-0, i = 20----------run--otherMethodsynchronized threadName = Thread-0, i = 21synchronized threadName = Thread-0, i = 22synchronized threadName = Thread-0, i = 23...

  "otherMethod()"方法和"doLongTask()"方法中的synchronized块异步执行了

将任意对象作为对象监视器

  总结一下前面的内容:

  1、synchronized同步方法

    (1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态

    (2)同一时间只有一个线程可以执行synchronized同步方法中的代码

  2、synchronized同步代码块

    (1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态

    (2)同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码

  前面都使用synchronized(this)的格式来同步代码块,其实Java还支持对"任意对象"作为对象监视器来实现同步的功能。这个"任意对象"大多数是实例变量方法的参数,使用格式为synchronized(非this对象)。看一下将任意对象作为对象监视器的使用例子:

public class ThreadDomain21{    private String userNameParam;    private String passwordParam;    private String anyString = new String();        public void setUserNamePassword(String userName, String password)    {        try        {            synchronized (anyString)            {                System.out.println("线程名称为:" + Thread.currentThread().getName() +                         "在 " + System.currentTimeMillis() + " 进入同步代码块");                userNameParam = userName;                Thread.sleep(3000);                passwordParam = password;                System.out.println("线程名称为:" + Thread.currentThread().getName() +                         "在 " + System.currentTimeMillis() + " 离开同步代码块");            }        }        catch (InterruptedException e)        {            e.printStackTrace();        }    }}

  写两个线程分别调用一下:

public class MyThread21_0 extends Thread{    private ThreadDomain21 td;        public MyThread21_0(ThreadDomain21 td)    {        this.td = td;    }        public void run()    {        td.setUserNamePassword("A", "AA");    }}
public class MyThread21_1 extends Thread{    private ThreadDomain21 td;        public MyThread21_1(ThreadDomain21 td)    {        this.td = td;    }        public void run()    {        td.setUserNamePassword("B", "B");    }}

  写一个main函数调用一下:

public static void main(String[] args){    ThreadDomain21 td = new ThreadDomain21();    MyThread21_0 mt0 = new MyThread21_0(td);    MyThread21_1 mt1 = new MyThread21_1(td);    mt0.start();    mt1.start();}

  看一下运行结果:

线程名称为:Thread-0在 1443855101706 进入同步代码块线程名称为:Thread-0在 1443855104708 离开同步代码块线程名称为:Thread-1在 1443855104708 进入同步代码块线程名称为:Thread-1在 1443855107708 离开同步代码块

  这个例子证明了:多个线程持有"对象监视器"为同一个对象的前提下,同一时间只能有一个线程可以执行synchronized(非this对象x)代码块中的代码。

  锁非this对象具有一定的优点:如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。但如果同步代码块锁的是非this对象,则synchronized(非this对象x)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率。

  其实无论是方法所还是代码锁都是要以一个对象监视器来锁定,锁定的代码是同步的,锁this是当前对象,锁String是String这个对象,锁Object是Object这个对象,互不干扰,如果有其它线程调用同样用到跟上面锁this、Objcet、String相同对象的方法或代码,就需要等待同步,锁代码块比锁方法更加灵活。因为锁方法锁的是this 也就是当前对象,当一个线程正在调用当前这个对象的所方法时,导致其它线程调用不了该对象的其它锁this的代码,也调不了所有该对象的锁方法。

  锁的是当前这个线程,针对锁的对象的这段代码或方法,一次只能一个线程运行,其它线程运行到此的话会暂停,如果是执行其它非锁的则是异步的,注意这里不要被多线程搞迷糊了。单个线程执行的时候都是同步的,当这个线程被阻塞后,之后的代码(锁内的和锁外的)无论什么都不会执行,只有当唤醒或者恢复正常时才会继续往下走,走完锁内的代码就会放锁,然后继续走剩余的代码

  注意一下"private String anyString = new String();"这句话,现在它是一个全局对象,因此监视的是同一个对象。如果移到try里面,那么对象的监视器就不是同一个了,调用的时候自然是异步调用,可以自己试一下。

  最后提一点,synchronized(非this对象x),这个对象如果是实例变量的话,指的是对象的引用,只要对象的引用不变,即使改变了对象的属性,运行结果依然是同步的

 脏读数据的情况

  线程调用顺序是无序的,在一个没有上锁的方法里面调用任意已经上锁两个的方法,不会保证调两个上锁的方法会同时阻塞被一个线程调用,而是第一个上锁方法阻塞,然后第二个再阻塞,调用第一个方法和第二个方法的中间是异步的,这里做一点操作的话,比如if分支判断,极有可能导致脏读数据发生,参考下面:

  脏读出现了,出现的原因是两个线程以异步的代码调用2个同步的方法,中间做了if分支判断并且sleep延迟,由于调用是在没有同步的代码里面,第一个线程进入了if睡觉,还没开始做插入操作,第二个线程又进入了if,所以导致脏读出现。

  解决:使用锁方法  或者 锁代码块都行 在分支前面锁定代码块。

  打印 listsize = 1 脏读不再出现。

细化synchronized(非this对象x)的三个结论

  synchronized(非this对象x)格式的写法是将x对象本身作为对象监视器,有三个结论得出:

    1、当多个线程同时执行synchronized(x){}同步代码块时呈同步效果

    2、当其他线程执行x对象中的synchronized同步方法时呈同步效果

    3、当其他线程执行x对象方法中的synchronized(this)代码块时也呈同步效果

  第一点很明显,第二点和第三点意思类似,无非一个是同步方法,一个是同步代码块罢了,举个例子验证一下第二点:

public class MyObject{    public synchronized void speedPrintString()    {        System.out.println("speedPrintString__getLock time = " +                 System.currentTimeMillis() + ", run ThreadName = " +                 Thread.currentThread().getName());        System.out.println("----------");        System.out.println("speedPrintString__releaseLock time = " +                 System.currentTimeMillis() + ", run ThreadName = " +                 Thread.currentThread().getName());    }}

  ThreadDomain24中持有MyObject的引用:

public class ThreadDomain24{    public void testMethod1(MyObject mo)    {        try        {            synchronized (mo)            {                System.out.println("testMethod1__getLock time = " +                         System.currentTimeMillis() + ", run ThreadName = " +                         Thread.currentThread().getName());                Thread.sleep(5000);                System.out.println("testMethod1__releaseLock time = " +                         System.currentTimeMillis() + ", run ThreadName = " +                         Thread.currentThread().getName());            }        }        catch (InterruptedException e)        {            e.printStackTrace();        }    }}

  写两个线程分别调用"speedPrintString()"方法和"testMethod1(MyObject mo)"方法:

public class MyThread24_0 extends Thread{    private ThreadDomain24 td;    private MyObject mo;        public MyThread24_0(ThreadDomain24 td, MyObject mo)    {        this.td = td;        this.mo = mo;    }        public void run()    {        td.testMethod1(mo);    }}
public class MyThread24_1 extends Thread{    private MyObject mo;        public MyThread24_1(MyObject mo)    {        this.mo = mo;    }        public void run()    {        mo.speedPrintString();    }}

  写一个main函数启动这两个线程:

public static void main(String[] args){    ThreadDomain24 td = new ThreadDomain24();    MyObject mo = new MyObject();    MyThread24_0 mt0 = new MyThread24_0(td, mo);    MyThread24_1 mt1 = new MyThread24_1(mo);    mt0.start();    mt1.start();}

  看一下运行结果:

testMethod1__getLock time = 1443855939811, run ThreadName = Thread-0testMethod1__releaseLock time = 1443855944812, run ThreadName = Thread-0speedPrintString__getLock time = 1443855944812, run ThreadName = Thread-1----------speedPrintString__releaseLock time = 1443855944812, run ThreadName = Thread-1

  看到"speedPrintString()"方法必须等待"testMethod1(MyObject mo)"方法执行完毕才可以执行,没有办法异步执行,证明了第二点的结论。第三点的验证方法类似,就不写代码证明了。

转载于:https://www.cnblogs.com/jing99/p/10612471.html

你可能感兴趣的文章
mysql基础语句
查看>>
Oracle中的rownum不能使用大于>的问题
查看>>
[Data Structure & Algorithm] 有向无环图的拓扑排序及关键路径
查看>>
git 常用命令
查看>>
cassandra vs mongo (1)存储引擎
查看>>
Visual Studio基于CMake配置opencv1.0.0、opencv2.2
查看>>
Vue音乐项目笔记(三)
查看>>
遍历Map对象
查看>>
计算剪贴板里仿制的代码行数
查看>>
MySQL索引背后的数据结构及算法原理
查看>>
#Leetcode# 209. Minimum Size Subarray Sum
查看>>
eclipse远程连接hive
查看>>
db2循环
查看>>
C#语言-04.OOP基础
查看>>
1)session总结
查看>>
什么?云数据库也能C位出道?
查看>>
PHP深浅拷贝
查看>>
SDN第四次作业
查看>>
ActiveMQ(4) ActiveMQ JDBC 持久化 Mysql 数据库
查看>>
DM8168 DVRRDK软件框架研究
查看>>