对象及变量的并发访问(同步方法、同步代码块、对class进行加锁、线程死锁)&内部类的基本用法

  主要学习多线程的并发访问,也就是使得线程安全。

  同步的单词为synchronized,异步的单词为asynchronized

  同步主要就是通过锁的方式实现,一种就是隐式锁,另一种是显示锁Lock,本节主要研究隐式锁synchronized的使用。

  每个Java对象都可以用作一个实现同步的锁,注意前提是Java对象,也就是类型必须是引用类型,这些锁被称为内置锁(Instrinsic Lock)或者监视器锁(Monitor Lock),是一种互斥锁。

1. synchronized同步方法

  “非线程安全"其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果是"脏读",也就是取到的数据其实被更改过的。而"线程安全"就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。

  "非线程安全"的问题存在于实例变量中,如果是方法内部中的局部变量,不存在"非线程安全"的问题。(线程内部的变量线程不会线程共享)

 

1.1如果多个线程同时访问一个对象的实例变量,则有可能出现"非线程安全"问题。

如下:

package cn.qlq.thread.four;

public class Demo1 extends Thread {
    private int i;// 默认值为0

    @Override
    public void run() {
        System.out.println("i=" + (i++) + ",threadName=" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Demo1 run = new Demo1();
        // 开启十个线程进行计算
        for (int i = 0; i < 10; i++) {
            Thread t1 = new Thread(run);
            t1.start();
        }
    }
}

结果:(产生非现场安全问题)

i=0,threadName=Thread-2
i=0,threadName=Thread-1
i=1,threadName=Thread-3
i=2,threadName=Thread-4
i=3,threadName=Thread-5
i=4,threadName=Thread-6
i=5,threadName=Thread-7
i=6,threadName=Thread-8
i=7,threadName=Thread-9
i=8,threadName=Thread-10

 

解决办法:

(1)run方法加synchronized关键字

@Override
    public synchronized void run() {
        System.out.println("i=" + (i++) + ",threadName=" + Thread.currentThread().getName());
    }

 

(2)使用并发包的原子变量

package cn.qlq.thread.one;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadSafe extends Thread {

    private AtomicInteger i = new AtomicInteger(0);// 默认值为0

    @Override
    public void run() {
        System.out.println("i=" + (i.getAndIncrement()) + ",threadName=" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadSafe run = new ThreadSafe();
        // 开启十个线程进行计算
        for (int i = 0; i < 10; i++) {
            Thread t1 = new Thread(run);
            t1.start();
        }
    }
}

 

1.2多个对象多把锁

  关键字synchronized取得的锁是对象锁,而不是把一段代码或者方法当作锁。如果多个线程访问多个对象,则JVM会创建多把锁。

package cn.qlq.thread.four;

public class Demo2 extends Thread {
    private SyncObj1 syncObj1;

    public Demo2(SyncObj1 syncObj1) {
        this.syncObj1 = syncObj1;
    }

    @Override
    public void run() {
        syncObj1.syncMethod();
    }

    public static void main(String[] args) {
        Demo2 demo2 = new Demo2(new SyncObj1());
        Demo2 demo3 = new Demo2(new SyncObj1());
        demo2.start();
        demo3.start();
    }
}

class SyncObj1 {
    public synchronized void syncMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println("threadName->" + Thread.currentThread().getName());
        }
    }
}

结果:(线程交替打印则说明是异步执行,也就说明了多个对象多把锁)

threadName->Thread-0
threadName->Thread-1
threadName->Thread-0
threadName->Thread-1
threadName->Thread-0
threadName->Thread-1
threadName->Thread-0
threadName->Thread-1
threadName->Thread-0
threadName->Thread-1

 

1.3 synchronized方法与锁对象

 A线程持有Object的Lock锁,B线程可以以异步的方式调用object中的非synchronized方法

A线程持有Object的Lock锁,B线程如果在这时调用object中的synchronized方法,则需要等待锁,也就是同步。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo3 extends Thread {

    public static void main(String[] args) {
        final SyncObj2 syncObj2 = new SyncObj2();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj2.syncMethod();
            }
        }, "A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj2.asyncMethod();
            }
        }, "B").start();
    }
}

class SyncObj2 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj2.class);

    public synchronized void syncMethod() {
        LOGGER.debug("SyncObj2 syncMethod start!!!" + Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.debug("SyncObj2 syncMethod end!!!" + Thread.currentThread().getName());
    }

    public void asyncMethod() {
        LOGGER.debug("SyncObj2 asyncMethod start!!!" + Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.debug("SyncObj2 asyncMethod end!!!" + Thread.currentThread().getName());
    }
}

结果:

18:11:59 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 syncMethod start!!!A
18:11:59 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 asyncMethod start!!!B
18:12:04 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 syncMethod end!!!A
18:12:04 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 asyncMethod end!!!B

 

将上面SyncObj2的asyncMethod方法变为同步方法再次查看结果:(变为同步执行)

    public synchronized void asyncMethod() {
        LOGGER.debug("SyncObj2 asyncMethod start!!!" + Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.debug("SyncObj2 asyncMethod end!!!" + Thread.currentThread().getName());
    }

结果:

18:13:00 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 syncMethod start!!!A
18:13:05 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 syncMethod end!!!A
18:13:05 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 asyncMethod start!!!B
18:13:10 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 asyncMethod end!!!B

 

总结:

  当A线程调用obj的加入synchronized关键字的X方法时,A线程就获得了obj的对象锁(每个对象一把锁),所以其他线程必须等待A线程执行完才可以获得obj的对象锁,执行其他synchronized修饰的方法,但是可以随意调用其他非synchronized方法。(synchrinized修饰的方法相当于在对象上加了同步锁)

  

1.4 synchronized锁重入

  synchronized关键字具有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求对象锁时是可以再次得到该对象的对象锁的。这也证明在一个synchronized方法/块内部调用本类的其他synchronized方法/代码块时,是永远可以得到锁的。(实际想一下也可以明白,如果得不到会造成一直等待锁--死锁)

  重入的一种实现方法就是为每个锁关联一个获取一个计数值和一个所有者线程。当计数值为0时代表这个锁没有被任何线程占用。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值设置为1,如果同一个线程再次获取这个锁,计数器将递增,当线程退出代码块时,计数值会递减。当计数值为0的时候,这个锁被释放。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo4 extends Thread {

    public static void main(String[] args) {
        final SyncObj3 syncObj2 = new SyncObj3();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj2.syncMethod1();
            }
        }, "A").start();
    }
}

