Java多线程2:Synchronized——学习方腾飞Java并发编程的艺术

synchronized原理

相信大家对于synchronized都不陌生,并且或许你还有一种记忆关于synchronized和Lock的优点和缺点。大家都觉得Lock怎么怎么方便,synchronized方法是重量级锁,但是其实在Java版本不断的更新中,1.6对synchronized进行了各种优化,他的情况有很大的改善,之前看到过一篇博客详细分析了synchronized的性能,其实越来越好了,有些甚至超过Lock等性能,具体博客有些忘了0-0。

实现同步

1、普通同步方法,锁的是当前实例对象
2、静态同步方法,锁的是当前类的Class对象
3、 同步代码块,锁的是Synchronized括号例配置的对象
JVM中Synchronized的实现原理是,JVM基于进入和退出Monitor对象来实现方法的同步和代码块同步,但两者实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现,monitorenter指令在编译后插入到同步代码块的开始位置,monitorexit插入到方法结束异常处,JVM保证monitorenter和monitorexit是成对出现的。任何对象都又一个monitor与之关联,当且一个monitor被持有后,他将处于锁定状态。线程执行到monitorenter指令时,会尝试获取对象对应的monitor的所有权,即尝试获取锁。
问题实例

public class ThreadDomain01 {
    private int num = 0;

