美文网首页
synchronized关键字(一)

synchronized关键字(一)

作者: AD刘涛 | 来源:发表于2020-03-13 16:53 被阅读0次

    synchronized的简介:

    在java中synchronized关键字是同步锁,同步锁是依赖于对象而存在的,而且每一个对象有且仅有一个同步锁。当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了obj 这个对象的同步锁。

    synchronized的原理:

    Synchronized是通过对象内部的一个叫做monitor的锁来实现的,但是monitor锁的本质又是依赖于底层的操作系统的互斥锁Mutex Lock)来实现的,而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。这种依赖于操作系统互斥锁Mutex Lock)所实现的锁我们称之为重量级锁

    不使用并发手段会有什么后果?

    package synchronize;
    
    /**
     * 消失的请求数
     */
    public class DisappearRequest1 implements Runnable{
    
        static int i = 0;
        static DisappearRequest1 instance = new DisappearRequest1();
    
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            // 同时启动线程t1与线程t2,然后调用run方法
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
    
        @Override
        public void run() {
            for (int j = 0; j < 100000; j++) {
                i++;
            }
        }
    }
    
    

    通过运行以上代码,我们发现:两个线程同时a++操作,最后结果会比预计的少。

    原因:
    count++, 看上去只是一个操作, 实际上包含了三个操作,它并非原子性操作:

    1. 读取count
    2. count 加 1
    3. count的值写入内存中

    在多线程环境下,任何一步执行完都有可能被打断, 都有可能轮到第二个线程去执行。假设目前我们的count的值是9,当第一个线程执行完count++操作时,正好被第二个线程打断,而此时第一个线程执行完count++操作后并没有把值写入内存,所以此时第二个线程在读取count的值时,count的值依旧是9。按正常逻辑来讲,两次count++操作后,count的值为11,然而在这样的一种情况下,两次count++操作后,count的值只是10。对于这样的一种现象,我们称作线程不安全

    synchronized的两个用法:

    对象锁

    包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)

    代码块形式

    手动指定锁对象

    package synchronize;
    
    /**
     * 描述 对象锁实例1,代码块形式
     */
    public class SynchronizedObjectCodeBlock2 implements Runnable {
    
        static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
    
        // 当两个线程一起调用该方法时,线程会进入阻塞状态,从而达到串行的效果,以保证线程安全
        @Override
        public void run() {
            // this值为SynchronizedObjectCodeBlock2对象
            synchronized (this) {
                System.out.println("this的值为:"+ this);
                System.out.println("我是对象锁的代码块形式。我叫" + Thread.currentThread().getName());
    
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "运行结束");
            }
        }
    
        public static void main(String[] args) {
    
            System.out.println("instance实例对象:" + instance);
    
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
    
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
    
    
        }
    }
    
    

    方法锁形式

    synchronized修饰普通方法, 锁对象默认为this

    package synchronize;
    
    /**
     * 描述:对象锁实例2,方法锁形式
     */
    public class SynchronizedObjectMethods3 implements Runnable {
    
        static SynchronizedObjectMethods3 instance = new SynchronizedObjectMethods3();
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
    
            t1.start();
            t2.start();
    
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
    
        }
    
        @Override
        public void run() {
            methods();
        }
    
        // 当两个线程一起调用该方法时,线程会进入阻塞状态,从而达到串行的效果,以保证线程安全
        public synchronized void methods(){
            System.out.println("我是对象锁的方法修饰符形式,我叫"+ Thread.currentThread().getName());
    
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"运行结束");
        }
    
    }
    
    
    

    类锁

    synchronized修饰静态的方法或指定锁Class对象。在java类中可能有很个对象,但只有1Class对象。所谓的类锁,其本质不过就是Class 对象锁而已。

    类锁与对象锁的区别

    类锁只能在同一时刻被一个对象拥有。如果有个对象进行竞争的话,线程与线程之间将会阻塞;但对象锁则不同,不同的对象锁之间是不会有任何干扰的,他们会并行执行,并不会造成阻塞。因为锁的当前对象是this同一个类中,当锁对象不一样的多个线程同时运行时,会同时执行;如果是类锁,且两个对象都属于同一个类,则意味着这两个对象共享同一把锁,这样一来则会进行阻塞

    形式一:

    static方法上加synchroized关键字。

    package synchronize;
    
    /**
     * 描述:类锁的第一种形式:静态方法锁
     */
    public class SynchronizedClassStatic4 implements Runnable {
    
        static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
        static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
    
        @Override
        public void run() {
            methods();
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instance1);
            Thread t2 = new Thread(instance2);
            t1.start();
            t2.start();
    
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    
        // TODO why? 为什么methods方法加上 static 关键字就会造成阻塞?没加static关键字时,是一种的并行的状态?
        //  原因是该方法加上static关键字后就会属于类锁(锁定的对象是class对象),
        //  而class对象又是唯一的,要争取资源必须等待,所以先后执行了;
        //  然而一旦去掉static关键字,methods方法则变成对象方法,因此不同的对象都执行各的run方法,
        //  即锁(this)对象不同,所以则是并行执行。
        //
        public static synchronized void methods(){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是类锁的第一形式:static形式,我叫"+ Thread.currentThread().getName());
    
        }
    }
    
    

    形式二:

    synchroized(*.class)代码块。

    package synchronize;
    
    /**
     * 描述:类锁的第二种形式:`synchroized`(*.class)代码块。
     */
    public class SynchronizedClassClass5 implements Runnable {
    
        static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
        static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();
    
        @Override
        public void run() {
            try {
                methods();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instance1);
            Thread t2 = new Thread(instance2);
            t1.start();
            t2.start();
    
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    
        // TODO 当使用 SynchronizedClassClass5.class 时,线程以串行的方式执行;
        //  当我们使用this关键字进行替换时,线程差不多是以并行的方式来执行 ? 为什么呢 ?
        private void methods() throws InterruptedException {
            synchronized (SynchronizedClassClass5.class){
                System.out.println("我是类锁的第二种形式:synchronized(*.class)。" +
                        "我叫"+ Thread.currentThread().getName());
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "运行结束");
            }
        }
    }
    
    

    思考?

    1. 两个线程同时访问一个对象的同步方法。

      • 会进行阻塞。原因是他们的锁对象是同一个,两者之间会形成互斥,以串行的方式执行。
    2. 两个线程访问的是两个对象的同步方法。

      • 这个时候,synchroized是不起作用的,它俩是并行执行,原因是他们的锁对象并不是同一个,两者之间并不会形成互斥。
    3. 两个线程访问的是synchroized的静态方法。

      • 锁生效,线程会进行阻塞,两者之间并会形成互斥,因为一旦在方法名上加上static关键字,则会构成类锁,多个对象共用一个方法。因此会造成阻塞。具体如SynchronizedClassStatic4案例所示。
    4. 同时访问同步方法与非同步方法(通过代码演示)。

    package synchronize;
    
    /**
     *  描述: 同时访问同步方法与非同步方法,执行结果:同时开始,同时结束,可见非同步方法不受到影响
     */
    public class SynchronizedYesAndNo6 implements Runnable {
    
        static SynchronizedYesAndNo6 instance = new SynchronizedYesAndNo6();
    
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-0")){
                methods1();
            }else {
                methods2();  
            }
        }
    
        // 同步方法
        public synchronized void methods1(){
            System.out.println("我是加锁方法。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
    
        }
    
        // 非同步方法
        public void methods2(){
            System.out.println("我是没加锁方法。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
    
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
        }
    }
    
    
    1. 访问同一个对象的不同的普通同步方法(代码如下)。
    package synchronize;
    
    /**
     * 描述: 同时访问一个类的不同的普通同步方法
     */
    public class SynchronizedDifferenceMethods7 implements Runnable {
    
        static SynchronizedDifferenceMethods7 instance1 = new SynchronizedDifferenceMethods7();
    
        // TODO 从执行结果来看,虽然没有指定锁对象,但本质上是指定了this作为锁的对象 
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-0")){
                methods1();
            }else {
                methods2();
            }
        }
    
        // 同步方法
        public synchronized void methods1(){
            System.out.println("我是加锁方法。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
    
        }
    
        // 同步方法
        public synchronized void methods2(){
            System.out.println("我是加锁方法。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
    
        }
    
        public static void main(String[] args) {
    
            Thread t1 = new Thread(instance1);
            Thread t2 = new Thread(instance1);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
    
        }
    }
    
    
    1. 同时访问静态synchronized和非静态synchronized的方法
    package synchronize;
    
    /**
     *  描述:同时访问静态synchronized和非静态synchronized的方法
     */
    public class SynchronizedStaticAndNormal8 implements Runnable {
    
        static SynchronizedStaticAndNormal8 instance = new SynchronizedStaticAndNormal8();
    
        // TODO 从执行结果来看,两个线程同时开始,同时结束。这是因为两个线程走的是不同的方法,即线程锁对象并非是同一个。
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-0")){
                // 第一个线程走methods1()方法
                methods1();
            }else {
                // 第二个线程走methods2()方法
                methods2();
            }
        }
    
        // 非静态同步方法(该方法的锁对象为:对象实例本身)
        public synchronized void methods1(){
            System.out.println("我是非静态加锁方法。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
    
        }
    
        // 静态同步方法(该方法的锁对象为:类锁)
        public static synchronized void methods2(){
            System.out.println("我是静态加锁方法。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
    
        }
    
        public static void main(String[] args) {
    
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
    
        }
    }
    
    
    1. 方法抛出异常后,会释放锁吗?
      • lock不一样,synchronized在抛出异常后,会释放锁,而lock则需要在异常的`finaly中进行释放。
    package synchronize;
    
    /**
     * 描述: 方法抛弃后,会释放锁。
     * 展示不抛出异常前和抛出异常后的对比:一旦抛出异常,第二个线程会立刻进入同步方法,意味着锁已经释放
     */
    public class SynchronizedException9 implements Runnable {
    
        static SynchronizedException9 instance = new SynchronizedException9();
    
        @Override
        public void run() {
            if(Thread.currentThread().getName().equals("Thread-0")){
                methods1();
            }else {
                methods2();
            }
        }
    
        // 非静态同步方法
        public synchronized void methods1(){
            System.out.println("我是加锁方法。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                throw new Exception();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
    
        }
    
        public synchronized void methods2(){
            System.out.println("我是加锁方法。我叫" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
    
        }
    
        public static void main(String[] args) {
    
            // TODO 这两个线程使用的是同一个对象,这意味着是同一个对象锁
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
    
            }
            System.out.println("finished");
    
        }
    }
    
    

    总结:

    1. 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(如案例1和案例5)。
    2. 每个实例都对应有自己的一把锁,不同实例之间互不影响,例外:锁对象是.class以及synchronized修饰的是static方法的时候,所有对象共同使用一把类锁(如2,3,4,6案例所示)。
    3. synchronized代码块中,无论是正常使用还是在执行过程中抛出异常,锁都会得到释放(如案例7所示)。

    在这里我们在补充一下,当线程调用synchronized关键字修饰的方法时,而这个方法中再次调用一个没有被synchronized关键字修饰的其他方法时,这个时候还是线程安全吗?

    答案是否定的。一旦离开synchronized关键字修饰的方法,进入到另一个方法时,这个方法就有可以被多个线程同时访问。

    参考

    相关文章

      网友评论

          本文标题:synchronized关键字(一)

          本文链接:https://www.haomeiwen.com/subject/asuvjhtx.html