class SyncObj3 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj2.class);

    public synchronized void syncMethod1() {
        LOGGER.debug("SyncObj2 syncMethod1 start!!!" + Thread.currentThread().getName());
        syncMethod2();
    }

    public synchronized void syncMethod2() {
        LOGGER.debug("SyncObj2 syncMethod2 start!!!" + Thread.currentThread().getName());
    }

}

结果:

18:58:32 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 syncMethod1 start!!!A
18:58:32 [cn.qlq.thread.four.SyncObj2]-[DEBUG] SyncObj2 syncMethod2 start!!!A

 

  "可重入锁"的概念就是自己可以再次获取自己的内部锁,比如有个线程获取了某个对象的锁,此时这个对象锁还没有释放,当其再次获取这个对象的锁的时候还是可以获取的,如果锁不可重入的话会造成死锁。

 

"可重入锁"也支持在父子类继承的环境中。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo5 extends Thread {

    public static void main(String[] args) {
        final SyncObj5 syncObj5 = new SyncObj5();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj5.syncMethod1();
            }
        }, "A").start();
    }
}

class SyncObj4 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj4.class);

    public synchronized void syncMethod2() {
        LOGGER.debug("SyncObj2 syncMethod2 start!!!" + Thread.currentThread().getName());
    }

}

class SyncObj5 extends SyncObj4 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj5.class);

    public synchronized void syncMethod1() {
        LOGGER.debug("SyncObj2 syncMethod2 start!!!" + Thread.currentThread().getName());
        syncMethod2();
    }

}

结果:

19:06:06 [cn.qlq.thread.four.SyncObj5]-[DEBUG] SyncObj2 syncMethod2 start!!!A
19:06:06 [cn.qlq.thread.four.SyncObj4]-[DEBUG] SyncObj2 syncMethod2 start!!!A

解释:当存在父子关系继承时,子类是完全可以通过"可重入锁"调用父类的同步方法。

 

1.5 出现异常,锁自动释放

  当一个线程执行的代码出现异常时,其持有的锁会自动释放。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo6 extends Thread {

    public static void main(String[] args) {
        final SyncObj6 syncObj6 = new SyncObj6();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj6.syncMethod2();
            }
        }, "A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj6.syncMethod2();
            }
        }, "B").start();
    }
}

class SyncObj6 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj6.class);

    public synchronized void syncMethod2() {
        if ("A".equals(Thread.currentThread().getName())) {
            LOGGER.debug("syncMethod2 ,threadName->{}", Thread.currentThread().getName());
            int i = 1 / 0;
        } else {
            LOGGER.debug("syncMethod2 ,threadName->{}", Thread.currentThread().getName());
        }
        LOGGER.debug("syncMethod2 end ,threadName->{}", Thread.currentThread().getName());
    }

}

结果: (线程A出现异常自动释放锁,线程B正常进入方法,证明出现异常的锁被自动释放了)

19:13:16 [cn.qlq.thread.four.SyncObj6]-[DEBUG] syncMethod2 ,threadName->A
Exception in thread "A" 19:13:16 [cn.qlq.thread.four.SyncObj6]-[DEBUG] syncMethod2 ,threadName->B
19:13:16 [cn.qlq.thread.four.SyncObj6]-[DEBUG] syncMethod2 end ,threadName->B
java.lang.ArithmeticException: / by zero
at cn.qlq.thread.four.SyncObj6.syncMethod2(Demo6.java:32)
at cn.qlq.thread.four.Demo6$1.run(Demo6.java:13)
at java.lang.Thread.run(Thread.java:745)

 

1.6 同步不具有继承性

  同步不可以继承。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo7 extends Thread {

    public static void main(String[] args) {
        final SyncObj8 syncObj8 = new SyncObj8();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj8.syncMethod2();
            }
        }, "A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj8.syncMethod2();
            }
        }, "B").start();
    }
}