    public void addNum(String userName)
    {
        try
        {
            if ("a".equals(userName))
            {
                num = 100;
                System.out.println(Thread.currentThread().getName()+" set over!");
                Thread.sleep(2000);
            }
            else
            {
                num = 200;
                System.out.println(Thread.currentThread().getName()+" set over!");
            }
            System.out.println(userName + " num = " + num);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

设计线程a、b去调用上面的方法

public class MyThread01 {
    public static void main(String[] args) {
        ThreadDomain01 th01 = new ThreadDomain01();
        Thread aThread = new Thread(new aRunner(th01), "a");
        Thread bThread = new Thread(new bRunner(th01), "b");
        aThread.start();
        bThread.start();
    }

    static class aRunner implements Runnable {
        private ThreadDomain01 thd;

        public aRunner(ThreadDomain01 thd) {
            this.thd = thd;
        }

        @Override
        public void run() {
            thd.addNum("a");
        }
    }

    static class bRunner implements Runnable {
        private ThreadDomain01 thd;

        public bRunner(ThreadDomain01 thd) {
            this.thd = thd;
        }

        @Override
        public void run() {
            thd.addNum("b");
        }
    }
}
// 运行结果
// a set over!
// b set over!
// b num = 200
// a num = 200

为什么 a num = 200呢?
1、线程a运行,给num赋值100,打印信息,开始睡觉
2、b开始运行,给num赋值200,然后打印b num = 200
3、a醒来,由于同为一个num,所以被b修改后就成了a num = 200
解决方案:addNum方法加上同步,synchronized

 public synchronized void addNum(String userName)
    {
        try
        {
            if ("a".equals(userName))
            {
                num = 100;
                System.out.println(Thread.currentThread().getName()+" set over!");
                Thread.sleep(2000);
            }
// 省略同样的代码
//结果
//a set over! 停顿2秒后打印下一句
//a num = 100
//b set over!
// b num = 200

我们看到是a和b是一个执行完另外一个才会执行。如果我们将线程a和b中分别传入不同的ThreadDomain01对象,则a,b会交叉打印,证明了synchronized的锁是对象锁

同步方法域非同步方法的执行

再看一个例子,同步方法和非同步方法

public class ThreadDomain02 {
    private int num = 0;

    public synchronized void syncMethod() {
        try {
            System.out.println("Begin syncMethod, threadName = " +
                    Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("End syncMethod, threadName = " +
                    Thread.currentThread().getName() + ", end Time = " +
                    System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void asyncMethod() {
        try {
            System.out.println("Begin asyncMethod, threadName = " +
                    Thread.currentThread().getName() + ", begin time = " +
                    System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("End asyncMethod, threadName = " +
                    Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

两个线程执行一下

public class MyThread02 {
    public static void main(String[] args) {
        ThreadDomain02 th02 = new ThreadDomain02();
        Thread syncThread = new Thread(new aRunner(th02), "sync");
        Thread asyncThread = new Thread(new bRunner(th02), "async");
        syncThread.start();
        asyncThread.start();
    }

    static class aRunner implements Runnable {
        private ThreadDomain02 thd;

        public aRunner(ThreadDomain02 thd) {
            this.thd = thd;
        }

        @Override
        public void run() {
            thd.syncMethod();
        }
    }

    static class bRunner implements Runnable {
        private ThreadDomain02 thd;

        public bRunner(ThreadDomain02 thd) {
            this.thd = thd;
        }

        @Override
        public void run() {
            thd.asyncMethod();
        }
    }
}
/**
*Begin syncMethod, threadName = sync
*Begin asyncMethod, threadName = async, begin time = 1533030460434
*End syncMethod, threadName = sync, end Time = 1533030465481
*End asyncMethod, threadName = async
*/

从结果可以看出,执行同步线程过程中,并不影响非同步线程的运行,当然如果非同步方法也加上synchronized 的关键子,则同步方法则因持有同样的对象锁而排队执行。

Synchronized同步代码块

用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间。这种情况下可以尝试使用synchronized同步语句块来解决问题。
使用synchronized同步代码块可以将需要锁起来的代码锁起来,而其他的任务则不需要,减少了代码的执行时间,也使锁更加轻便。
其实和synchronized方法一样,被圈起来的代码,在一线程访问时,他就对外是锁定状态,其他线程访问时会被阻塞。

public class ThreadDomain03 {

    public void syncMethodA() {
        synchronized (this) {
            try {
                System.out.println("Begin syncMethodA, threadName = " +
                        Thread.currentThread().getName() + ", begin Time = " +
                        System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("End syncMethodA, threadName = " +
                        Thread.currentThread().getName() + ", end Time = " +
                        System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void syncMethodB() {
        synchronized (this) {
            System.out.println("Begin syncMethodB, threadName = " +
                    Thread.currentThread().getName() + ", begin time = " +
                    System.currentTimeMillis());
            System.out.println("End syncMethodB, threadName = " +
                    Thread.currentThread().getName() + ", end time = " +
                    System.currentTimeMillis());
        }
    }
}
public class MyThread03 {
    public static void main(String[] args) {
        ThreadDomain03 th = new ThreadDomain03();
        Thread syncThreadA = new Thread(new aRunner(th), "syncA");
        Thread syncThreadB = new Thread(new bRunner(th), "syncB");
        syncThreadA.start();
        syncThreadB.start();
    }

    static class aRunner implements Runnable {
        private ThreadDomain03 thd;

        public aRunner(ThreadDomain03 thd) {
            this.thd = thd;
        }

        @Override
        public void run() {
            thd.syncMethodA();
        }
    }

    static class bRunner implements Runnable {
        private ThreadDomain03 thd;

        public bRunner(ThreadDomain03 thd) {
            this.thd = thd;
        }

        @Override
        public void run() {
            thd.syncMethodB();
        }
    }
}
// Begin syncMethodA, threadName = syncA, begin Time = 1533035257623
// End syncMethodA, threadName = syncA, end Time = 1533035262680
// Begin syncMethodB, threadName = syncB, begin time = 1533035262684
// End syncMethodB, threadName = syncB, end time = 1533035262687

看到syncMethodB方法的同步块访问必须等到syncMethodA方法同步块访问结束之后才可以运行。
结论
1、当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分(默认)
2、当A线程进入对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞
3、synchronized快获得的是一个对象锁,也就是说synchronized块锁定的是整个对象

猜你喜欢

转载自blog.csdn.net/qq_22798455/article/details/81317359