zoukankan      html  css  js  c++  java
  • Java多线程5:synchronized锁方法块

    synchronized同步代码块

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

    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 = 1
    synchronized threadName = Thread-1, i = 2
    nosynchronized threadName = Thread-0, i = 95
    synchronized threadName = Thread-1, i = 3
    nosynchronized threadName = Thread-0, i = 96
    synchronized threadName = Thread-1, i = 4
    nosynchronized threadName = Thread-0, i = 97
    synchronized threadName = Thread-1, i = 5
    nosynchronized threadName = Thread-0, i = 98
    synchronized threadName = Thread-1, i = 6
    nosynchronized threadName = Thread-0, i = 99
    synchronized threadName = Thread-1, i = 7
    nosynchronized threadName = Thread-0, i = 100
    ...
    synchronized threadName = Thread-1, i = 98
    synchronized threadName = Thread-1, i = 99
    synchronized threadName = Thread-1, i = 100
    synchronized threadName = Thread-0, i = 1
    synchronized threadName = Thread-0, i = 2
    synchronized 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 = 1443843271982
    A end time = 1443843273983
    B begin time = 1443843273983
    B 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 = 995
    synchronized threadName = Thread-0, i = 996
    synchronized threadName = Thread-0, i = 997
    synchronized threadName = Thread-0, i = 998
    synchronized threadName = Thread-0, i = 999
    synchronized threadName = Thread-0, i = 1000
    ----------run--otherMethod

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

    ...
    synchronized threadName = Thread-0, i = 16
    synchronized threadName = Thread-0, i = 17
    synchronized threadName = Thread-0, i = 18
    synchronized threadName = Thread-0, i = 19
    synchronized threadName = Thread-0, i = 20
    ----------run--otherMethod
    synchronized threadName = Thread-0, i = 21
    synchronized threadName = Thread-0, i = 22
    synchronized 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锁,大大提高了运行效率。

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

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

    细化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-0
    testMethod1__releaseLock time = 1443855944812, run ThreadName = Thread-0
    speedPrintString__getLock time = 1443855944812, run ThreadName = Thread-1
    ----------
    speedPrintString__releaseLock time = 1443855944812, run ThreadName = Thread-1

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

  • 相关阅读:
    又玩起了“数独”
    WebService应用:音乐站图片上传
    大家都来DIY自己的Blog啦
    CSS导圆角,不过这个代码没有怎么看懂,与一般的HTML是不同
    网站PR值
    CommunityServer2.0何去何从?
    网络最经典命令行
    炎热八月,小心"落雪"
    Topology activation failed. Each partition must have at least one index component from the previous topology in the new topology, in the same host.
    SharePoint 2013服务器场设计的一些链接
  • 原文地址:https://www.cnblogs.com/xrq730/p/4851530.html
Copyright © 2011-2022 走看看