class SyncObj7 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj7.class);

    public synchronized void syncMethod2() {
        LOGGER.debug("SyncObj7 syncMethod2,threadName->{}", Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class SyncObj8 extends SyncObj7 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj8.class);

    @Override
    public void syncMethod2() {
        LOGGER.debug("SyncObj8 syncMethod2,threadName->{}", Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
            super.syncMethod2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:(子类的方法没有同步,虽然子类在调用父类方法时实现了同步)

19:20:21 [cn.qlq.thread.four.SyncObj8]-[DEBUG] SyncObj8 syncMethod2,threadName->A
19:20:21 [cn.qlq.thread.four.SyncObj8]-[DEBUG] SyncObj8 syncMethod2,threadName->B
19:20:26 [cn.qlq.thread.four.SyncObj7]-[DEBUG] SyncObj7 syncMethod2,threadName->A
19:20:31 [cn.qlq.thread.four.SyncObj7]-[DEBUG] SyncObj7 syncMethod2,threadName->B

 

如果需要同步,需要在子类方法加同步关键字:

class SyncObj8 extends SyncObj7 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj8.class);

    @Override
    synchronized public void syncMethod2() {
        LOGGER.debug("SyncObj8 syncMethod2,threadName->{}", Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
            super.syncMethod2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

19:22:02 [cn.qlq.thread.four.SyncObj8]-[DEBUG] SyncObj8 syncMethod2,threadName->A
19:22:07 [cn.qlq.thread.four.SyncObj7]-[DEBUG] SyncObj7 syncMethod2,threadName->A
19:22:12 [cn.qlq.thread.four.SyncObj8]-[DEBUG] SyncObj8 syncMethod2,threadName->B
19:22:17 [cn.qlq.thread.four.SyncObj7]-[DEBUG] SyncObj7 syncMethod2,threadName->B

 

2. synchronized同步代码块

  用synchronized来同步方法是有弊端的,比如A线程执行一个时间较长的任务,那么B线程则必须等待较长的时间。在这样的情况下可以用synchronized同步语句块来解决。

  也就是说不一定整个方法都需要同步,但是synchronized会使得整个方法进行同步,有时候只需要同步代码块即可。而且同步整个方法有可能整个方法是死循环,造成其他线程得不到锁而锁死。

2.1 同步代码块的使用

  使用synchronized同步代码块,实现半异步半同步的方式执行代码。不在synchronized代码快中的是异步,在synchronized中的是同步。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo8 extends Thread {

    public static void main(String[] args) {
        final SyncObj9 syncObj9 = new SyncObj9();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj9.syncMethod2();
            }
        }, "A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj9.syncMethod2();
            }
        }, "B").start();
    }
}

class SyncObj9 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj9.class);

    public void syncMethod2() {
        LOGGER.debug("SyncObj9 syncMethod2 start,threadName->{}", Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
            synchronized (this) {
                LOGGER.debug("进入同步代码块,准备睡眠!threadName->{}", Thread.currentThread().getName());
                Thread.sleep(5 * 1000);
                LOGGER.debug("结束同步代码块,结束睡眠!,threadName->{}", Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.debug("SyncObj9 syncMethod2 end,threadName->{}", Thread.currentThread().getName());
    }
}

结果:

21:37:22 [cn.qlq.thread.four.SyncObj9]-[DEBUG] SyncObj9 syncMethod2 start,threadName->A
21:37:22 [cn.qlq.thread.four.SyncObj9]-[DEBUG] SyncObj9 syncMethod2 start,threadName->B
21:37:27 [cn.qlq.thread.four.SyncObj9]-[DEBUG] 进入同步代码块,准备睡眠!threadName->A
21:37:32 [cn.qlq.thread.four.SyncObj9]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->A
21:37:32 [cn.qlq.thread.four.SyncObj9]-[DEBUG] 进入同步代码块,准备睡眠!threadName->B
21:37:32 [cn.qlq.thread.four.SyncObj9]-[DEBUG] SyncObj9 syncMethod2 end,threadName->A
21:37:37 [cn.qlq.thread.four.SyncObj9]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->B
21:37:37 [cn.qlq.thread.four.SyncObj9]-[DEBUG] SyncObj9 syncMethod2 end,threadName->B

 

与synchronized同步方法一样,synchronized(this)也是锁定当前对象,而且占有锁之后其他访问此对象的synchronized代码块或者synchronized方法的时候也会进行锁等待,但是刻意随意访问此对象的非synchrinozed方法。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo9 extends Thread {

    public static void main(String[] args) throws InterruptedException {
        final SyncObj10 syncObj10 = new SyncObj10();
        // 访问同步代码块占用锁
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj10.syncMethod1();
            }
        }, "A").start();
        Thread.sleep(1 * 1000);
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj10.syncMethod2();
            }
        }, "B").start();
        Thread.sleep(1 * 1000);
        // 访问普通方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj10.syncMethod4();
            }
        }, "C").start();
    }
}

