Java多线程笔记(一)

synchronized(this)、synchronized(Object)、synchronized(*.class)以及synchronized应用在静态方法上的区别

Posted by LANY on November 5, 2019

背景

我们都知道在多线程的情况下,在代码中使用synchronized可以实现同步功能,但是仔细留意下,会发现synchronized括号内的值有this,也有*.class,还有的会将synchronized应用在方法上,我们今天来针对这几种类型的同步方法来研究下到底有什么不同。

synchronized(this)或者synchronized应用在非静态方法上时

当我们使用以上两种同步方法时,我们是将当前对象作为监视器。现在我们通过code来检验一下:

首先创建一个服务类,这个类中定义两个常用方法,一个是将synchronized应用在非静态方法上,另一个将synchronized应用在代码块上。


/**
 * Created by lany on 2019/11/4.
 */
public class ObjectServiceTwo {

    /**
     * 当加上synchronized关键字后,该方法在多线程中被执行时会被堵塞,即同步执行。
     * 反之,会异步执行
     */
//    public synchronized void objectServiceA(){
    public synchronized void objectServiceA(){
        System.out.println("run ----- objectServiceA");
    }

    public void objectServiceB(){
        synchronized (this){
            try {
                for(int i=0;i<10;i++){
                    System.out.println("synchronized thread name:"+Thread.currentThread().getName()+"====i=>"+i);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

分别为上述类中的两个方法创建两个继承了Thread的类,ThreadAThreadB。这两个类没什么特别的,就是分别调用了ObjectServiceTwo类中的两个方法,具体代码如下:



/**
 * Created by lany on 2019/11/4.
 */
public class ThreadA extends Thread {

    private ObjectServiceTwo objectServiceTwo ;

    public ThreadA(ObjectServiceTwo objectServiceTwo){
        super();
        this.objectServiceTwo = objectServiceTwo;
    }

    @Override
    public void run() {
        super.run();
        this.objectServiceTwo.objectServiceA();
    }
}

/**
 * Created by lany on 2019/11/4.
 */
public class ThreadB extends Thread {

    private ObjectServiceTwo objectServiceTwo ;

    public ThreadB(ObjectServiceTwo objectServiceTwo){
        super();
        this.objectServiceTwo = objectServiceTwo;
    }

    @Override
    public void run() {
        super.run();
        this.objectServiceTwo.objectServiceB();
    }
}

最后我们创建一个TestThread类用来测试两种同步方法是否有区别:

/**
 *
 * Created by lany on 2019/11/4.
 */
public class TestThread {

    public static void main(String[] args) {
        ObjectServiceTwo objectServiceTwo = new ObjectServiceTwo();
        ThreadA threadA = new ThreadA(objectServiceTwo);
        ThreadB threadB = new ThreadB(objectServiceTwo);
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(4, 10, 60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new ThreadFactory() {
                    private int count = 0;
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r,"Thread-"+(count++));
                    }
                });


        tpe.execute(threadB);
        for(int i=0;i<10;i++){
            tpe.execute(threadA);
        }
        tpe.shutdown();
    }

}

通过运行测试类,我们得到了以下结果:

synchronized thread name:Thread-0====i=>0
synchronized thread name:Thread-0====i=>1
synchronized thread name:Thread-0====i=>2
synchronized thread name:Thread-0====i=>3
synchronized thread name:Thread-0====i=>4
synchronized thread name:Thread-0====i=>5
synchronized thread name:Thread-0====i=>6
synchronized thread name:Thread-0====i=>7
synchronized thread name:Thread-0====i=>8
synchronized thread name:Thread-0====i=>9
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA

从这个结果中我们可以看到结果是根据执行的顺序同步生成的,这就证明我们同步锁已经生效。由此可以得到结果:

多个线程在调用同一个对象中不同名称synchronized方法或者synchronized(this)代码块时是同步的。 对同一个对象中其他的synchronized同步方法或者synchronized(this)代码块调用是堵塞状态。 同一时间只有一个线程执行该对象中的synchronized同步方法或者synchronized(this)代码块。

那么,在这里我们验证了synchronized方法跟synchronized(this)其实都是监控的同一个对象,现在我们继续验证这两个同步方法监控的对象都是当前对象,现在我们对TestThread类进行下列修改:

/**
 *
 * Created by lany on 2019/11/4.
 */
public class TestThread {

    public static void main(String[] args) {
        //在这里生成两个不同的ObjectServiceTwo对象,然后分别调用
        ObjectServiceTwo objectServiceTwo = new ObjectServiceTwo();
        ObjectServiceTwo objectServiceTwo1 = new ObjectServiceTwo();
        ThreadA threadA = new ThreadA(objectServiceTwo);
        ThreadB threadB = new ThreadB(objectServiceTwo1);
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(4, 10, 60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new ThreadFactory() {
                    private int count = 0;
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r,"Thread-"+(count++));
                    }
                });


        tpe.execute(threadB);
        for(int i=0;i<10;i++){
            tpe.execute(threadA);
        }
        tpe.shutdown();
    }

}

继续运行此测试类,我们可以得到如下结果:

synchronized thread name:Thread-0====i=>0
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
run ----- objectServiceA
synchronized thread name:Thread-0====i=>1
synchronized thread name:Thread-0====i=>2
synchronized thread name:Thread-0====i=>3
synchronized thread name:Thread-0====i=>4
synchronized thread name:Thread-0====i=>5
synchronized thread name:Thread-0====i=>6
synchronized thread name:Thread-0====i=>7
synchronized thread name:Thread-0====i=>8
synchronized thread name:Thread-0====i=>9

我们从上述的结果中可以看到产生的结果并没有按照我们的调用顺序来产生结果,这是因为线程是调用的两个不同的实例,而导致监控的对象不同所以导致线程的调用没有堵塞,可见synchronized方法以及synchronized(this)代码块都是将当前对象作为监控对象。

synchronized(任意对象)

synchronized(任意对象)运行我们自定义一个监控对象,可以是任意类型的对象,下面我们来通过代码来探索下这种同步方法的使用场景:

首先创建一个服务类ObjectServiceThree类,里面有一个设置账户密码的方法,然后我们创建了一个Integer类型的对象来充当一个监控对象:

/**
 * 将任意对象作为监控对象 : synchronized(任意对象)
 * Created by lany on 2019/11/4.
 */
public class ObjectServiceThree {

