同步:synchronized

synchronized用法

1. 作用于实例方法  synchronized(this)

    对当前的实例对象加锁。

    这里需要注意的是,这样只能保证,多个线程访问同一个实例的时候,对于那个加了synchronized的方法访问共享数据是线程安全的。

    如果是不同的实例,相当于锁也不同了,也就不能保证线程安全了。

2. 作用于静态方法  synchronized(AccountingSync.class)

    对当前的class类的锁

    对于不同的实例对象,静态方法上加了synchronized后线程安全的。

3. 同步代码块 synchronized(instance)

    synchronized(this) 对当前的实例对象加锁

    synchronized(AccountingSync.class)对类加锁

synchronized的可重入性

基本概念

1、基础

临界区是

指一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问。

synchronized,

我们谓之锁,主要用来给方法、代码块加锁。

当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码。

当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。

但是,其余线程是可以访问该对象中的非加锁代码块的。无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象。

在java中每一个对象都可以作为锁,它主要体现在下面三个方面:

  • 对于同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前对象的Class对象。
  • static超脱于对象之外,它属于类级别的。

所以,对象锁就是该静态放发所在的类的Class实例。

由于在JVM中,所有被加载的类都有唯一的类对象,在该实例当中就是唯一的对象。

不管我们创建了该类的多少实例,但是它的类实例仍然是一个!所以对象锁是唯一且共享的。

线程同步!

如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized的instance函数B,

那么这个类的同一对象Obj,在多线程中分别访问A和B两个方法时,不会构成同步,

因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

对于同步方法块,锁是Synchonized括号里配置的对象。

2、对于 synchronized 关键字的了解

synchronized关键字解决的是多个线程之间访问资源的同步性

synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,

因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,

Java 的线程是映射到操作系统的原生线程之上的。

如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,

而操作系统实现线程之间的切换时需要从用户态转换到内核态,

这个状态之间的转换需要相对比较长的时间,时间成本相对较高,

这也是为什么早期的 synchronized 效率低的原因。

庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,

所以现在的 synchronized 锁效率也优化得很不错了。

JDK1.6对锁的实现引入了大量的优化,

如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

3、怎么使用 synchronized 关键字

synchronized关键字最主要的三种使用方式:

修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁

修饰静态方法: :也就是给当前类加锁,会作用于类的所有对象实例,

因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。

所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,

而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,

不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,

而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

总结:

synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。

synchronized 关键字加到实例方法上是给对象实例上锁。

尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!

深入分析synchronized的实现原理

刚开始学习Java的时候,一遇到多线程情况就是synchronized,

相对于当时的我们来说synchronized是这么的神奇而又强大,

那个时候我们赋予它一个名字“同步”,也成为了我们解决多线程情况的百试不爽的良药。

但是,随着我们学习的进行我们知道synchronized是一个重量级锁,

相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它。

诚然,随着Javs SE 1.6对synchronized进行的各种优化后,synchronized并不会显得那么重了。

synchronized 原理

synchronized可以保证方法或者代码块在运行时,

同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

普通同步方法,锁是当前实例对象

静态同步方法,锁是当前类的class对象

同步方法块,锁是括号里面的对象

当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁,

那么它是如何来实现这个机制的呢?我们先看一段简单的代码:

public class SynchronizedTest {
    public synchronized void test1(){
    }
    public void test2(){
        synchronized (this){
        }
    }
}

利用javap工具查看生成的class文件信息来分析Synchronize的实现

从上面可以看出,同步代码块是使用monitorenter和monitorexit指令实现的,

同步方法(在这看不出来需要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。

同步代码块:

monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,

JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。

任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。

线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;

同步方法:

synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,

在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的

synchronized标志位置1,

表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。

(摘自:http://www.cnblogs.com/javaminer/p/3889023.html)

下面我们来继续分析,但是在深入之前我们需要了解两个重要的概念:Java对象头,Monitor。

Java对象头、monitor

Java对象头和monitor是实现synchronized的基础!下面就这两个概念来做详细介绍。

Java对象头

synchronized用的锁是存在Java对象头里的,那么什么是Java对象头呢?

Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。

其中Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,

Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键,所以下面将重点阐述

Mark Word。

Mark Word用于存储对象自身的运行时数据,

如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。

Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),

但是如果对象是数组类型,则需要三个机器码,

因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,

但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。下图是Java对象头的存储结构(32位虚拟机)

对象头信息是与对象自身定义的数据无关的额外存储成本,

但是考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,

它会根据对象的状态复用自己的存储空间,

也就是说,Mark Word会随着程序的运行发生变化,变化状态如下(32位虚拟机):

Monitor

什么是Monitor?我们可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。

与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,

因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。

Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。

每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),