class SyncObj10 {
    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj10.class);

    /**
     * 同步代码块
     */
    public void syncMethod1() {
        try {
            synchronized (this) {
                LOGGER.debug("进入同步代码块,准备睡眠!threadName->{}", Thread.currentThread().getName());
                syncMethod3();
                Thread.sleep(5 * 1000);
                LOGGER.debug("结束同步代码块,结束睡眠!,threadName->{}", Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 同步方法
     */
    public synchronized void syncMethod2() {
        try {
            LOGGER.debug("进入同步方法syncMethod2,准备睡眠!threadName->{}", Thread.currentThread().getName());
            syncMethod3();
            Thread.sleep(5 * 1000);
            LOGGER.debug("结束同步方法syncMethod2,结束睡眠!,threadName->{}", Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 同步方法
     */
    public synchronized void syncMethod3() {
        LOGGER.debug("同步方法syncMethod3,threadName->{}", Thread.currentThread().getName());
    }

    public void syncMethod4() {
        LOGGER.debug("普通方法syncMethod4,threadName->{}", Thread.currentThread().getName());
    }
}

结果:(根据打印日志的时间也可以确定线程的同步性)

21:49:20 [cn.qlq.thread.four.SyncObj10]-[DEBUG] 进入同步代码块,准备睡眠!threadName->A
21:49:20 [cn.qlq.thread.four.SyncObj10]-[DEBUG] 同步方法syncMethod3,threadName->A
21:49:22 [cn.qlq.thread.four.SyncObj10]-[DEBUG] 普通方法syncMethod4,threadName->C
21:49:25 [cn.qlq.thread.four.SyncObj10]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->A
21:49:25 [cn.qlq.thread.four.SyncObj10]-[DEBUG] 进入同步方法syncMethod2,准备睡眠!threadName->B
21:49:25 [cn.qlq.thread.four.SyncObj10]-[DEBUG] 同步方法syncMethod3,threadName->B
21:49:30 [cn.qlq.thread.four.SyncObj10]-[DEBUG] 结束同步方法syncMethod2,结束睡眠!,threadName->B

 

2.2 将任意对象作为对象监视器(引用类型方法参数也可以加锁)

  多个线程调用同一个对象的不同名称的synchronized方法或者synchronized(this)代码块的时候,调用的结果就是按顺序执行,也就是同步的,阻塞的。

  这说明synchronized同步方法或者synchronized(this)代码块中两种作用。

(1)synchronized同步方法

  对其他同步方法或者synchronized(this)同步代码块呈阻塞状态

  同一时间只有一个线程可以执行synchronized同步方法中的代码。

(2)synchronized(this)同步代码块

    对其他同步方法或者synchronized(this)同步代码块呈阻塞状态

  同一时间只有一个线程可以执行synchronized(this)同步方法中的代码块。

 

  除了可以使用synchronized(this)作为同步代码块之外,java还支持对任意对象作为对象监视器来实现同步功能。这个任意对象大多是实例变量以及方法的参数(基本类型是无法加同步的,只能是引用类型,数组类型也是引用类型),使用的格式为synchronized(非this对象)。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo10 extends Thread {

    public static void main(String[] args) throws InterruptedException {
        // 同步对象
        final SyncObj11 syncObj11 = new SyncObj11();
        // 访问同步代码块占用锁
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj11.syncMethod1(1);
            }
        }, "A").start();
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj11.syncMethod1(2);
            }
        }, "B").start();
    }
}

class SyncObj11 {
    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj11.class);

    /**
     * 同步代码块
     */
    public void syncMethod1(Integer x) {
        try {
            synchronized (x) {
                LOGGER.debug("进入同步代码块,准备睡眠!threadName->{}", Thread.currentThread().getName());
                Thread.sleep(5 * 1000);
                LOGGER.debug("结束同步代码块,结束睡眠!,threadName->{}", Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:(可以看出来是异步执行,没有实现同步效果,原因是我们传的参数是两个对象,所有是两把锁)

22:02:43 [cn.qlq.thread.four.SyncObj11]-[DEBUG] 进入同步代码块,准备睡眠!threadName->A
22:02:43 [cn.qlq.thread.four.SyncObj11]-[DEBUG] 进入同步代码块,准备睡眠!threadName->B
22:02:48 [cn.qlq.thread.four.SyncObj11]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->A
22:02:48 [cn.qlq.thread.four.SyncObj11]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->B

 

为了实现同步效果,我们需要对参数的引用类型也传递同一个对象,其实我们只要明白同一个对象是一把锁,只有引用类型有锁就可以了。

如下代码对Integer进行加锁:(调用两个对象的synchronized同步代码块,但是同步的参数是同一个对象,所以仍然具有同步效果)

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo10 extends Thread {

    public static void main(String[] args) throws InterruptedException {
        // 同步对象
        final SyncObj11 syncObj11 = new SyncObj11();
        final SyncObj11 syncObj12 = new SyncObj11();
        final Integer integer = 1;
        // 访问同步代码块占用锁
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj11.syncMethod1(integer);
            }
        }, "A").start();
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj12.syncMethod1(integer);
            }
        }, "B").start();
    }
}

class SyncObj11 {
    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj11.class);

    /**
     * 同步代码块
     */
    public void syncMethod1(Integer x) {
        try {
            synchronized (x) {
                LOGGER.debug("进入同步代码块,准备睡眠!threadName->{}", Thread.currentThread().getName());
                Thread.sleep(5 * 1000);
                LOGGER.debug("结束同步代码块,结束睡眠!,threadName->{}", Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

22:05:54 [cn.qlq.thread.four.SyncObj11]-[DEBUG] 进入同步代码块,准备睡眠!threadName->A
22:05:59 [cn.qlq.thread.four.SyncObj11]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->A
22:05:59 [cn.qlq.thread.four.SyncObj11]-[DEBUG] 进入同步代码块,准备睡眠!threadName->B
22:06:04 [cn.qlq.thread.four.SyncObj11]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->B

 

总结:synchronized(非this对象)格式的写法是将x对象本身作为"对象监视器",这样就可以得出以下3个结论:

(1)当多个线程同时执行synchronized(x){}时呈现同步效果

(2)当其他线程访问x对象的synchronized方法的时候呈现同步效果。

(3)当其他线程访问x对象的synchronized(this){}代码块的时候也会呈现同步效果。

  也就是上面说的只有引用类型可以加同步,而且一个对象只有一把锁,持有锁的线程可以执行synchronized方法或者代码块,而且有锁重入功能(持有锁的可以调用其他synchronized方法或者synchronized(obj)代码块)。

 

2.3  静态同步synchronized方法与synchronized(class)代码块---相当于是对对应类的class字节码对象加锁,所有对象共享一把锁

  关键字synchronized还可以应用在static静态方法上,如果这样写就是对当前的 *.java对应的Class类进行持锁。

  与非静态方法的区别是,静态方法加synchronized是给Class对象上锁(也就是该类的所有对象共享一把锁),而synchronized关键字加到非static静态方法是给对象上锁。

  synchronized(class)的作用效果和static静态方法加synchronized的效果是一样的,该class的所有对象共享一把锁。   

 

如下代码:虽然是两个对象,但是用的是一把锁,直接访问静态方法也会同步

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.swing.internal.plaf.synth.resources.synth;

public class Demo11 extends Thread {

    public static void main(String[] args) throws InterruptedException {
        // 同步对象
        final SyncObj12 syncObj11 = new SyncObj12();
        final SyncObj12 syncObj12 = new SyncObj12();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj11.syncMethod1();
            }
        }, "A").start();
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj12.syncMethod1();
            }
        }, "B").start();
        // 访问静态方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                SyncObj12.syncMethod1();
            }
        }, "C").start();
    }
}

