zoukankan      html  css  js  c++  java
  • 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的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,

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

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

     

  • 相关阅读:
    LeetCode 977 有序数组的平方
    LeetCode 24 两两交换链表中的节点
    LeetCode 416 分割等和子集
    LeetCode 142 环形链表II
    LeetCode 106 从中序与后序遍历序列构造二叉树
    LeetCode 637 二叉树的层平均值
    LeetCode 117 填充每个节点的下一个右侧节点
    LeetCode 75 颜色分类
    redhat 7.4 挂载ntfs格式的u盘并且使用
    redhat 查看CPU frequency scaling(CPU频率缩放)
  • 原文地址:https://www.cnblogs.com/weiguo21/p/4753536.html
Copyright © 2011-2022 走看看