同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

其结构如下:

Owner:

初始时为NULL表示当前没有任何线程拥有该monitor record,

当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL;

EntryQ:

关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。

RcThis:

表示blocked或waiting在该monitor record上的所有线程的个数。

Nest:用来实现重入锁的计数。

HashCode:

保存从对象头拷贝过来的HashCode值(可能还包含GC age)。

Candidate:

用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,

如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,

会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。

Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

摘自::Java中synchronized的实现原理与应用))

我们知道synchronized是重量级锁,效率不怎么滴,同时这个观念也一直存在我们脑海里,不过在jdk 1.6中对synchronize的实现进行了各种优化,使得它显得不是那么重了,那么JVM采用了那些优化手段呢?

synchronized用法实例

方法内的变量为线程安全

package com.example;
public class RunClass1 {
    public void runNumber(String userName){
        try {
            int num=100;
            if(userName.equals("a")){
                num=1000;
                System.out.println("a is set");
                Thread.sleep(1000);
            }else{
                num=2000;
                System.out.println("b is set");
                Thread.sleep(1000);
            }
            System.out.println(userName+"num"+num);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}

package com.example;
public class ThreadA extends Thread {
    private RunClass1 runClass1;
    public  ThreadA(RunClass1 runClass1){
        super();
        this.runClass1=runClass1;
    }

    @Override
    public void run(){
        super.run();
        runClass1.runNumber("a");
    }
}

package com.example;
public class ThreadB extends Thread {
    private  RunClass1 runClass1;

    public ThreadB(RunClass1 runClass1){
        super();
        this.runClass1=runClass1;
    }
    @Override
    public void run(){
        super.run();
        runClass1.runNumber("b");
    }
}

package com.example;
public class ManClass {
    public static void main(String[] args) {
        RunClass1 runClass1=new RunClass1();
        
        ThreadA threadA=new ThreadA(runClass1);
        threadA.start();

        ThreadB threadB=new ThreadB(runClass1);
        threadB.start();
    }
}
View Code

最终运行结果:因为方法内的变量是私有的,不存在非线程安全问题
a is set
anum1000
b is set
bnum2000

实例变量非线程安全

将变量num提到方法外面,其他不变

package com.example;
public class RunClass1 {
    private  int num=100; //将变量放到方法的外面,则两个线程共同操作一个变量
    public void runNumber(String userName){
        try {
            if(userName.equals("a")){
                num=1000;
                System.out.println("a is set");
                Thread.sleep(1000);
            }else{
                num=2000;
                System.out.println("b is set");
                Thread.sleep(1000);
            }
            System.out.println(userName+"num"+num);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}
View Code

结果
两个线程操作同一个对象,对象的变量,会出现线程不安全的情况。
a is set
b is set
bnum2000
anum2000

只需再方法的前面加上sychronized关键字,来实现同步

package com.example;
public class RunClass1 {
    private  int num=100;
    public synchronized void runNumber(String userName){
        try {
            if(userName.equals("a")){
                num=1000;
                System.out.println("a is set");
                Thread.sleep(1000);
            }else{
                num=2000;
                System.out.println("b is set");
                Thread.sleep(1000);
            }
            System.out.println(userName+"num"+num);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}
View Code

多个对象多个锁

在方法上加上synchronized后,更改man函数,新建了两个类,两个线程访问了两个不同的对象实例

package com.example;
public class ManClass {
    public static void main(String[] args) {
        RunClass1 runClassA=new RunClass1();
        RunClass1 runClassB=new RunClass1();

        ThreadA threadA=new ThreadA(runClassA);
        threadA.start();

        ThreadB threadB=new ThreadB(runClassB);
        threadB.start();
    }
}
View Code

结果
发现打印的顺序是不同步的,说明通过synchronized取得的锁是对象锁,多个线程访问多个实例则JVM创建多个锁。
a is set
b is set
anum1000
bnum2000

synchronized方法与锁的对象

package com.example;
public class MyObj {
    public void methodA(){
        try {
            System.out.println("begin methood A methodName is: "+Thread.currentThread().getName());
            Thread.sleep(2000);
            System.out.println("end");
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}

package com.example;
public class ThreadA  extends  Thread{
    private  MyObj obj;
    public ThreadA(MyObj obj){
        super();
        this.obj=obj;
    }

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

package com.example;
public class ThreadB extends  Thread {
    private MyObj obj;

    public ThreadB(MyObj obj){
        super();
        this.obj=obj;
    }

    public void  run(){
        super.run();
        obj.methodA();
    }
}
package com.example;
public class Run {
    public static void main(String[] args) {
        MyObj obj=new MyObj();

        ThreadA threadA=new ThreadA(obj);
        threadA.setName("a");

        ThreadB threadB=new ThreadB(obj);
        threadB.setName("b");

        threadA.start();
        threadB.start();
    }
}
View Code

结果:
两个线程都可以同时进入到方法中。
begin methood A methodName is: a
begin methood A methodName is: b
end
end

在方法前加上synchronized后

package com.example;
public class MyObj {
    public synchronized void methodA(){
        try {
            System.out.println("begin methood A methodName is: "+Thread.currentThread().getName());
            Thread.sleep(2000);
            System.out.println("end");
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}
View Code

结果,两个线程是排队进入到方法中的。
begin methood A methodName is: a
end
begin methood A methodName is: b
end

package com.example;
public class MyObj {
    public synchronized void methodA(){  //只对方法a前面设置锁
        try {
            System.out.println("begin methood A methodName is: "+Thread.currentThread().getName());
            Thread.sleep(1000);
            System.out.println("end A");
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
    public void methodB(){
        try {
            System.out.println("begin methood B methodName is: "+Thread.currentThread().getName());
            Thread.sleep(1000);
            System.out.println("end B");
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}
View Code

ThreadA访问加锁的方法

package com.example;
public class ThreadA  extends  Thread{
    private  MyObj obj;
    public ThreadA(MyObj obj){
        super();
        this.obj=obj;
    }
    @Override
    public  void  run(){
        super.run();
        obj.methodA();
    }
}
View Code

ThreadB访问未加锁的方法

package com.example;
public class ThreadB extends  Thread {
    private MyObj obj;
    public ThreadB(MyObj obj){
        super();
        this.obj=obj;
    }
    public void  run(){
        super.run();
        obj.methodB();
    }
}
package com.example;
public class Run {
    public static void main(String[] args) {
        MyObj obj=new MyObj();

        ThreadA threadA=new ThreadA(obj);
        threadA.setName("a");

        ThreadB threadB=new ThreadB(obj);
        threadB.setName("b");

        threadA.start();
        threadB.start();
    }
}
View Code

begin methood A methodName is: a
begin methood B methodName is: b
end B
end A
A线程先持有object对象的lock锁,则B线程可以以异步的方式去调用object对象中的非synchronized方法

若将对象中的另一个方法也加锁

package com.example;
public class MyObj {
    public synchronized void methodA(){
        try {
            System.out.println("begin methood A methodName is: "+Thread.currentThread().getName());
            Thread.sleep(1000);
            System.out.println("end A");
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
    public synchronized void methodB(){
        try {
            System.out.println("begin methood B methodName is: "+Thread.currentThread().getName());
            Thread.sleep(1000);
            System.out.println("end B");
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }

}
View Code

则调用后结果为
begin methood A methodName is: a
end A
begin methood B methodName is: b
end B

A线程先持有object的lock锁,则B线程在调用object对象中的synchronized方法时需要等待,也就是同步

脏读

写方法设置了锁,而读方法没有设置同步锁

package com.example;
public class PublicVar {
    public String userName = "A";
    public String password = "AA";
    synchronized public void setValue(String userName, String password) {
        try {
            this.userName = userName;
            Thread.sleep(2000);
            this.password = password;
            System.out.println("Thread set value current name is " +
                    Thread.currentThread().getName() + " userName:" + userName + " password: " + password);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
     public void getValue() {
        System.out.println("get value method name is " +
                Thread.currentThread().getName() + "userName: " + userName + " password:" + password);
    }
}

package com.example;
public class ThreadA  extends Thread{
    private PublicVar publicVar;

    public ThreadA(PublicVar publicVar){
        super();
        this.publicVar=publicVar;
    }
    @Override
    public void run(){
        super.run();
        publicVar.setValue("11","222");
    }
}

package com.example;

public class Run {

    public static void main(String[] args) {
        try {
            PublicVar publicVar=new PublicVar();
            ThreadA threadA=new ThreadA(publicVar);
            threadA.start();
            Thread.sleep(200);
            publicVar.getValue();
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}
View Code

get value method name is mainuserName: 11 password:AA
Thread set value current name is Thread-0 userName:11 password: 222

synchronized锁重入

可重入锁的概念是自己可以再次获取自己的内部锁,比如有一个线程获取的某对象的锁,此时对象锁还没有释放,

当其想再次获取到这个对象锁时还是可以获取的,不然就会造成死锁。

package com.example;
public class Service {
    synchronized public void service1(){
        System.out.println("service1");
        service2();
    }
    synchronized public void service2(){
        System.out.println("service2");
        service3();
    }
    synchronized public void service3(){
        System.out.println("service3");
    }
}

package com.example;
public class MyThread extends Thread {
    @Override
    public void run(){
        Service service=new Service();
        service.service1();
    }
}

package com.example;
public class Run {
    public static void main(String[] args) {
        MyThread t=new MyThread();
        t.start();
    }
}
View Code

可重入锁也支持在父子类继承中,

package com.example;
public class Main {
    public int i=10;
    synchronized public void operateIMainMethod(){
        try {
            i--;
            System.out.println("main print i="+i);
            Thread.sleep(100);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}

package com.example;
public class Sub  extends Main{
    synchronized public void operateISubMethod(){
        try{
            while (i>0){
                i--;
                System.out.println("sub print i ="+i);
                Thread.sleep(100);
                this.operateIMainMethod();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

package com.example;
public class MyThread extends  Thread{
    @Override
    public void run(){
        Sub sub=new Sub();
        sub.operateISubMethod();
    }
}

package com.example;
public class Main {
    public int i=10;
    synchronized public void operateIMainMethod(){
        try {
            i--;
            System.out.println("main print i="+i);
            Thread.sleep(100);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}
存在父子类继承时,子类可以通过可重入锁,调用父类的同步方法
sub print i =9
main print i=8
sub print i =7
main print i=6
sub print i =5
main print i=4
sub print i =3
main print i=2
sub print i =1
main print i=0
View Code

出现异常锁自动释放

package com.example;
public class Service {
    synchronized public void testMethod() {
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("threadName " + Thread.currentThread().getName() +
                    " currentTime:" + System.currentTimeMillis());
            int i = 1;
            while (i == 1) {
                if (("" + Math.random()).substring(0, 8).equals("0.123456")) {
                    System.out.println("ThreadName=" + Thread.currentThread().getName() +
                            "run exceptionTime=" + System.currentTimeMillis());
                    Integer.parseInt("a");
                }
            }
        } else {
            System.out.println("Thread B run Time=" + System.currentTimeMillis());
        }
    }
}

package com.example;
public class ThreadA extends  Thread{
    private Service service;
    public ThreadA(Service service){
        super();
        this.service=service;
    }
    @Override
    public void run(){
        super.run();
        service.testMethod();
    }
}

package com.example;
public class ThreadB extends  Thread{
    private Service service;
    public ThreadB(Service service){
        super();
        this.service=service;
    }
    @Override
    public void run(){
        super.run();
        service.testMethod();
    }
}

package com.example;
public class Test {
    public static void main(String[] args) {
        try {
            Service service=new Service();
            ThreadA threadA=new ThreadA(service);
            threadA.setName("a");
            threadA.start();

            Thread.sleep(500);
            ThreadB threadB=new ThreadB(service);
            threadB.setName("b");
            threadB.start();

        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
}
View Code

同步不具有继承性

package com.example;
public class Main {
    synchronized public void serviceMethod(){
        try {
            System.out.println("int main next step begin threadName="+
                    Thread.currentThread().getName()+"time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int main net time sleep end ThreadName="+
                    Thread.currentThread().getName()+"time="+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

package com.example;
public class Sub extends Main {
    @Override
    public void serviceMethod(){
        try {
            System.out.println("int sub next step begin threadName="+
                    Thread.currentThread().getName()+" time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int sub net time sleep end ThreadName="+
                    Thread.currentThread().getName()+" time="+System.currentTimeMillis());
            super.serviceMethod();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

package com.example;
public class MyThread extends  Thread {
    private  Sub sub;
    public  MyThread(Sub sub){
        super();
        this.sub=sub;
    }
    @Override
    public void run(){
        super.run();
        sub.serviceMethod();
    }
}

package com.example;
public class Test {
    public static void main(String[] args) {
        Sub sub=new Sub();
        MyThread a=new MyThread(sub);
        a.setName("A");
        a.start();

        MyThread b=new MyThread(sub);
        b.setName("B");
        b.start();
    }
}
View Code

从结果可以看出,子类sub的方法并未加锁,也就是未继承父类的锁。
int sub next step begin threadName=A time=1563958471398
int sub next step begin threadName=B time=1563958471398
int sub net time sleep end ThreadName=A time=1563958476398
int sub net time sleep end ThreadName=B time=1563958476398
int main next step begin threadName=A time=1563958476398
int main net time sleep end ThreadName=A time=1563958481398
int main next step begin threadName=B time=1563958481398
int main net time sleep end ThreadName=B time=1563958486399

synchronized方法的弊端

使用synchronized方法时,若是方法中有些处理会很耗时,则会妨碍其他的线程获取资源进行处理,严重影响性能

synchronized同步代码块的使用

package com.example;
public class ObjectService {
    public void serviceMethod() {
        try {
            synchronized (this) {
                System.out.println("begin time: " + System.currentTimeMillis() +
                        " thread name=" + Thread.currentThread().getName());
                Thread.sleep(2000);
                System.out.println("end time: " + System.currentTimeMillis() +
                        " thread name=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

package com.example;
public class MyThread extends Thread {
    private ObjectService objectService;
    public MyThread(ObjectService o){
        super();
        this.objectService=o;
    }
    @Override
    public void run(){
        super.run();
        objectService.serviceMethod();
    }
}

package com.example;
public class Test {
    public static void main(String[] args) {
        ObjectService objectService=new ObjectService();
        MyThread myThread1=new MyThread(objectService);
        MyThread myThread2=new MyThread(objectService);
        myThread1.start();
        myThread2.start();
    }
}
View Code

结果:
当并发的线程访问同一个对象object中的synchronized(this)包括的代码段时,

后访问的线程需要等待先访问的线程访问完了才能访问
begin time: 1563965337447 thread name=Thread-0
end time: 1563965339447 thread name=Thread-0
begin time: 1563965339447 thread name=Thread-1
end time: 1563965341448 thread name=Thread-1


不在synchronized块中的代码是异步执行的,而在synchronized中的代码是同步执行的。

对于同一个对象中不同的synchronize(this)代码块,当一个线程访问其中一个synchronized(this)代码块时,其他的线程将阻塞

这说明synchronized使用的对象监视器是一个。

synchronized(this)是锁定当前对象的。

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

synchronized同步方法:

《1》对其他的synchronized同步方法或synchronized(this)同步块调用呈阻塞状态。

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

synchronized(this)同步块:

《1》对其他的synchronized同步方法或synchronized(this)同步块调用呈阻塞状态。

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

将任意对象作为对象监视器

synchronized(非this对象x)

当多个线程持有的对象监视器为同一个对象时,在同一时间内只有一个线程可以访问synchronized(非this对象x)中对的代码块。

对于synchronized(非this对象x) 不同的x对象之间的代码块是异步的,这样可以大大提高运行效率。

脏读

如下面代码所示,一个只能存储一条数据的list,由于两个线程异步的去获取list的大小,造成脏读出现,最终发现存入的结果是两个数据。

package com.example;
import java.util.ArrayList;
import java.util.List;
public class MyOneList {
     private List list=new ArrayList();
     synchronized public void add(String data){
         list.add(data);
     }
     synchronized public int getSize(){
         return list.size();
     }
}

package com.example;
public class MyService {
    public MyOneList addServiceMethod(MyOneList list, String data) {
        try {
                if (list.getSize() < 1) {
                    Thread.sleep(2000);
                    list.add(data);
                }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        return list;
    }
}

package com.example;
public class MyThread extends Thread {
    private MyOneList myOneList;
    private String data;
    public MyThread(MyOneList list,String data){
        super();
        this.myOneList=list;
        this.data=data;
    }
    @Override
    public void run(){
        super.run();
        MyService service=new MyService();
        service.addServiceMethod(myOneList,data);
    }
}

package com.example;
public class Run {
    public static void main(String[] args)throws InterruptedException {
        MyOneList list=new MyOneList();
        MyThread myThread1=new MyThread(list,"a");
        myThread1.start();
        MyThread myThread2=new MyThread(list,"b");
        myThread2.start();
        Thread.sleep(6000);
        System.out.println(list.getSize());
    }
}
View Code

更改;

synchronized (list)

package com.example;
public class MyService {
    public MyOneList addServiceMethod(MyOneList list, String data) {
        try {
            synchronized (list){
                if (list.getSize() < 1) {
                    Thread.sleep(2000);
                    list.add(data);
                }
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        return list;
    }
}
View Code

当多个线程同时执行synchronized(x){}同步代码块时呈现同步的效果。

当其他线程执行x对象中的synchronized同步方法时呈现同步效果。

当其他线程执行x对象里面的synchronized(this){} 代码块时呈现同步效果。

静态同步synchronized方法和synchronized(class)同步代码块

package com.example;
public class Service {
    synchronized public static void printA(){
        try {
            System.out.println(Thread.currentThread().getName()+" printA start  "+System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+" printA end  "+System.currentTimeMillis());
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
    synchronized public static void printB(){
        System.out.println(Thread.currentThread().getName()+" printB start  "+System.currentTimeMillis());
        System.out.println(Thread.currentThread().getName()+" printB end  "+System.currentTimeMillis());
    }
}

package com.example;
public class MyThread extends Thread {
    private int flag;
    public MyThread(int flag){
        this.flag=flag;
    }
    @Override
    public void run(){
        super.run();
        if(flag==1){
            Service.printA();
        }else {
            Service.printB();
        }
    }
}

package com.example;
public class Run {
    public static void main(String[] args) {
        MyThread threadA=new MyThread(1);
        MyThread threadB=new MyThread(2);
        threadA.start();
        threadB.start();
    }
}
View Code

结果:
将synchronized应用到非静态的方法上是对对象加锁
而将synchronized应用到静态方法上是对class加锁
Thread-0 printA start 1564015251677
Thread-0 printA end 1564015254678
Thread-1 printB start 1564015254678
Thread-1 printB end 1564015254678

一个synchronized加在静态方法上,一个加在非静态方法上时,当多个线程调用这两个方法时是呈现异步的。

这是因为一个是加在class类上的锁,一个是加在对象上的锁。

package com.example;
public class Service {
    synchronized public static void printA(){
        try {
            System.out.println(Thread.currentThread().getName()+" printA start  "+System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+" printA end  "+System.currentTimeMillis());
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
    synchronized public void printC(){
        try {
            System.out.println(Thread.currentThread().getName()+" printC start  "+System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+" printC end  "+System.currentTimeMillis());
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }

    }
}
View Code

synchronized(Class)同步代码块和synchronized static方法作用是一样的。

下面代码中加锁的监视对象是Service这个类

package com.example;

public class Service {
    synchronized public static void printA(){
        try {
            System.out.println(Thread.currentThread().getName()+" printA start  "+System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+" printA end  "+System.currentTimeMillis());
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
    }
    public void printC(){
        synchronized (Service.class){
            try {
                System.out.println(Thread.currentThread().getName()+
                " printC start  "+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+
                " printC end  "+System.currentTimeMillis());
            }catch (InterruptedException ex){
                ex.printStackTrace();
            }
        }
    }
}
View Code

 

参考文章

https://blog.csdn.net/javazejian/article/details/72828483

https://www.jianshu.com/p/d53bf830fa09

【Java并发编程实战】—–synchronized

聊聊并发(二)Java SE1.6中的Synchronized

Java 并发进阶常见面试题总结

死磕Java并发】—–深入分析synchronized 的实现原理

天天用Synchronized,底层原理是个啥?

【死磕 Java 并发】----- synchronized 的锁膨胀过程

Java多线程访问Synchronized同步方法的八种使用场景 [ 中奖名单 ]

吃透Synchronized,长文精讲!

为什么 wait 方法要在 synchronized 中调用?

死磕Synchronized底层实现

Java之戳中痛点之 synchronized 深度解析

一道题决定去留:为什么synchronized无法禁止指令重排,却能保证有序性

一个synchronized跟面试官扯了半个小时

面试官最想要的synchronized,你值得拥有

Java 并发编程:Synchronized 及其实现原理

死磕Synchronized底层实现

Java多线程:synchronized关键字和Lock

一道大题决定去留:为什么synchronized无法禁止指令重排,却能保证有序性?

Java多线程访问Synchronized同步方法的八种使用场景 [ 中奖名单 ]

Synchronized 同步方法的八种使用场景

【72期】面试官:对并发熟悉吗?说一下synchronized与Lock的区别与使用

一口气说出 Synchronized 同步方法的八种使用场景

被问到傻傻不懂synchronized底层原理

死磕Synchronized底层实现

Synchronized 同步方法的八种使用场景

 

 

posted @ 2018-10-22 10:15  弱水三千12138  阅读(271)  评论(0编辑  收藏  举报