    private String uname;
    private String pwd;
    private int count=0;

    /**
     * 锁可以为任意对象
     *
     */
    final Integer int_lock = new Integer(1);

    public void setUsernameAndPassword(String username,String passwrod){

        try {
            synchronized (int_lock){
                System.out.println("ThreadName:"+Thread.currentThread().getName()+"- 进代码块时间: "+System.currentTimeMillis());
                Thread.sleep(1000);
                System.out.println("UserName:"+username+"--Password:"+passwrod+"Count:"+(count++));
                this.uname=username;
                this.pwd=passwrod;
                System.out.println("ThreadName:"+Thread.currentThread().getName()+"- 出代码块时间: "+System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

紧接着为上述服务类创建两个继承了Thread类的调用类,以此调用ObjectServiceThree类中的setUsernameAndPassword方法:

/**
 * Created by lany on 2019/11/4.
 */
public class ThreadA extends Thread{

    private ObjectServiceThree objectServiceThree;

    public ThreadA(ObjectServiceThree objectServiceThree){
        super();
        this.objectServiceThree=objectServiceThree;
    }

    @Override
    public void run() {
        super.run();
        this.objectServiceThree.setUsernameAndPassword("Txx","password");
    }
}

/**
 * Created by lany on 2019/11/4.
 */
public class ThreadB extends Thread{

    private ObjectServiceThree objectServiceThree;

    public ThreadB(ObjectServiceThree objectServiceThree){
        super();
        this.objectServiceThree=objectServiceThree;
    }

    @Override
    public void run() {
        super.run();
        this.objectServiceThree.setUsernameAndPassword("lany","passw0rd");

    }
}

最后我们创建一个测试类TestThread类来进行测试:

/**
 *
 * Created by lany on 2019/11/4.
 */
public class TestThread {

    public static void main(String[] args) {
        ObjectServiceThree objectServiceThree = new ObjectServiceThree();
//        ObjectServiceThree objectServiceThree1 = new ObjectServiceThree();
        ThreadA threadA = new ThreadA(objectServiceThree);
        ThreadB threadB = new ThreadB(objectServiceThree);
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(4, 10, 60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new ThreadFactory() {
                    private int count = 0;
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r,"Thread-"+(count++));
                    }
                });

        for(int j=0;j<5;j++){
            tpe.execute(threadB);
        }
        for(int i=0;i<5;i++){
            tpe.execute(threadA);
        }
        tpe.shutdown();
    }
}

通过运行上述测试,我们得到了如下的结果:

ThreadName:Thread-0- 进代码块时间: 1573357867105
UserName:lany--Password:passw0rdCount:0
ThreadName:Thread-0- 出代码块时间: 1573357868107
ThreadName:Thread-3- 进代码块时间: 1573357868108
UserName:lany--Password:passw0rdCount:1
ThreadName:Thread-3- 出代码块时间: 1573357869109
ThreadName:Thread-2- 进代码块时间: 1573357869109
UserName:lany--Password:passw0rdCount:2
ThreadName:Thread-2- 出代码块时间: 1573357870114
ThreadName:Thread-2- 进代码块时间: 1573357870114
UserName:Txx--Password:passwordCount:3
ThreadName:Thread-2- 出代码块时间: 1573357871120
ThreadName:Thread-2- 进代码块时间: 1573357871120
UserName:Txx--Password:passwordCount:4
ThreadName:Thread-2- 出代码块时间: 1573357872125
ThreadName:Thread-2- 进代码块时间: 1573357872125
UserName:Txx--Password:passwordCount:5
ThreadName:Thread-2- 出代码块时间: 1573357873130
ThreadName:Thread-2- 进代码块时间: 1573357873130
UserName:Txx--Password:passwordCount:6
ThreadName:Thread-2- 出代码块时间: 1573357874131
ThreadName:Thread-1- 进代码块时间: 1573357874132
UserName:lany--Password:passw0rdCount:7
ThreadName:Thread-1- 出代码块时间: 1573357875133
ThreadName:Thread-3- 进代码块时间: 1573357875133
UserName:Txx--Password:passwordCount:8
ThreadName:Thread-3- 出代码块时间: 1573357876138
ThreadName:Thread-0- 进代码块时间: 1573357876138
UserName:lany--Password:passw0rdCount:9
ThreadName:Thread-0- 出代码块时间: 1573357877142

从上述结果的进出代码块时间来看,每个方法都是同步执行的。那么我们可以得到如下结论:

在多个线程持有对象监视器且将同一个对象作为监控对象的情况下,同一时间只有同一个线程执行该对象synchronized(任意对象)方法中的代码块。

synchronized(任意对象)与synchronized(this)

那么,看到这里,肯定有读者会好奇,这种同步方式跟上述讲的第一种synchronized(this)或者syunchronized方法上的同步方式有什么区别呢?下面我们来通过代码进行验证:

首先创建一个服务类,这个服务类包含有两个方法,这两种方法通过不同的同步方法进行同步,具体代码如下:

/**
 * Created by lany on 2019/11/4.
 */
public class ObjectServiceFour {

    /**
     * 对象锁
     */
    private String lock = new String();

    /**
     * synchronized 同步方法
     */
    public synchronized void methodA(){
        System.out.println("StartTime--"+System.currentTimeMillis());
        System.out.println("ThreadName:"+Thread.currentThread().getName()+"execute MethodA");
        System.out.println("EndTime--"+System.currentTimeMillis());
    }

    /**
     * synchronized(任意对象同步代码块)
     */
    public void methodB(){
        synchronized (lock){
            System.out.println("StartTime--"+System.currentTimeMillis());
            System.out.println("ThreadName:"+Thread.currentThread().getName()+"execute MethodB");
            System.out.println("EndTime--"+System.currentTimeMillis());
        }
    }
}

然后为上述服务类创建两个继承了Thread的调用类,用来分别调用上述服务类中的两个方法:

/**
 * Created by lany on 2019/11/4.
 */
public class ThreadA extends Thread {

    private ObjectServiceFour objectServiceFour;

    public ThreadA(ObjectServiceFour objectServiceFour){
        super();
        this.objectServiceFour=objectServiceFour;
    }

    @Override
    public void run() {
        super.run();
        this.objectServiceFour.methodA();
    }
}

/**
 * Created by lany on 2019/11/4.
 */
public class ThreadB extends Thread {

    private ObjectServiceFour objectServiceFour;

    public ThreadB(ObjectServiceFour objectServiceFour){
        super();
        this.objectServiceFour=objectServiceFour;
    }

    @Override
    public void run() {
        super.run();
        this.objectServiceFour.methodB();
    }
}

最后我们创建一个测试类TestThread类,用来测试:

/**
 * Created by lany on 2019/11/4.
 */
public class TestThread {

    public static void main(String[] args) {
        ObjectServiceFour objectServiceFour = new ObjectServiceFour();
        ThreadA threadA = new ThreadA(objectServiceFour);
        ThreadB threadB = new ThreadB(objectServiceFour);

        ThreadPoolExecutor tpe = new ThreadPoolExecutor(4, 10, 60L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(10),
                new ThreadFactory() {
                    private int count = 0;
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r,"ThreadNum"+(count++));
                    }
                });
//        for(int i=0;i<5;i++){
//            tpe.execute(threadA);
//        }
        tpe.execute(threadA);
        tpe.execute(threadB);
        tpe.execute(threadA);
        tpe.execute(threadB);
        tpe.execute(threadB);
        tpe.shutdown();
    }
}

运行上述代码,我们得到了如下结果:

StartTime--1573604510859
StartTime--1573604510859
ThreadName:ThreadNum0execute MethodA
ThreadName:ThreadNum1execute MethodB
EndTime--1573604510859
EndTime--1573604510859
StartTime--1573604510859
ThreadName:ThreadNum2execute MethodA
StartTime--1573604510859
ThreadName:ThreadNum3execute MethodB
EndTime--1573604510859
EndTime--1573604510859
StartTime--1573604510859
ThreadName:ThreadNum0execute MethodB
EndTime--1573604510859

根绝结果我们可以发现,调用同一个对象中的不同方法是异步执行的,这是因为两个方法上锁的对象不一样,一个是当前对象,另一个是一个任意对象。锁不同,所以执行的时候不会同步。由此我们可以得到如下结论:

如果一个对象中有synchronized(this)包含的代码块,也有synchronized(任意对象)的代码块,那么在多个线程中执行这些代码块时,因为对象监控器不是同一个,所以代码会异步执行。

synchronized应用在静态方法上

我们在前几小节讲述了synchronized应用在非静态方法中,在这个小节我们探讨当synchronized应用在静态方法上,现在我们通过代码来进行一个验证:

首先创建一个服务类,该服务类包含了两个被synchronized的静态同步方法,代码如下:

/**
 * 探讨静态同步synchronized方法
 * Created by lany on 2019/11/4.
 */
public class ObjectServiceFive {

