对象及变量的并发拜访

2.1 synchronized同步办法

首先在这里阐明两个概念:线程平安和非线程平安

线程平安:就是获取的实例变量的值通过同步解决不会呈现脏读的景象。

非线程平安:就是多个线程对同一个对象中的实例变量进行并发拜访时产生脏读的,即取到的数据其实是被更改过的。

2.1.1 实例变量非线程平安

上面咱们用代码来阐明一下非线程平安,HasSelfPrivateNum.java代码如下:

public class HasSelfPrivateNum {    private int num = 0;    public void addI(String username) {        try {            if (("a").equals(username)) {                num = 100;                System.out.println("a set over");                Thread.sleep(2000);            } else {                num = 200;                System.out.println("b set over");            }            System.out.println(username + " num= " + num);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

自定义线程类 MyThread1.java, MyThread2.java 代码如下:

public class MyThread1 extends Thread{    private  HasSelfPrivateNum hasSelfPrivateNum;    public MyThread1(HasSelfPrivateNum hasSelfPrivateNum){        this.hasSelfPrivateNum=hasSelfPrivateNum;    }    @Override    public void run() {        hasSelfPrivateNum.addI("a");    }}public class MyThread2 extends Thread{    private HasSelfPrivateNum hasSelfPrivateNum;    public MyThread2 (HasSelfPrivateNum hasSelfPrivateNum){        this.hasSelfPrivateNum=hasSelfPrivateNum;    }    @Override    public void run() {        hasSelfPrivateNum.addI("b");    }}

创立运行类 Test.java,代码如下:

public class Test {    public static void main(String[] args) {        HasSelfPrivateNum hasSelfPrivateNum=new HasSelfPrivateNum();        MyThread1 myThread1=new MyThread1(hasSelfPrivateNum);        myThread1.start();        MyThread2 myThread2=new MyThread2(hasSelfPrivateNum);        myThread2.start();    }}

代码运行运行后果:

a set over
b set over
b num= 200
a num= 200

两个线程同时拜访一个没有同步的办法,如果两个线程同时操作业务对象中的实例变量,则会呈现 “非线程平安”的问题,那咱们如何解决这个问题,只须要在 public void addI(String username)办法后面加上关键字synchronized即可 ,更改后办法的代码如下:

public synchronized void addI(String username)

再次运行程序后果如下:

a set over
a num= 100
b set over
b num= 200

所以得出结论,两个线程拜访同一个对象中的同步办法时肯定是线程平安的。

2.1.2 多个对象多个锁

其余类的代码不变,批改运行类 Test.java,代码如下:

public class Test {    public static void main(String[] args) {        HasSelfPrivateNum hasSelfPrivateNum1=new HasSelfPrivateNum();        HasSelfPrivateNum hasSelfPrivateNum2=new HasSelfPrivateNum();        MyThread1 myThread1=new MyThread1(hasSelfPrivateNum1);        myThread1.start();        MyThread2 myThread2=new MyThread2(hasSelfPrivateNum2);        myThread2.start();    }}

运行程序后果如下:

a set over
b set over
b num= 200
a num= 100

示例是两个线程同时拜访同一个类的两个不同的实例的同一个办法,成果却是以异步的形式运行的。本示例创立两个HasSelfPrivateNum(业务对象),在零碎中产生2个锁,所以运行后果是异步的。打印的成果是先打印 b,而后再打印a.

那么从下面的运行后果来看,尽管在HasSelfPrivateNum.java中应用synchronized关键字,然而打印的程序却不是同步的,是穿插的。那为什么呈现这样的后果呢?

因为关键字synchronized获得的锁是都是对象锁,而不是把一段代码或者是办法当作锁。所以下面的事了中,哪个线程执行带有synchronized关键字的办法,哪个线程就持有该办法所属对象的锁Lock,其余线程只能出现期待状态,前提是多个线程拜访的是同一个对象。

但如果多个线程拜访多个对象,则JVM会创立多个锁。

那咱们在不批改运行类 Test.java的状况下,如何实现线程平安呢?

批改HasSelfPrivateNum.java代码如下:

public class HasSelfPrivateNum {    private static int num = 0;    public static synchronized void addI(String username) {        try {            if (("a").equals(username)) {                num = 100;                System.out.println("a set over");                Thread.sleep(2000);            } else {                num = 200;                System.out.println("b set over");            }            System.out.println(username + " num= " + num);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

运行程序后果如下:

a set over
a num= 100
b set over
b num= 200

从运行的后果来看,实现类同步的成果,下面的实例中将synchronized关键字加到非动态(static)办法上运行的成果是不一样的, 其实它们的实质是不同的,synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非动态(static)办法上是给对象上锁。

论断:

