• java多线程详解(3)-线程的互斥与同步


    前言:前一篇文章主要描述了多线程中访成员变量与局部变量问题,我们知道访成员变量有线程安全问题,在多线程程序中

    我们可以通过使用synchronized关键字完成线程的同步,能够解决部分线程安全问题

    在java中synchronized同步关键字可以使用在静态方法和实例方法中使用,两者的区别在于:

    对象锁与类锁
    对象锁
    当一个对象中有synchronized method或synchronized block的时候调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。

    如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放

    类锁
    由上述同步静态方法引申出一个概念,那就是类锁。其实系统中并不存在什么类锁。当一个同步静态方法被调用时,系统获取的其实就是代表该类的类对象的对象锁
    在程序中获取类锁
    可以尝试用以下方式获取类锁
    synchronized (xxx.class) {...}
    synchronized (Class.forName("xxx")) {...}
    同时获取2类锁
    同时获取类锁和对象锁是允许的,并不会产生任何问题,但使用类锁时一定要注意,一旦产生类锁的嵌套获取的话,就会产生死锁,因为每个class在内存中都只能生成一个Class实例对象。

    同步静态方法/静态变量互斥体
    由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只由一份。所以,一旦一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。一旦一个静态变量被作为synchronized block的mutex。进入此同步区域时,都要先获得此静态变量的对象锁

    代码

    /**
     * 同步代码块与同步实例方法的互斥
     * 
     * @author cary
     */
    public class TestSynchronized {
        /**
         * 同步代码块
         */
        public void testBlock() {
            synchronized (this) {
                int i = 5;
                while (i-- > 0) {
                    System.out
                            .println(Thread.currentThread().getName() + " : " + i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException ie) {
                    }
                }
            }
        }
    
        /**
         * 非同步普通方法
         */
        public void testNormal() {
            int i = 5;
            while (i-- > 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    
        /**
         * 同步实例方法
         */
        public synchronized void testMethod() {
            int i = 5;
            while (i-- > 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    
        /**
         * 主方法分别调用三个方法
         * 
         * @param args
         */
        public static void main(String[] args) {
            final TestSynchronized test = new TestSynchronized();
            Thread test1 = new Thread(new Runnable() {
                public void run() {
                    test.testBlock();
                }
            }, "testBlock");
            Thread test2 = new Thread(new Runnable() {
                public void run() {
                    test.testMethod();
                }
            }, "testMethod");
            test1.start();
            ;
            test2.start();
            test.testNormal();
        }
    }

    执行结果

    testBlock : 4
    main : 4
    testBlock : 3
    main : 3
    testBlock : 2
    main : 2
    testBlock : 1
    main : 1
    testBlock : 0
    main : 0
    testMethod : 4
    testMethod : 3
    testMethod : 2
    testMethod : 1
    testMethod : 0

    上述的代码,第一个方法时用了同步代码块的方式进行同步,传入的对象实例是this,表明是当前对象,

    当然,如果需要同步其他对象实例,也不可传入其他对象的实例;第二个方法是修饰方法的方式进行同步。

    因为第一个同步代码块传入的this,所以两个同步代码所需要获得的对象锁都是同一个对象锁,

    下面main方法时分别开启两个线程,分别调用testBlock()和testMethod()方法,那么两个线程都需要获得该对象锁,

    另一个线程必须等待。上面也给出了运行的结果可以看到:直到testBlock()线程执行完毕,释放掉锁testMethod线程才开始执行

    (两个线程没有穿插执行,证明是互斥的)

    对于普通方法

    结果输出是交替着进行输出的,这是因为,某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。

    进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,

    其他线程还是可以访问那些没有同步的方法(普通方法)

    结论:synchronized只是一个内置锁的加锁机制,当某个方法加上synchronized关键字后,就表明要获得该内置锁才能执行,

    并不能阻止其他线程访问不需要获得该内置锁的方法

    类锁的修饰(静态)方法和代码块:

    /**
     * 
     * 类锁与静态方法锁
     * 
     * @author cary
     */
    public class TestSynchronized2 {
        /**
         * 类锁
         */
        public void testClassLock() {
            synchronized (TestSynchronized2.class) {
                int i = 5;
                while (i-- > 0) {
                    System.out
                            .println(Thread.currentThread().getName() + " : " + i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException ie) {
                    }
                }
            }
        }
    
        public static synchronized void testStaticLock() {
            int i = 5;
            while (i-- > 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    
        /**
         * 普通方法
         */
        public void testNormal() {
            int i = 5;
            while (i-- > 0) {
                System.out.println("normal-" + Thread.currentThread().getName()
                        + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    
        /**
         * 静态方法
         */
        public void testStaticNormal() {
            int i = 5;
            while (i-- > 0) {
                System.out.println("static-" + Thread.currentThread().getName()
                        + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    
        /**
         * 测试synchronized锁的互斥效果
         * 
         * @param args
         */
        public static void main(String[] args) {
            final TestSynchronized2 test = new TestSynchronized2();
            Thread testClass = new Thread(new Runnable() {
                public void run() {
                    test.testClassLock();
                }
            }, "testClassLock");
            Thread testStatic = new Thread(new Runnable() {
                public void run() {
                    TestSynchronized2.testStaticLock();
                }
            }, "testStaticLock");
            /**
             * 线程1
             */
            testClass.start();
            /**
             * 线程2
             */
            testStatic.start();
            /**
             * 成员方法
             */
            test.testNormal();
            /**
             * 静态方法
             */
            TestSynchronized2.testStaticLock();
    
        }
    }

    执行结果

    testClassLock : 4
    normal-main : 4
    normal-main : 3
    testClassLock : 3
    normal-main : 2
    testClassLock : 2
    testClassLock : 1
    normal-main : 1
    testClassLock : 0
    normal-main : 0
    testStaticLock : 4
    testStaticLock : 3
    testStaticLock : 2
    testStaticLock : 1
    testStaticLock : 0
    main : 4
    main : 3
    main : 2
    main : 1
    main : 0

    类锁和静态方法锁线程是分先后执行的,没有相互交叉,类锁和静态方法锁是互斥的

    其实,类锁修饰方法和代码块的效果和对象锁是一样的,因为类锁只是一个抽象出来的概念,

    只是为了区别静态方法的特点,因为静态方法是所有对象实例共用的,

    所以对应着synchronized修饰的静态方法的锁也是唯一的,所以抽象出来个类锁。

    结论:类锁和静态方法锁是互斥的

     锁静态方法和普通方法

    /**
     * 锁普通方法和静态方法。
     * 
     * @author cary
     */
    public class TestSynchronized3 {
        /**
         * 锁普通方法
         */
        public synchronized void testNormal() {
            int i = 5;
            while (i-- > 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    
        /**
         * 锁静态方法
         */
        public static synchronized void testStatic() {
            int i = 5;
            while (i-- > 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    
        /**
         * 普通方法和静态方法
         * 
         * @param args
         */
        public static void main(String[] args) {
            final TestSynchronized test = new TestSynchronized();
            Thread test1 = new Thread(new Runnable() {
                public void run() {
                    test.testNormal();
                }
            }, "testNormal");
            Thread test2 = new Thread(new Runnable() {
                public void run() {
                    TestSynchronized3.testStatic();
                }
            }, "testStatic");
            /**
             * 启动普通方法线程
             */
            test1.start();
            /**
             * 启动静态方法线程
             */
            test2.start();
    
        }
    }

     执行结果

    testNormal : 4
    testStatic : 4
    testNormal : 3
    testStatic : 3
    testNormal : 2
    testStatic : 2
    testStatic : 1
    testNormal : 1
    testNormal : 0
    testStatic : 0

    上面代码synchronized同时修饰静态方法和实例方法,但是运行结果是交替进行的,

    这证明了类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。

    同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。

    到这里,对synchronized的用法已经有了一定的了解。这时有一个疑问,既然有了synchronized修饰方法的同步方式,

    为什么还需要synchronized修饰同步代码块的方式呢?而这个问题也是synchronized的缺陷所在

    synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,

    必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,

    那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。

     

  • 相关阅读:
    严选B端产品设计原则
    从严选供应链采购看业务系统架构
    网易严选数据任务治理实践
    hibernate主键生成策略
    the user operation is waiting
    hibernate入门案例
    jsp重定向和转发
    jsp编码过程
    在eclipse中部署jsp项目
    oracle误删表解决方案
  • 原文地址:https://www.cnblogs.com/weiguo21/p/4753536.html
Copyright © 2020-2023  润新知