java并发之synchronized详解

Posted sxkgeek

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java并发之synchronized详解相关的知识,希望对你有一定的参考价值。

前言

多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身. 同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放. 因此就算你有多余的cpu可以执行, 但是你没有锁, 所以你还是不能进入synchronized方法执行, CPU因此而空闲. 如果某个线程长期持有一个竞争激烈的锁, 那么将导致其他线程都因等待所的释放而被挂起, 从而导致CPU无法得到利用, 系统吞吐量低下.甚至导致死锁的产生, 因此要尽量避免某个线程对锁的长期占有 !

一、修饰方法

方法声明时使用,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候

用法

public synchronized void synMethod() {

    //方法体

}

demo

public class SyncMethod {
public synchronized void syncMethod2() {
    try {
        System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)");
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)");
}
public synchronized void syncMethod1() {
    System.out.println("######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)");
}
static class Thread1 extends Thread {
    SyncMethod syncMethod;
    public Thread1(SyncMethod syncMethod) {
        this.syncMethod = syncMethod;
    }
    @Override
    public void run() {
        syncMethod.syncMethod2();
    }
}
static class Thread2 extends Thread {
    SyncMethod syncMethod;
    public Thread2(SyncMethod syncMethod) {
        this.syncMethod = syncMethod;
    }
    @Override
    public void run() {
        System.out.println("Thread2 running ...");
        syncMethod.syncMethod1();
    }
}
public static void main(String[] args) throws InterruptedException {
    SyncMethod syncMethod = new SyncMethod();
    Thread1 thread1 = new Thread1(syncMethod);
    Thread2 thread2 = new Thread2(syncMethod);
    thread1.start();    //先执行, 以便抢占锁
    Thread.sleep(500); //放弃cpu, 让thread1执行, 以便获的锁
    thread2.start(); //在syncMethod1()方法获得锁时, 看看syncMethod2()方法能否执行
}
}

console打印:

@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)
Thread2 running ...
@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)
######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)

上述代码synchronized修饰的方法,锁住的是类的实例化对象syncMethod,所以Thread1执行syncMethod2的方法将syncMethod对象锁住,使得Thread2受到阻塞必须在Thread1释放锁之后才能执行syncMethod1方法。

将上述代码中的Main方法修改如下:

SyncMethod syncMethod1 = new SyncMethod();
SyncMethod syncMethod2 = new SyncMethod();
Thread1 thread1 = new Thread1(syncMethod1);
Thread2 thread2 = new Thread2(syncMethod2);
thread1.start();    
Thread.sleep(500); 
thread2.start(); 
    

console打印:

@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)
Thread2 running ...
######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)
@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)

上述代码中Thread1锁的是syncMethod1对象,而Thread2锁的是syncMethod2对象,所以Thread1线程执行syncMethod2并不会阻塞Thread2

当然还有第二种改进措施:

public class SyncObject {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void syncMethod2() {
    synchronized (lock1) {
        try {
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 已经获取内置锁`SyncMethod.this`)");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ (syncMethod2, 即将释放内置锁`SyncMethod.this`)");
    }
}
public void syncMethod1() {
    synchronized (lock2) {
        System.out.println("######################## (syncMethod1, 已经获取内置锁`SyncMethod.this`, 并即将退出)");
    }
}

static class Thread1 extends Thread {
    SyncObject syncObject;

    public Thread1(SyncObject syncObject) {
        this.syncObject = syncObject;
    }

    @Override
    public void run() {
        syncObject.syncMethod2();
    }
}
static class Thread2 extends Thread {
    SyncObject syncObject;

    public Thread2(SyncObject syncObject) {
        this.syncObject = syncObject;
    }

    @Override
    public void run() {
        System.out.println("Thread2 running ...");
        syncObject.syncMethod1();
    }
}
public static void main(String[] args) throws InterruptedException {
    SyncObject syncObject = new SyncObject();

    Thread1 thread1 = new Thread1(syncObject);
    Thread2 thread2 = new Thread2(syncObject);

    thread1.start();    //先执行, 以便抢占锁
    Thread.sleep(500); //放弃cpu, 让thread1执行, 以便获的锁

    thread2.start(); //在syncMethod1()方法获得锁时, 看看syncMethod2()方法能否执行



}
}

下面是一些关于使用锁的一些建议: 为了避免对锁的竞争, 你可以使用锁分解,锁分段以及减少线程持有锁的时间, 如果上诉程序中的syncMethod1和syncMethod2方法是两个不相干的方法(请求的资源不存在关系), 那么这两个方法可以分别使用两个不同的锁。