class SyncObj12 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj12.class);

    /**
     * 同步代码块
     */
    public static synchronized void syncMethod1() {
        LOGGER.debug("进入同步代码块,准备睡眠!threadName->{}", Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.debug("结束同步代码块,结束睡眠!,threadName->{}", Thread.currentThread().getName());
    }
}

结果:(虽然是两个对象,但是也实现了同步效果,两个对象共享同一把锁,直接访问类的静态方法也会实现同步)

11:02:56 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 进入同步代码块,准备睡眠!threadName->A
11:03:01 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->A
11:03:01 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 进入同步代码块,准备睡眠!threadName->C
11:03:06 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->C
11:03:06 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 进入同步代码块,准备睡眠!threadName->B
11:03:11 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->B

 

与上面等价的同步代码块的代码:

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo12 extends Thread {

    public static void main(String[] args) throws InterruptedException {
        // 同步对象
        final SyncObj13 syncObj131 = new SyncObj13();
        final SyncObj13 syncObj132 = new SyncObj13();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj131.syncMethod1();
            }
        }, "A").start();
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj132.syncMethod1();
            }
        }, "B").start();
        // 访问静态方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                SyncObj13.syncMethod1();
            }
        }, "C").start();
    }
}

class SyncObj13 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj13.class);

    /**
     * 同步代码块
     */
    public static void syncMethod1() {
        synchronized (SyncObj13.class) {
            LOGGER.debug("进入同步代码块,准备睡眠!threadName->{}", Thread.currentThread().getName());
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.debug("结束同步代码块,结束睡眠!,threadName->{}", Thread.currentThread().getName());
        }
    }
}

结果:

11:10:54 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 进入同步代码块,准备睡眠!threadName->A
11:10:59 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->A
11:10:59 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 进入同步代码块,准备睡眠!threadName->C
11:11:04 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->C
11:11:04 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 进入同步代码块,准备睡眠!threadName->B
11:11:09 [cn.qlq.thread.four.SyncObj12]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->B

 

补充:对Class对象加锁以对对象本身加锁是两个对象,所以静态方法或者代码块对class加锁与对对象本身加锁可以同时被对个线程同时调用。

  类锁和对象锁不是同1个东西,一个是类的Class对象的锁,一个是类的实例的锁。也就是说:1个线程访问静态synchronized的时候,允许另一个线程访问对象的实例synchronized方法。反过来也是成立的,因为他们需要的锁是不同的。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo17 {

    private static final Logger LOGGER = LoggerFactory.getLogger(Demo17.class);

    // 对class字节码对象加锁
    public static void test0() {
        try {
            synchronized (Demo17.class) {
                LOGGER.info("threadName -> {} 进入test0");
                Thread.sleep(2 * 1000);
                LOGGER.info("threadName -> {} 退出test0");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 对class字节码对象加锁
    public static synchronized void test1() {
        try {
            LOGGER.info("threadName -> {} 进入test1");
            Thread.sleep(2 * 1000);
            LOGGER.info("threadName -> {} 退出test1");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 对当前对象加锁
    public synchronized void test2() {
        try {
            LOGGER.info("threadName -> {} 进入test2");
            Thread.sleep(2 * 1000);
            LOGGER.info("threadName -> {} 退出test2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 对当前对象加锁
    public void test3() {
        try {
            synchronized (this) {
                LOGGER.info("threadName -> {} 进入test3");
                Thread.sleep(2 * 1000);
                LOGGER.info("threadName -> {} 退出test3");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

  实际上上面test0和test1共用一把锁,test2和test3共用一把锁。相互之间不影响。

 

2.4 String常量池的特性

  在JVM中具有String常量池缓存的功能。

        String aString = "a";
        String bString = "a";
        System.out.println(aString == bString);

结果:

true

 

所以在使用synchronized(string)同步块与String联合使用时需要注意常量池带来的一些意外。

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo13 extends Thread {

    public static void main(String[] args) throws InterruptedException {

        // 同步对象
        final SyncObj14 syncObj14 = new SyncObj14();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj14.syncMethod1("a");
            }
        }, "A").start();
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj14.syncMethod1("a");
            }
        }, "B").start();
    }
}

class SyncObj14 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj14.class);

    /**
     * 同步代码块
     */
    public static void syncMethod1(String param) {
        synchronized (param) {
            LOGGER.debug("进入同步代码块,准备睡眠!threadName->{}", Thread.currentThread().getName());
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.debug("结束同步代码块,结束睡眠!,threadName->{}", Thread.currentThread().getName());
        }
    }
}

结果:(线程以同步的方式执行,也就是两个参数"a"是同一个对象,持有相同的锁,所以一般不用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象)

11:17:30 [cn.qlq.thread.four.SyncObj14]-[DEBUG] 进入同步代码块,准备睡眠!threadName->A
11:17:35 [cn.qlq.thread.four.SyncObj14]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->A
11:17:35 [cn.qlq.thread.four.SyncObj14]-[DEBUG] 进入同步代码块,准备睡眠!threadName->B
11:17:40 [cn.qlq.thread.four.SyncObj14]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->B

 

解决办法:用new Object作为锁同步(切记一般不用String作为锁同步)

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo13 extends Thread {

    public static void main(String[] args) throws InterruptedException {

        // 同步对象
        final SyncObj14 syncObj14 = new SyncObj14();
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj14.syncMethod1(new Object());
            }
        }, "A").start();
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                syncObj14.syncMethod1(new Object());
            }
        }, "B").start();
    }
}

