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

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

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

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

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

若是此对象的对象锁已被其余调用者占用,则须要等待此锁被释放高并发

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

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

 

代码spa

/**
 * 同步代码块与同步实例方法的互斥
 * 
 * @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 : 0code

上述的代码,第一个方法时用了同步代码块的方式进行同步,传入的对象实例是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的缺陷:当某个线程进入同步方法得到对象锁,那么其余线程访问这里对象的同步方法时,

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

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

相关文章
相关标签/搜索