上面Thread1锁的是对象lock1,而Thread2锁的是对象lock2。

二、修饰静态方法

用法

public synchronized static void method() {
    // todo
}

demo

我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对第一节的Demo进行一些修改如下:

class SyncThread implements Runnable {
private static int count;
public SyncThread() {
  count = 0;
}
public synchronized static void method() {
  for (int i = 0; i < 5; i ++) {
     try {
        System.out.println(Thread.currentThread().getName() + ":" + (count++));
        Thread.sleep(100);
     } catch (InterruptedException e) {
        e.printStackTrace();
     }
  }
}
public synchronized void run() {
  method();
  }
}

public static void main(String[] args) {
    SyncStatic syncThread1 = new SyncStatic();
    SyncStatic syncThread2 = new SyncStatic();
    Thread thread1 = new Thread(syncThread1, "SyncThread1");
    Thread thread2 = new Thread(syncThread2, "SyncThread2");
    thread1.start();
    thread2.start();
}

console打印:

SyncThread1:0 
SyncThread1:1 
SyncThread1:2 
SyncThread1:3 
SyncThread1:4 
SyncThread2:5 
SyncThread2:6 
SyncThread2:7 
SyncThread2:8 
SyncThread2:9

印证了我们刚开始说的

synchronized修饰的静态方法锁定的是这个类的所有对象

三、修饰代码块

当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
    count = 0;
    }
    public  void run() {
        synchronized(this) {
            for (int i = 0; i < 5; i++) {
                try {
             System.out.println(Thread.currentThread().getName() + ":" + (count++));
           Thread.sleep(100);
           //this.wait(100);释放锁,其他线程可以执行
        } catch (InterruptedException e) {
           e.printStackTrace();
        }
     }
  }
}
    public int getCount() {
        return count;
}
}
//main函数调用
SyncThread的调用:
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();

console打印:

SyncThread1:0 
SyncThread1:1 
SyncThread1:2 
SyncThread1:3 
SyncThread1:4 
SyncThread2:5 
SyncThread2:6 
SyncThread2:7 
SyncThread2:8 
SyncThread2:9

值得注意的是:

当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

给指定的对象加锁:

public void method(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
     // todo
}
}

四、修饰类

public class SyncClass {
    public void methodA(){
    try {
            synchronized (SyncClass.class){
                System.out.println("methodA begin 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
                Thread.sleep(3000);
                System.out.println("methodA end 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());

            }
    }catch (InterruptedException e){
        e.printStackTrace();
    }
}
    public void methodB(){
    synchronized (SyncClass.class){
        System.out.println("methodB begin 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
        System.out.println("methodB end 线程名称:"+Thread.currentThread().getName()+"times:"+System.currentTimeMillis());
    }
}

static class Thread1 extends Thread {
    private SyncClass syncClass;

    public Thread1(SyncClass syncClass) {
        super();
        this.syncClass = syncClass;
    }

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

static class Thread2 extends Thread {
    private SyncClass syncClass;

    public Thread2(SyncClass syncClass) {
        super();
        this.syncClass = syncClass;
    }

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

public static void main(String[] args) {
    SyncClass syncClass1 = new SyncClass();
    SyncClass syncClass2 = new SyncClass();

    Thread1 thread1=new Thread1(syncClass1);
    Thread2 thread2 = new Thread2(syncClass2);

    thread1.setName("A");
    thread2.setName("B");

    thread1.start();
    thread2.start();
}
}

console打印:

methodA begin 线程名称:Atimes:1533208268430
methodA end 线程名称:Atimes:1533208271431
methodB begin 线程名称:Btimes:1533208271431
methodB end 线程名称:Btimes:1533208271431

由打印结果以及与第一节改进1结果相比可得结论:

synchronized作用于一个类T时,是给这个类T加锁,T的所有实例化对象用的是同一把锁。

所以才会出现methodB在等methodA执行完毕才执行,收到阻塞。

五、synchronized原理

修饰静态代码块

将一个synchronized静态代码块反编译会看到两个专有名词

monitorenter

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

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

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

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

monitorexit:

执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
  
总结

通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

修饰方法

将一个synchronized同步方法反编译:

ACC_SYNCHRONIZED

从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

六、总结

  • 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
  • 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

以上是关于java并发之synchronized详解的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程之---Lock框架详解

java并发编程synchronized详解

(并发编程之Synchronized详解)

(并发编程之Synchronized详解)

(并发编程之Synchronized详解)

java并发之CAS详解