class SyncObj14 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncObj14.class);

    /**
     * 同步代码块
     */
    public static void syncMethod1(Object param) {
        synchronized (param) {
            LOGGER.debug("进入同步代码块,准备睡眠!threadName->{}", Thread.currentThread().getName());
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.debug("结束同步代码块,结束睡眠!,threadName->{}", Thread.currentThread().getName());
        }
    }
}

结果:(以异步的方式执行)

11:21:36 [cn.qlq.thread.four.SyncObj14]-[DEBUG] 进入同步代码块,准备睡眠!threadName->B
11:21:36 [cn.qlq.thread.four.SyncObj14]-[DEBUG] 进入同步代码块,准备睡眠!threadName->A
11:21:41 [cn.qlq.thread.four.SyncObj14]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->B
11:21:41 [cn.qlq.thread.four.SyncObj14]-[DEBUG] 结束同步代码块,结束睡眠!,threadName->A

 

2.5 多线程的死锁

  多线程的死锁一般是由synchronized里面嵌套synchronized造成的,解决办法就是避免多层synchronized。

  其实不使用嵌套的synchronized也会出现死锁,与嵌套不嵌套没有任何关系。只要等待对方释放锁就有可能出现死锁。

一个简单的线程死锁的例子:

package cn.qlq.thread.four;

public class Demo14 {

    public static void main(String[] args) {
        final Integer lock1 = 1;
        final Integer lock2 = 2;
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                synchronized (lock1) {
                    try {
                        System.out.println("lock1-----");
                        Thread.sleep(2 * 1000);
                        synchronized (lock2) {
                            System.out.println("lock1-----lock2");
                        }
                    } catch (InterruptedException e) {
                    }
                }
            }
        };
        new Thread(r1).start();

        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                synchronized (lock2) {
                    try {
                        System.out.println("lock2-----");
                        Thread.sleep(2 * 1000);
                        synchronized (lock1) {
                            System.out.println("lock2-----lock1");
                        }
                    } catch (InterruptedException e) {
                    }
                }
            }
        };
        new Thread(r2).start();
    }
}

 

检测死锁:

(1)jps+jstack

C:\Users\Administrator>jps
1972 Demo14
10864 Jps
6560

C:\Users\Administrator>jstack -l 1972
2018-12-10 11:30:15
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.80-b11 mixed mode):

"DestroyJavaVM" prio=6 tid=0x00000000013fd800 nid=0xc40 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Thread-1" prio=6 tid=0x000000000b2e9000 nid=0x4644 waiting for monitor entry [0x000000000d21f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at cn.qlq.thread.four.Demo14$2.run(Demo14.java:33)
        - waiting to lock <0x00000007d629eb88> (a java.lang.Integer)
        - locked <0x00000007d629eb98> (a java.lang.Integer)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

"Thread-0" prio=6 tid=0x000000000cc63800 nid=0x2cf8 waiting for monitor entry [0x000000000d11f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at cn.qlq.thread.four.Demo14$1.run(Demo14.java:16)
        - waiting to lock <0x00000007d629eb98> (a java.lang.Integer)
        - locked <0x00000007d629eb88> (a java.lang.Integer)
        at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
        - None

"Service Thread" daemon prio=6 tid=0x000000000b2ce000 nid=0x3d98 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread1" daemon prio=10 tid=0x000000000b2c5000 nid=0x1d2c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread0" daemon prio=10 tid=0x000000000b2aa000 nid=0x2248 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Attach Listener" daemon prio=10 tid=0x000000000b2a7800 nid=0x35d4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Signal Dispatcher" daemon prio=10 tid=0x000000000b2c1000 nid=0x1a80 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Finalizer" daemon prio=8 tid=0x000000000b26e800 nid=0x3d74 in Object.wait() [0x000000000c61e000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000007d6204858> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
        - locked <0x00000007d6204858> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

   Locked ownable synchronizers:
        - None

"Reference Handler" daemon prio=10 tid=0x000000000b265800 nid=0x335c in Object.wait() [0x000000000c51f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000007d6204470> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:503)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
        - locked <0x00000007d6204470> (a java.lang.ref.Reference$Lock)

   Locked ownable synchronizers:
        - None

"VM Thread" prio=10 tid=0x000000000b261800 nid=0x2884 runnable

"GC task thread#0 (ParallelGC)" prio=6 tid=0x0000000002f36800 nid=0x1320 runnable

"GC task thread#1 (ParallelGC)" prio=6 tid=0x0000000002f38000 nid=0xdd0 runnable

"GC task thread#2 (ParallelGC)" prio=6 tid=0x0000000002f3b000 nid=0x17b8 runnable

"GC task thread#3 (ParallelGC)" prio=6 tid=0x0000000002f3c800 nid=0x3e70 runnable

"VM Periodic Task Thread" prio=10 tid=0x000000000b2e0000 nid=0x2394 waiting on condition

JNI global references: 107


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x000000000b26cf48 (object 0x00000007d629eb88, a java.lang.Integer),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x000000000b26e338 (object 0x00000007d629eb98, a java.lang.Integer),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at cn.qlq.thread.four.Demo14$2.run(Demo14.java:33)
        - waiting to lock <0x00000007d629eb88> (a java.lang.Integer)
        - locked <0x00000007d629eb98> (a java.lang.Integer)
        at java.lang.Thread.run(Thread.java:745)
"Thread-0":
        at cn.qlq.thread.four.Demo14$1.run(Demo14.java:16)
        - waiting to lock <0x00000007d629eb98> (a java.lang.Integer)
        - locked <0x00000007d629eb88> (a java.lang.Integer)
        at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

 

(2)jconsole可视化工具查看以及JVisualVM查看 参考:https://www.cnblogs.com/qlqwjy/p/10029719.html

 

2.6内置类与静态内置类

  关键字synchronized还涉及内置类的使用。

2.6.1 内部类的基本用法

package cn.qlq.thread.four;

public class PublicClass {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    class PrivateClass {
        private String address;
        private String age;

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }
    }
}

 