  • synchronized加在静态方法上给对象上锁。
  • synchronized加在非静态方法上给Class类上锁。

2.2 synchronized同步代码块

2.2.1 synchronized(this)同步代码快的应用

当两个并发线程拜访同一个对象objectsynchronized(this)同步代码块时,一段时间内只能一个线程被执行,另一个线程必须期待以后线程执行完这个代码块当前能力执行该代码块。

自定义线程类 MyThread1.java, MyThread2.java ,运行类 Test.java 代码不变,批改HasSelfPrivateNum.java,代码如下:

public class ObjectService {    public void serviceMethod() {        try {            synchronized (this){                System.out.println("begin time = " + System.currentTimeMillis());                Thread.sleep(2000);                System.out.println("end time= " + System.currentTimeMillis());            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

程序运行后果

a set over
a num= 100
b set over
b num= 200

上述试验执行synchronized (this)同步代码块,实现线程平安。但应用synchronized (this)须要留神的是,当一个线程拜访object的一个synchronized (this)同步代码块时,其余的线程对同一个object中所有其余synchronized (this)同步代码块的拜访将被阻塞,这样阐明synchronized (this)属于应用“对象监听器”,和synchronized关键字加到非动态(static)办法上是给对象上锁是一样的。

举一个示例验证synchronized (this)属于应用“对象监听器

HasSelfPrivateNum.java新增一个办法,那么在HasSelfPrivateNum.java 类中有两个同步的办法,别离是: addI()update() ,新增代码如下:

public void update(){    synchronized (this) {        try {            System.out.println("同步代码块update");            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

批改MyThread2.java,更改run()办法中调用类的办法,代码如下:

@Overridepublic void run() {    hasSelfPrivateNum.update();}

运行类的代码不变,程序运行的后果如下:

a set over
a num= 100
同步代码块update

2.2.2 synchronized(非this)同步代码快的应用

批改HasSelfPrivateNum.java类,应用Object 类作为对象监听器 ,代码如下:

public class HasSelfPrivateNum {    private int num = 0;        private Object object = new Object();    public void addI(String username) {        //这个对象能够是任何类         synchronized (object) {            try {                if (("a").equals(username)) {                    num = 100;                    System.out.println("a set over");                    Thread.sleep(2000);                } else {                    num = 200;                    System.out.println("b set over");                }                System.out.println(username + " num= " + num);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

其余类代码不做批改,程序运行后果如下:

a set over
a num= 100
b set over
b num= 200

锁非this对象有肯定的长处:如果一个类紫红有很多synchronized的办法,这时尽管能实现同步,但会呈现阻塞,所以会影响效率,但弱国应用同步代码块锁时非this对象,则synchronized(非this)代码块中的程序与同步办法是异步的,不与其余锁this同步办法争抢this锁,这样大大提高效率。

2.2.3 synchronized(class)代码块

其实动态同步synchronizedsynchronized(class)代码块实现的成果是一样。

上面咱们来验证synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非动态(static)办法上是给对象上锁。

创立类HasSelfPrivateNum.java,代码如下:`

public class HasSelfPrivateNum {    public synchronized static void addA() {        try {            System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入 addA() ");            Thread.sleep(2000);            System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 来到 addA() ");        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public synchronized static void addB() {        try {            System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入 addB() ");            Thread.sleep(2000);            System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 来到 addB() ");        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public synchronized void addC() {        try {            System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入 addC() ");            Thread.sleep(2000);            System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 来到 addC() ");        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

自定义线程类 MyThread1.java, MyThread2.javaMyThread3.java代码如下:

public class MyThread1 extends Thread{    @Override    public void run() {        HasSelfPrivateNum.addA();    }}public class MyThread2 extends Thread{    @Override    public void run() {        HasSelfPrivateNum.addB();    }}public class MyThread3 extends Thread{    private HasSelfPrivateNum hasSelfPrivateNum;    public MyThread3(HasSelfPrivateNum hasSelfPrivateNum){        this.hasSelfPrivateNum=hasSelfPrivateNum;    }    @Override    public void run() {        hasSelfPrivateNum.addC();    }}

创立运行类 Test.java,代码如下:

public class Test {    public static void main(String[] args) {        HasSelfPrivateNum hasSelfPrivateNum=new HasSelfPrivateNum();        MyThread1 myThread1=new MyThread1();        myThread1.start();        MyThread2 myThread2=new MyThread2();        myThread2.start();        MyThread3 myThread3=new MyThread3(hasSelfPrivateNum);        myThread3.start();    }}

程序运行后果:

线程的名称: Thread-0 在 1618653226240 进入 addA()
线程的名称: Thread-2 在 1618653226241 进入 addC()
线程的名称: Thread-2 在 1618653228245 来到 addC()
线程的名称: Thread-0 在 1618653228245 来到 addA()
线程的名称: Thread-1 在 1618653228246 进入 addB()
线程的名称: Thread-1 在 1618653230247 来到 addB()

呈现异步的起因是静态方法 addA()addB() 持有一个Class锁,非动态addC()持有一个对象锁,而Class锁能够对类的所有实例起作用。

上面咱们验证一下动态同步synchronizedsynchronized(class)代码块实现的成果是一样

批改上述HasSelfPrivateNum.java,addC()办法的代码,其余类不做批改:`

public  void addC() {        synchronized (HasSelfPrivateNum.class){            try {                System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入 addC() ");                Thread.sleep(2000);                System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 来到 addC() ");            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }

程序运行后果:

线程的名称: Thread-0 在 1618653359853 进入 addA()
线程的名称: Thread-0 在 1618653361857 来到 addA()
线程的名称: Thread-2 在 1618653361857 进入 addC()
线程的名称: Thread-2 在 1618653363859 来到 addC()
线程的名称: Thread-1 在 1618653363859 进入 addB()
线程的名称: Thread-1 在 1618653365863 来到 addB()

从程序的运行后果来看,同步synchronized(class)代码块的作用其实和synchronized**的静态方法是一样。

总结:

synchronized同步办法: 动态同步办法非动态同步办法

synchronized同步代码块 :synchronized(this),synchronized(非this),synchronized(class)


看到这里明天的分享就完结了,如果感觉这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

欢送关注集体公众号 「JavaClub」,定期为你分享一些技术干货。