    public synchronized static void methodA(){
            System.out.println("MethodA---ThreadName:"+Thread.currentThread().getName()+"--BeginTime:"+System.currentTimeMillis());
            System.out.println("MethodA---ThreadName:"+Thread.currentThread().getName()+"--EndTime:"+System.currentTimeMillis());
    }

    public synchronized static void methodB(){
        System.out.println("MethodB---ThreadName:"+Thread.currentThread().getName()+"--BeginTime:"+System.currentTimeMillis());
        System.out.println("MethodB---ThreadName:"+Thread.currentThread().getName()+"--EndTime:"+System.currentTimeMillis());
    }
}

然后创建两个继承了Thread的调用来,代码如下:

/**
 * Created by lany on 2019/11/4.
 */
public class ThreadA extends Thread {


    @Override
    public void run() {
        super.run();
        ObjectServiceFive.methodA();
    }
}
/**
 * Created by lany on 2019/11/4.
 */
public class ThreadB extends Thread {

    @Override
    public void run() {
        super.run();
        ObjectServiceFive.methodB();
    }
}

最后创建一个测试类TestThread用来对服务类进行测试:

/**
 * Created by lany on 2019/11/4.
 */
public class TestThread {
    public static void main(String[] args) {
//        ObjectServiceFive objectServiceFive = new ObjectServiceFive();
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();

        ThreadPoolExecutor tpe = new ThreadPoolExecutor(4, 10, 60L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(10),
                new ThreadFactory() {
                    private int count = 0;
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r,"ThreadNum"+(count++));
                    }
                });