测试代码:

package cn.qlq.thread.four;

import cn.qlq.thread.four.PublicClass.PrivateClass;

public class Demo15 {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("qlq");
        publicClass.setPassword("111222");
        PrivateClass privateClass = publicClass.new PrivateClass();
        privateClass.setAddress("address");
        privateClass.setAge("19");
        System.out.println(publicClass.getUsername() + "\t" + publicClass.getPassword());
        System.out.println(privateClass.getAge() + "\t" + privateClass.getAddress());
    }
}

结果:

qlq 111222
19 address

  如果PublicClass.java和运行的类不在同一个包下,则需要将PrivateClass内置声明为public公开的。想要实例化类必须使用如下代码:

        PrivateClass privateClass = publicClass.new PrivateClass();

 

  内置类还有一种是静态内置类。

package cn.qlq.thread.four;

public class PublicClass {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    static class PrivateClass {
        private String address;
        private String age;

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }
    }
}

 

package cn.qlq.thread.four;

import cn.qlq.thread.four.PublicClass.PrivateClass;

public class Demo15 {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("qlq");
        publicClass.setPassword("111222");
        PrivateClass privateClass = new PrivateClass();
        privateClass.setAddress("address");
        privateClass.setAge("19");
        System.out.println(publicClass.getUsername() + "\t" + publicClass.getPassword());
        System.out.println(privateClass.getAge() + "\t" + privateClass.getAddress());
    }
}

 

结果同上。

静态内部类的创建方式与普通类一样,如下:

PrivateClass privateClass = new PrivateClass();

 

补充:内部类可以直接访问外部类的成员属性:

例如内部类的address直接获取了外部类的username属性

package cn.qlq.thread.four;

public class PublicClass2 {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public class PrivateClass {
        private String address;
        private String age;

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }

        public PrivateClass(String age) {
            this.age = age;
            this.address = username;
        }
    }

    public static void main(String[] args) {
        PublicClass2 publicClass2 = new PublicClass2();
        publicClass2.setUsername("sss");
        PrivateClass privateClass = publicClass2.new PrivateClass("25");
        System.out.println(privateClass.getAddress());
        System.out.println(privateClass.getAge());
    }
}

结果:

sss
25

 

反编译之后发现是生成两个类,在创建内部类的时候将外部类的实例传到构造方法上,因此内部类实例可以访问外部类的属性。

 如下内部类反编译后的代码:

反汇编查看main方法:(创建外部类,并且创建内部类,在实例化的时候传了两个:引用类型参数外部类和引用类型字符串)

  public static void main(java.lang.String[]);
    Code:
       0: new           #4                  // class PublicClass2
       3: dup
       4: invokespecial #5                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #6                  // String sss
      11: invokevirtual #7                  // Method setUsername:(Ljava/lang/String;)V
      14: new           #8                  // class PublicClass2$PrivateClass
      17: dup
      18: aload_1
      19: dup
      20: invokevirtual #9                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      23: pop
      24: ldc           #10                 // String 25
      26: invokespecial #11                 // Method PublicClass2$PrivateClass."<init>":(LPublicClass2;Ljava/lang/String;)V
      29: astore_2
      30: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
      33: aload_2
      34: invokevirtual #13                 // Method PublicClass2$PrivateClass.getAddress:()Ljava/lang/String;
      37: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      40: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
      43: aload_2
      44: invokevirtual #15                 // Method PublicClass2$PrivateClass.getAge:()Ljava/lang/String;
      47: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      50: return

 

 

2.4.2  内部类的同步机制与普通的同步机制相同

例如:同一个对象加同步代码块和同步方法实现同步效果

package cn.qlq.thread.four;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PublicClass {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    static class PrivateClass {
        private static final Logger LOGGER = LoggerFactory.getLogger(PrivateClass.class);
        private String address;
        private String age;

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }

        public synchronized void test1() {
            LOGGER.debug("test1 同步方法   start---,threadName->{}", Thread.currentThread().getName());
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.debug("test1 同步方法   end---,threadName->{}", Thread.currentThread().getName());
        }

        public void test2(PrivateClass privateClass) {
            synchronized (privateClass) {
                LOGGER.debug("test2 同步代码块   start---,threadName->{}", Thread.currentThread().getName());
                try {
                    Thread.sleep(5 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                LOGGER.debug("test2 同步代码块   end---,threadName->{}", Thread.currentThread().getName());
            }
        }

    }
}

 

package cn.qlq.thread.four;

import cn.qlq.thread.four.PublicClass.PrivateClass;

public class Demo16 {
    public static void main(String[] args) {
        final PrivateClass privateClass = new PrivateClass();
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                privateClass.test1();
            }
        }, "A").start();
        // 访问同步方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                privateClass.test2(privateClass);
            }
        }, "B").start();
    }
}

