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)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

原文地址:https://www.cnblogs.com/sxkgeek/p/9415818.html

时间: 2024-10-13 02:46:45

java并发之synchronized详解的相关文章

java并发之CAS详解

前言 在高并发的应用当中,最关键的问题就是对共享变量的安全访问,通常我们都是通过加锁的方式,比如说synchronized.Lock来保证原子性,或者在某些应用当中,用voliate来保证变量的可见性,还有就是通过TheadLocal将变量copy一份,称为局部变量(线程私有)等等.现在我们学习一种不加锁机制(CAS) 上述我们提到的synchronized和Lock这都是通过加锁实现的(悲观锁),其实加锁本质上是将并发转变成串行实现的,势必会阻塞线程的执行,影响应用的吞吐量,而CAS正是一种乐

Java并发之AQS详解

http://www.cnblogs.com/waterystone/p/4920797.html http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer#anch140433 http://www.infoq.com/cn/articles/java8-abstractqueuedsynchronizer

黑马------synchronized详解

黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA线程-synchronized详解 一.synchronized概述 1.线程间实现互斥,必须使用同一个监视器(一个对象) 2.synchronized的作用:为同步代码块或同步方法指定监视器 3.使用同一个监视器的多块代码块或多个方法,在任何时刻,只有获得监视器的线程可访问其中的一块代码块或方法. 二.synchronized作用对象 1.synchronized语句块:需要显式指定监视器 1)生成一个对象obj,

Java synchronized详解

Java synchronized详解 第一篇: 使用synchronized 在编写一个类时,如果该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题.在Java中内置了语言级的同步原语--synchronized,这也大大简化了Java中多线程同步的使用.我们首先编写一个非常简单的多线程的程序,是模拟银行中的多个线程同时对同一个储蓄账户进行存款.取款操作的. 在程序中我们使用了一个简化版本的Account类,代表了一个银行账户的信息.在主程序中我们首先生成了1000个线程,然后启动它们

Java关键字synchronized详解

Java关键字synchronized详解 博客分类: Java综合 Java多线程thread互联网制造 synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A,没有的话,直接运行 它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法: 通过在方法声明中

【夯实基础】java关键字synchronized 详解

尊重版权:http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个sy

Android开发之SpannableString详解

在实际的应用开发过程中经常会遇到,在文本的不同部分显示一些不同的字体风格的信息如:文本的字体.大小.颜色.样式.以及超级链接等.一般情况下,TextView中的文本都是一个样式,对于类似的情况,可以借助SpannableString或SpannableStringBuilder对象来实现以上设置. SpannableString与SpannableStringBuilder都可以将某段文本设置成一个Span,在Android中,Span表示一段文本的效果,例如,链接形式.图像.带背景的文本等.只

Android开发之InstanceState详解

Android开发之InstanceState详解 本文介绍Android中关于Activity的两个神秘方法:onSaveInstanceState() 和 onRestoreInstanceState(),并且在介绍这两个方法之后,再分别来实现使用InstanceState保存和恢复数据功能.Android实现屏幕旋转异步下载效果这样两个示例. 首先来介绍onSaveInstanceState() 和 onRestoreInstanceState() .关于这两个方法,一些朋友可能在Andr

Android开发之WebView详解

概述: 一个显示网页的视图.这个类是你可以滚动自己的Web浏览器或在你的Activity中简单地显示一些在线内容的基础.它使用了WebKit渲染引擎来显示网页,包括向前和向后导航的方法(通过历史记录),放大和缩小,执行文本搜索等. 需要注意的是:为了让你的应用能够使用WebView访问互联网和加载网页,你必须添加Internet的权限在Android Manifest文件中: <uses-permission android:name="android.permission.INTERNE