        tpe.execute(threadA);
        tpe.execute(threadA);
        tpe.execute(threadB);
        tpe.execute(threadB);
        tpe.execute(threadA);
        tpe.execute(threadA);
        tpe.shutdown();
    }
}

通过运行上面的代码,我们得到了如下结果:

MethodA---ThreadName:ThreadNum0--BeginTime:1573606688909
MethodA---ThreadName:ThreadNum0--EndTime:1573606688909
MethodB---ThreadName:ThreadNum3--BeginTime:1573606688910
MethodB---ThreadName:ThreadNum3--EndTime:1573606688910
MethodA---ThreadName:ThreadNum1--BeginTime:1573606688910
MethodA---ThreadName:ThreadNum1--EndTime:1573606688910
MethodB---ThreadName:ThreadNum2--BeginTime:1573606688910
MethodB---ThreadName:ThreadNum2--EndTime:1573606688910
MethodA---ThreadName:ThreadNum3--BeginTime:1573606688910
MethodA---ThreadName:ThreadNum3--EndTime:1573606688910
MethodA---ThreadName:ThreadNum0--BeginTime:1573606688910
MethodA---ThreadName:ThreadNum0--EndTime:1573606688910

我们从上述结果中看到,所有的方法是同步执行的,那么这就说明了对象监控器是同一个-*.class(即ObjectServiceFive.class)。我们从该执行代码中可以得到如下结论:

