博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
synchronized使用及实现原理
阅读量:6768 次
发布时间:2019-06-26

本文共 5789 字,大约阅读时间需要 19 分钟。

  hot3.png

1.Synchronized的基本使用

Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。从语法上讲,Synchronized总共有三种用法:

  1. 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁;
  2. 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁;
  3. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

1)、没有同步的情况:

/** * 没有同步的情况 * * Created by Jiacheng on 2018/6/28. */public class SynchronizedTest {    public void method1() {        System.out.println("Method 1 start");        try {            System.out.println("Method 1 execute");            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 1 end");    }    public void method2() {        System.out.println("Method 2 start");        try {            System.out.println("Method 2 execute");            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 2 end");    }    public static void main(String[] args) {        final SynchronizedTest test = new SynchronizedTest();        new Thread(() -> test.method1()).start();        new Thread(() -> test.method2()).start();    }}

2)、对普通方法同步:

/** * 同步实例方法 *  * Created by Jiacheng on 2018/6/28. */public class SynchronizedMethod {    public synchronized void method1() {        System.out.println("Method 1 start");        try {            System.out.println("Method 1 execute");            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 1 end");    }    public synchronized void method2() {        System.out.println("Method 2 start");        try {            System.out.println("Method 2 execute");            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 2 end");    }    public static void main(String[] args) {        final SynchronizedMethod test = new SynchronizedMethod();        new Thread(() -> test.method1()).start();        new Thread(() -> test.method2()).start();    }}

虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。

3)、静态方法(类)同步:

/** * 同步静态方法 * * Created by Jiacheng on 2018/6/28. */public class SynchronizedStatic {    public static synchronized void method1() {        System.out.println("Method 1 start");        try {            System.out.println("Method 1 execute");            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 1 end");    }    public static synchronized void method2() {        System.out.println("Method 2 start");        try {            System.out.println("Method 2 execute");            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 2 end");    }    public static void main(String[] args) {        final SynchronizedStatic test = new SynchronizedStatic();        final SynchronizedStatic test2 = new SynchronizedStatic();        new Thread(() -> test.method1()).start();        new Thread(() -> test2.method2()).start();    }}

虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。

4)、代码块同步:

/** * 同步方法块 * * Created by Jiacheng on 2018/6/28. */public class SynchronizedBlock {    public void method1() {        System.out.println("Method 1 start");        try {            synchronized (this) {                System.out.println("Method 1 execute");                Thread.sleep(3000);            }        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 1 end");    }    public void method2() {        System.out.println("Method 2 start");        try {            synchronized (this) {                System.out.println("Method 2 execute");                Thread.sleep(1000);            }        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Method 2 end");    }    public static void main(String[] args) {        final SynchronizedBlock test = new SynchronizedBlock();        new Thread(() -> test.method1()).start();        new Thread(() -> test.method2()).start();    }}

对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。

2.用在代码块和方法上的区别?

  1. synchronized用在代码块锁的是调用该方法的对象(this),也可以选择锁住任何一个对象。
  2. synchronized用在方法上锁的是调用该方法的对象,
  3. synchronized用在代码块可以减小锁的粒度,从而提高并发性能。
  4. 无论用在代码块上还是用在方法上,都是获取对象的锁;每一个对象只有一个锁与之相关联;实现同步需要很大的系统开销作为代价,甚至可能造成死锁,所以尽量避免无谓的同步控制。

3.synchronized实现原理

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1)如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2)如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

3)如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。

在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。synchronized方法实际上等同于用一个synchronized块包住方法中的所有语句,然后在synchronized块的括号中传入this关键字。当然如果是静态方法,需要锁定的则是class对象。
可能一个方法中只有几行代码涉及到线程同步的问题,所以synchronized块比synchronized方法更近细粒度的控制了多个线程的访问,只有synchronized块中的内容不能同时被多个线程访问,方法中的其他语句仍然可以同时被多个线程所访问(包括synchronized块之前和之后的)。

4.优缺点

使用synchronized,当多个线程尝试获取锁时,未获取到锁的线程会不断的尝试获取锁,而不会发生中断,这样会造成性能消耗。

 

参考资料

《Java多线程编程实战指南》

转载于:https://my.oschina.net/ljc94/blog/1837279

你可能感兴趣的文章