结果:(根据打印时间判断同步执行)

15:11:52 [cn.qlq.thread.four.PublicClass$PrivateClass]-[DEBUG] test1 同步方法 start---,threadName->A
15:11:57 [cn.qlq.thread.four.PublicClass$PrivateClass]-[DEBUG] test1 同步方法 end---,threadName->A
15:11:57 [cn.qlq.thread.four.PublicClass$PrivateClass]-[DEBUG] test2 同步代码块 start---,threadName->B
15:12:02 [cn.qlq.thread.four.PublicClass$PrivateClass]-[DEBUG] test2 同步代码块 end---,threadName->B

 

补充:关于Synchronized同步原理的一点认识:

  同步方法是方法将同步修饰符,同步代码块是通过minitorenter(尝试占用对象的监视器,并且增加count的值)和monitorexit实现(将对象监视器的count减一,监视器为0代表锁没有占用)

package cn.qlq.thread.four;

public class Demo17 {

    // 对class字节码对象加锁
    public static void test0() {
        try {
            synchronized (Demo17.class) {
                Thread.sleep(2 * 1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 对class字节码对象加锁
    public static synchronized void test1() {
        try {
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 对当前对象加锁
    public synchronized void test2() {
        try {
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 对当前对象加锁
    public void test3() {
        try {
            synchronized (this) {
                Thread.sleep(2 * 1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

对静态代码test1反汇编查看: (对同步方法的方法修饰符增加了ACC_SYNCHRONIZED的修饰符)

 

 对静态代码块test3反汇编查看: (同步代码块是通过minitorenter和monitorexit实现的)

 

 

  •  JVM规范对monitorenter的解释:

The objectref must be of type reference.

Each object is associated with a monitor. A monitor is locked if and only
if it has an owner. The thread that executes monitorenter attempts to gain
ownership of the monitor associated with objectref, as follows.
If the entry count of the monitor associated with objectref is zero, the
thread enters the monitor and sets its entry count to one. The thread is
then the owner of the monitor.
If the thread already owns the monitor associated with objectref, it reenters
the monitor, incrementing its entry count.
If another thread already owns the monitor associated with objectref, the
thread blocks until the monitor’s entry count is zero, then tries again to
gain ownership.

  中文意思大概就是:第一句先声明对象必须是引用类型才有锁。每个对象都有一个监视器。监视器如果有主就会被锁住。线程执行monitorenter的时候尝试获取监视器的所属主,

    如果没有所属主,就占用锁,将监视器的count置为1,并且将拥有者设为线程自身。

    如果有所属主并且所属主是线程自己,就讲监视器的count加一(这个是为了锁可重入);如果所属主不是自己的话就阻塞直到所属主的count计数器为0,此线程会尝试再次获取锁。

 

  •  JVM规范对monitorexit的解释:

The objectref must be of type reference.
The thread that executes monitorexit must be the owner of the monitor
associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with
objectref. If as a result the value of the entry count is zero, the thread exits
the monitor and is no longer its owner. Other threads that are blocking to
enter the monitor are allowed to attempt to do so.

  中文意思大概就是:第一句先声明对象必须是引用类型才有锁。执行monitorexut的线程必须是对象监视器的拥有者。对象执行monitorexit的时候会将监视器的count减一。直到监视器的count为0的时候,线程就退出监视器并且监视器的所属主也不是该线程。其他阻塞的线程会尝试获取监视器。(也就是monitorexit不会释放锁。锁的count为0的时候其他阻塞的线程才可以尝试获取,因为可重入锁或多个enter和exit,通过count计数器可以实现)

 

补充:关于锁升级过程

1. 乐观锁 VS 悲观锁

  对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。

  乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

  悲观锁阻塞的线程在唤醒时需要CPU的参与。CPU唤醒等待的线程再次尝试获取锁。

2.自旋锁和适应自旋锁

  阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。

  自旋锁: 比如同步代码块执行的时间很短, CPU放弃再恢复的过程足够代码块执行完,这时候可以采用自旋的方式,不放弃CPU的执行权限。如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。

  自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

  自旋锁的实现原理同样也是CAS,AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。

  自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

3. 无锁、偏向锁、轻量级锁、重量级锁    升级过程

synchronized是悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在Java对象头里的。

Monitor: Monitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步。

Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

其升级过程:

无锁: CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。

偏向锁: (偏向锁通过对比Mark Word解决加锁问题, 避免执行CAS操作)

  偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。

  当一个线程访问同步代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可。

  偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

轻量级锁:(用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能)

  指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。

重量级锁:(将除了拥有锁的线程以外的线程都阻塞。需要CPU 参与阻塞和唤醒)

  若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。等待锁的线程会进入阻塞状态, 也就是除了占有锁的线程以外的线程都进入阻塞状态。重量级锁依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步(我自己测试依赖于 futex 指令)。

 

   关于监视器更深的理解参考:https://www.cnblogs.com/549294286/p/3688829.html

 

posted @ 2018-12-08 22:55  QiaoZhi  阅读(1097)  评论(0编辑  收藏  举报