当synchronized在静态方法上时,此时锁住的是*.class

synchronized(*.class)

除了将synchronized应用在静态方法上,我们有时候还见到过将synchronized应用在代码块,只是稍微不同的是synchronized的监控对象为*.class。下面还是通过代码来验证这两种同步方式的区别:

创建一个服务类,里面包含了两个方法,其方法中的代码分别用synchronized包含:

/**
 * Created by lany on 2019/11/4.
 */
public class ObjectServiceSix {

    public void methodA(){

        synchronized (ObjectServiceSix.class) {
//        synchronized (this) {
            try {
            System.out.println("MethodA---ThreadName:"+Thread.currentThread().getName()+"--BeginTime:"+System.currentTimeMillis());
                Thread.sleep(2000);
            System.out.println("MethodA---ThreadName:"+Thread.currentThread().getName()+"--EndTime:"+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void methodB(){
        synchronized (ObjectServiceSix.class) {
//        synchronized (this) {
            System.out.println("MethodB---ThreadName:"+Thread.currentThread().getName()+"--BeginTime:"+System.currentTimeMillis());
            System.out.println("MethodB---ThreadName:"+Thread.currentThread().getName()+"--EndTime:"+System.currentTimeMillis());
        }
    }

}

然后创建两个调用类用来调用服务类中的方法:

/**
 * Created by lany on 2019/11/4.
 */
public class ThreadA extends Thread {

    private ObjectServiceSix objectServiceSix;

    public ThreadA(ObjectServiceSix objectServiceSix){
        this.objectServiceSix=objectServiceSix;
    }

    @Override
    public void run() {
        super.run();
        this.objectServiceSix.methodA();
    }
}

/**
 * Created by lany on 2019/11/4.
 */
public class ThreadB extends Thread {

    private ObjectServiceSix objectServiceSix;

    public ThreadB(ObjectServiceSix objectServiceSix){
        this.objectServiceSix=objectServiceSix;
    }

    @Override
    public void run() {
        super.run();
        this.objectServiceSix.methodB();
    }
}

然后创建一个TestThread测试类来进行测试:

/**
 * Created by lany on 2019/11/4.
 */
public class TestThread {

    public static void main(String[] args) {
        ObjectServiceSix objectServiceSix = new ObjectServiceSix();
        ObjectServiceSix objectServiceSix1 = new ObjectServiceSix();
        ThreadA threadA = new ThreadA(objectServiceSix);
        ThreadB threadB = new ThreadB(objectServiceSix1);
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(4, 10, 60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new ThreadFactory() {
                    private int count = 0;
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r,"Thread-"+(count++));
                    }
                });

        tpe.execute(threadA);
        tpe.execute(threadA);
        tpe.execute(threadB);
        tpe.execute(threadB);
        tpe.execute(threadA);

        tpe.shutdown();
    }
}

通过运行上面的代码,我们可以得到如下结果:

MethodA---ThreadName:Thread-0--BeginTime:1573607552272
MethodA---ThreadName:Thread-0--EndTime:1573607554273
MethodB---ThreadName:Thread-3--BeginTime:1573607554274
MethodB---ThreadName:Thread-3--EndTime:1573607554274
MethodB---ThreadName:Thread-2--BeginTime:1573607554274
MethodB---ThreadName:Thread-2--EndTime:1573607554274
MethodA---ThreadName:Thread-1--BeginTime:1573607554274
MethodA---ThreadName:Thread-1--EndTime:1573607556275
MethodA---ThreadName:Thread-0--BeginTime:1573607556276
MethodA---ThreadName:Thread-0--EndTime:1573607558281

从上述代码我们可以看到,执行的方法都是同步执行,由此我们可以验证出当synchronized应用在静态方法上的时候,其监控对象为*.class,(当把监控对象换成this之后,你会发现方法是异步执行)由此我们可以得到如下结论:

使用synchronized(*.class)包含代码块时,同步效果与synchronized在静态方法上时产生的效果一样,其监控对象都是*.class

Class锁对类的所有对象实例中被synchronized(*.class)包含的代码块以及synchronized应用在静态方法上时起作用。