• Synchronized使用方法


    Synchronized是我们常用来维持线程安全时使用的一个关键字,内部通过monitor(监视器锁,由C++实现)来实现。而monitor本质又是依赖底层操作系统的mutex lock来实现。而操作系统实现线程之间的切换,需要从用户态切换到核心态,这个的成本非常高,状态之间的转换需要相对较长的时间,这也就是为什么synchronized效率低的原因。这种依赖mutex lock所实现的锁统称为“重量级锁”。jdk对Synchronized的优化,核心都是减少这种重量级锁的使用。

    monitor:由C++实现,可以与对象一起创建和销毁,或者在线程尝试获取对象锁时创建。

    一个线程尝试获取对象锁,会先令_owner指向该线程,同时_count自增1,这时候有其他线程尝试获取锁时,会先存入_EntryList集合,并进入阻塞。当前线程执行完成后,令_count自减1,_owner=null,同时将其放到_WaitSet中去。

    如果线程被调用了wait()等方式,释放锁时,也会令_owner=null,_count减1,同时将该线程放入_WaitSet集合,等待唤醒。

    Synchronized可以使用在三个地方:

    1. 非静态方法
    2. 静态方法
    3. 代码块

    首先我们先看一个普通的多线程方法

    public class Main6  {
    
        public void method1() {
            System.out.println("method1 start");
            try {
                System.out.println("method1 exec");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method1 completion");
        }
    
        public void method2() {
            System.out.println("method2 start");
            try {
                System.out.println("method2 exec");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method2 completion");
        }
    
        public static void main(String[] args) {
            Main6 m1 = new Main6();
            Main6 m2 = new Main6();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    m1.method1();
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    m1.method2();
                }
            }).start();
        }
    }

    在这个方法执行的时候,method1()和method2()并行运行,但由于method2()的运行时间短,因此总是会先运行结束。

    1.非静态方法

    public class Main6  {
    
        public synchronized void method1() {
            System.out.println("method1 start");
            try {
                System.out.println("method1 exec");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method1 completion");
        }
    
        public synchronized void method2() {
            System.out.println("method2 start");
            try {
                System.out.println("method2 exec");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method2 completion");
        }
    
        public static void main(String[] args) {
            Main6 m1 = new Main6();
            Main6 m2 = new Main6();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    m1.method1();
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    m1.method2();
                }
            }).start();
        }
    }

    在非静态方法上添加synchronized关键字,当我们使用同一个对象分别在不同线程中调用该方法时候,会出现如下顺序的结果:

    method1 start
    method1 exec
    method1 completion
    method2 start
    method2 exec
    method2 completion

    这是由于synchronized会获取当前调用对象的锁,当线程1在执行m1.method1();时候获取到了m1对象的锁,线程2企图执行m1.method2()方法时发现m1的锁已经被其他线程获取,因此会进入阻塞状态,直到线程1对method1()方法执行完。

    注意,由于调用线程1的start()方法并不是一定会立即开始执行线程1,它先会与main线程争夺cpu等资源,因此有可能method2先执行,method1进入阻塞,直到method2执行完成后才会开始执行。

    同时由于synchronized使用在非静态方法上获取的是当前对象的锁,因此假如线程1和2分别使用的是两个不同对象的mthod1方法和method2方法,则不会发生阻塞。

    2.静态方法

    public class Main6  {
    
        public static synchronized void method1() {
            System.out.println("method1 start");
            try {
                System.out.println("method1 exec");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method1 completion");
        }
    
        public static synchronized void method2() {
            System.out.println("method2 start");
            try {
                System.out.println("method2 exec");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method2 completion");
        }
    
        public static void main(String[] args) {
            Main6 m1 = new Main6();
            Main6 m2 = new Main6();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    m1.method1();
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    m2.method2();
                }
            }).start();
        }
    }

    静态方法与非静态方法的不同处在于方法是属于类,而不是属于当前对象。

    这样即使我们调用的是不同对象的method1方法和method2方法,但是它们获取的锁是类对象的锁(Class对象的锁),因此,不论使用的是哪个对象的method1和method2,均会发生线程阻塞的情况。只有当一个线程中的静态synchronized执行完成后才会执行另一个线程中的静态synchronized方法。

    3.代码块

    public class Main6  {
    
        public void method1() {
            System.out.println("method1 start");
            synchronized(this) {
                try {
                    System.out.println("method1 exec");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("method1 completion");
        }
    
        public void method2() {
            System.out.println("method2 start");
            synchronized (this) {
                try {
                    System.out.println("method2 exec");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("method2 completion");
        }
    
        public static void main(String[] args) {
            Main6 m1 = new Main6();new Thread(new Runnable() {
                @Override
                public void run() {
                    m1.method1();
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    m1.method2();
                }
            }).start();
        }
    }

    输出的结果可能如下:

    method1 start
    method1 exec
    method2 start
    method1 completion
    method2 exec
    method2 completion

    当synchronized被使用在代码块上时,它所获取的锁是括号中接收的对象的锁(如样例代码中的this指代的是当前调用它的对象),这样,假设method1方法正在执行,并且进入代码块休眠3秒,这时method2获取cpu,开始执行第一行代码打印"method2 start",然后遇见代码块,尝试获取this对象的锁,却发现已经被method1获取(sleet不释放锁),因此method2进入阻塞状态,直到method1执行完毕后释放锁,method2获取锁开始执行代码块。

     在代码块中调用Synchronized(下图),侧重点在于第3步monitorenter,获取monitor,并对当前_count加一。里面内容执行完成后,在第15步monitorexit,令_count减一,同时释放monitor。

    而当Synchronized被使用在方法上时,添加的是一个ACC_SYNCHRONIZED标志位,如果有该标志位,则判定当前方法使用了Synchronized

     

  • 相关阅读:
    CSS中float与A标签的疑问
    常用的Css命名方式
    div css 盒子模型
    HTML初级教程 表单form
    Redis学习记录(二)
    Redis学习记录(一)
    Java源码——HashMap的源码分析及原理学习记录
    java编程基础——从上往下打印二叉树
    java编程基础——栈压入和弹出序列
    java基础编程——获取栈中的最小元素
  • 原文地址:https://www.cnblogs.com/yxth/p/10655199.html
Copyright © 2020-2023  润新知