Java Synchronized 关键字

本文内容

  • Synchronized 关键字

  • 示例
  • Synchronized 方法
  • 内部锁(Intrinsic Locks)和 Synchronization
  • 参考资料

下载 Demo

Synchronized 关键字



Java 语言提供两个基本的同步机制:synchronized 方法(synchronized methods )和 synchronized  语句(synchronized statements)。

示例



先大概说一下 Java Synchronized 关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻只有一个线程执行该段代码。

  • 当两个线程访问同一个对象 synchronized(this) 代码块时,只能有一个线程执行,另一个线程必须等待这个线程执行完后才能执行;

  • 但是,当一个线程访问一个对象 synchronized(this) 同步代码块时,另一个线程仍能访问该对象的非 synchronized(this) 代码块;
  • 尤其是,当一个线程访问一个对象的一个 synchronized(this) 代码块时,其他线程对该对象中其他所有 synchronized(this) 代码块的访问也将被阻塞;
  • 以上规则对其它对象锁同样适用。

如下代码所示:

package cn.db.syncdemo;
 

public class NewClass {

    /**

     * 同步方法

     */

    public void synchronizedMethod() {

        synchronized (this) {

            int i = 5;

            while (i-- > 0) {

                System.out.println(Thread.currentThread().getName() + " : " + i

                        + " synchronized method");

                try {

                    Thread.sleep(2000);

                } catch (InterruptedException e) {

                    System.out.println(e.toString());

                }

            }

        }

    }

 

    /**

     * 同步方法 2

     */

    public void synchronizedMethod2() {

        synchronized (this) {

            int i = 5;

            while (i-- > 0) {

                System.out.println(Thread.currentThread().getName() + " : " + i

                        + " synchronized method 2");

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    System.out.println(e.toString());

                }

            }

        }

    }

 

    /**

     * 非同步方法

     */

    public void nonSynchronizedMethod() {

        int i = 5;

        while (i-- > 0) {

            System.out.println(Thread.currentThread().getName() + " : " + i

                    + " nonSynchronized method");

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                System.out.println(e.toString());

            }

        }

    }

 

    public static void main(String[] args) {

        final NewClass mClass = new NewClass();

 

        // t1 和 t2 都要访问同一个同步方法 synchronizedMethod

        Thread t1 = new Thread(new Runnable() {

            public void run() {

                mClass.synchronizedMethod();

            }

        }, "Thread 1");

        Thread t2 = new Thread(new Runnable() {

            public void run() {

                mClass.synchronizedMethod();

            }

        }, "Thread 2");

        // t3 要访问另一个同步方法 synchronizedMethod2

        Thread t3 = new Thread(new Runnable() {

            public void run() {

                mClass.synchronizedMethod2();

            }

        }, "Thread 3");

 

        // t4 要访问非同步方法 nonSynchronizedMethod

        Thread t4 = new Thread(new Runnable() {

            public void run() {

                mClass.nonSynchronizedMethod();

            }

        }, "Thread 4");

 

        t1.start();

        t2.start();

        t3.start();

        t4.start();

 

    }

}

运行结果:

Thread 4 : 4 nonSynchronized method
Thread 1 : 4 synchronized method

Thread 4 : 3 nonSynchronized method

Thread 4 : 2 nonSynchronized method

Thread 1 : 3 synchronized method

Thread 4 : 1 nonSynchronized method

Thread 4 : 0 nonSynchronized method

Thread 1 : 2 synchronized method

Thread 1 : 1 synchronized method

Thread 1 : 0 synchronized method

Thread 3 : 4 synchronized method 2

Thread 3 : 3 synchronized method 2

Thread 3 : 2 synchronized method 2

Thread 3 : 1 synchronized method 2

Thread 3 : 0 synchronized method 2

Thread 2 : 4 synchronized method

Thread 2 : 3 synchronized method

Thread 2 : 2 synchronized method

Thread 2 : 1 synchronized method

Thread 2 : 0 synchronized method

说明:

  • t4 访问非同步方法 nonSynchronizedMethod2,无论何时都可以访问,所以“thread 4:……”出现的是不规律的,交错的;

  • t1 和 t2 都要访问同一个同步方法 synchronizedMethod,所以“thread 1:……”和“thread 2:……”绝对不会交错显示,一个线程访问完后,另一个线程才能访问;
  • 对于 t3,t3 自己访问另一个同步方法 synchronizedMethod2,并且没有其他线程再访问,而且当 t3 访问该方法时,其他同步方法也被锁了,所以,“thread 3:……”是连续出现的;
  • 如果不看 t4,也就是没有该线程,那么结果会是:
Thread 1 : 4 synchronized method
Thread 1 : 3 synchronized method

Thread 1 : 2 synchronized method

Thread 1 : 1 synchronized method

Thread 1 : 0 synchronized method

Thread 3 : 4 synchronized method 2

Thread 3 : 3 synchronized method 2

Thread 3 : 2 synchronized method 2

Thread 3 : 1 synchronized method 2

Thread 3 : 0 synchronized method 2

Thread 2 : 4 synchronized method

Thread 2 : 3 synchronized method

Thread 2 : 2 synchronized method

Thread 2 : 1 synchronized method

Thread 2 : 0 synchronized method

注意:thread 2 绝对不会出现在 thread 1 前面,并且 thread 2 必须等 thread 1 访问完,它才能访问;thread 3 访问时,thread 1 和 thread 2 也不能访问。虽然没有线程跟 thread 3 抢,但这三个线程访问的都是两个同步方法。

Synchronized 方法



要想使一个方法为同步方法,只需简单给它的声明加上 synchronized 关键字就行:

package cn.db.syncdemo;
 

public class SynchronizedCounter {

    private int c = 0;

 

    public synchronized void increment() {

        c++;

    }

 

    public synchronized void decrement() {

        c--;

    }

 

    public synchronized int value() {

        return c;

    }

}

如果 countSynchronizedCounter 的一个实例,那么把这些方法变成 synchronized 方法会有两个效果:

  • 首先,在同一个对象上,交错调用 synchronized 方法是不可能的。当一个线程正在执行一个对象的 synchronized 方法时,所有调用该 synchronized 方法的其他线程会被挂起,直到第一个线程完成;

  • 其次,当一个 synchronized 方法退出时,它自动地建立一个对同一个对象的这个同步方法的任何后续调用的发生前关系(happens-before relationship)。这保证了对象状态的改变对所有线程都是可见的。

注意:构造函数不能是 synchronized,也就是说,synchronized 关键字修饰构造函数是一个语法错误。synchronized 构造函数没有意义,因为当对象创建时,只有创建对象的那个线程才能访问它。

警告:当构造一个对象在线程之间共享时,必须非常小心,一个对象的引用不能过早地“泄漏”。例如,假设你想维护一个名为 instances 的 List,它包含类的每个实例。你可能会用下面代码在你的构造函数里:

instances.add(this);

但是,当对象构造完成后,其他线程使用 instances 访问对象。

synchronized 方法用一个简单策略来阻止线程干扰和内存一致性错误:如果一个对象对多个线程可见,所有读或写该对象的变量都通过 synchronized 方法完成。 (一个重要的例外:final 字段,对象被构造后不能被修改,可以通过非 synchronized 方法来安全地读取,一旦对象被构造)。

内部锁(Intrinsic Locks)和 Synchronization



同步是建立在被称为内部锁(Intrinsic Locks)或监视器锁定的一个内部实体。(API 规范往往指的是作为一个“显示器”的实体。)内在锁扮演两方面同步的角色:执行独占访问对象的状态,建立发生前关系(happens-before relationships )必要的可见性。

每个对象都有一个与之关联的内部锁。通常,一个需要独占和一致的访问对象字段的线程必须在访问之前要求对象的内部锁,然后当完成后释放内部锁。线程在要求和释放锁之间的时间拥有内部锁。只要一个线程拥有一个内部锁,其他线程就不能再要求这个锁。当其他线程试图要求锁时就会被阻塞。

当一个线程释放内部锁时,一个发生前关系(happens-before relationship)在该动作和后续相同锁的动作之间被建立。

Synchronized Methods 方法

当一个线程调用一个 synchronized 方法,它会自动获取该方法对象的内部锁,并且,当方法返回时,释放锁。即使返回由未捕获的异常发生时造成的锁释放。

你可能想知道,一个静态的 synchronized 方法被调用时发生了什么,因为一个静态方法与一个类关联,而不是一个对象。在这种情况下,线程获得与类有关的 Class 对象的内部锁。因此,访问类的静态字段是由锁控制的,而这个锁与任何类的实例的锁不同。

Synchronized 语句

创建 synchronized 代码的另一个方式是 synchronized  语句。与 synchronized 方法不同,synchronized 语句必须指定提供内部锁的对象,如下代码所示中的“this”:

public void addName(String name) {
    synchronized(this) {

        lastName = name;

        nameCount++;

    }

    nameList.add(name);

}

本例中,addName 方法需要同步改变 lastNamenameCount,还需要避免其它对象的方法同步调用。要是没有 synchronized 语句,就必须有一个单独的、非 synchronized 方法用于调用 nameList.add 的目的。

Synchronized 语句对用细粒度同步来改进并发也很有用。假设,例如,类 MsLunch 有两个实例字段,c1c2,它们绝不会一起使用。所有这些字段的更新都必须被同步,但没有理由阻止 c1 和 c2 交叉更新(通过创建一个没有必要的代码块,来减少并发)。因此,不是使用 synchronized(this),而是为两个对象提供单独的锁。如下所示:

public class MsLunch {
    private long c1 = 0;

    private long c2 = 0;

    private Object lock1 = new Object();

    private Object lock2 = new Object();

 

    public void inc1() {

        synchronized(lock1) {

            c1++;

        }

    }

 

    public void inc2() {

        synchronized(lock2) {

            c2++;

        }

    }

}

这么用要格外小心。你必须绝对确保它对交叉访问受影响字段是真的安全。

Reentrant Synchronization

回想一下,一个线程不能获取由另一个线程拥有的锁。但一个线程可以获取,它已经拥有的锁。让一个线程不只一次获取相同的锁会造成折返同步(reentrant synchronization)。这描述了一种情况,同步代码码直接或间接地调用一个方法,该方法也包含的同步代码,并且这两组代码都使用相同的锁。如果没有折返同步(reentrant synchronization),同步代码必须采取采取很多许多额外的预防措施,来避免线程阻塞自己。

参考资料


下载 Demo

时间: 2024-10-12 08:20:57

Java Synchronized 关键字的相关文章

Java synchronized 关键字详解

Java synchronized 关键字详解 前置技能点 进程和线程的概念 线程创建方式 线程的状态状态转换 线程安全的概念 synchronized 关键字的几种用法 修饰非静态成员方法 synchronized public void sync(){ } 修饰静态成员方法 synchronized public static void sync(){ } 类锁代码块 synchronized (类.class){ } 对象锁代码块 synchronized (this|对象){ } syn

java synchronized关键字浅探

synchronized 是 java 多线程编程中用于使线程之间的操作串行化的关键字.这种措施类似于数据库中使用排他锁实现并发控制,但是有所不同的是,数据库中是对数据对象加锁,而 java 则是对将要执行的代码加锁. 在 java 中使用 synchronized 关键字时需要注意以下几点: 1.synchronized 是针对同一个资源的访问作出限制.这是出现该关键字的原因. 2.对于类而言,某一个线程执行到一个 synchronized 修饰的类方法或者类方法中的代码段时,该方法或者代码段

java synchronized关键字

Java中synchronized关键字和对象的内置锁结合使用,用来保护代码块在并发环境下的线程安全,可以使被保护的代码块操作原子性. synchronized关键字可以用于修饰方法来保护方法内的全部代码块,可以用synchronized(对象1) 的方式保护指定代码块.(这里说一下:很多书中都说synchronized可以给对象加锁,我实在不愿意这么说,这样让我概念混淆...因为,对象内置锁是本来就存在的,不是谁加给它的,"synchronized(对象1)"我更愿意解释成:执行到此

Java synchronized关键字用法(清晰易懂)

本篇随笔主要介绍 java 中 synchronized 关键字常用法,主要有以下四个方面: 1.实例方法同步 2.静态方法同步 3.实例方法中同步块 4.静态方法中同步块 我觉得在学习synchronized关键字之前,我们首先需要知道以下一点:Java 中每个实例对象对应一把锁且每个实例对象只有一把锁,synchronized 关键字是通过对相应的实例对象加锁来实现同步功能的. 一.实例方法中使用 synchronized 加锁 实例方法中默认被加锁的对象是调用此方法的实例对象. 1 cla

java synchronized关键字的底层实现

每个对象都有一个锁(Monitor,监视器锁),class对象也有锁,如果synchronized关键字修饰同步代码块,通过反编译可以看到,其实是有个monitorenter和monitorexit指令,也就是说,某个线程必须首先获得该对象的监视器锁,才能进入同步代码块,如果此时其它线程也去获取该对象的锁,则会阻塞直至当前线程释放掉监视器锁.synchronized(this)是锁当前对象,synchronized(A.class)是锁class对象. 如果synchronized关键字修饰普通

JAVA synchronized关键字锁机制(中)

synchronized 锁机制简单的用法,高效的执行效率使成为解决线程安全的首选. 下面总结其特性以及使用技巧,加深对其理解. 特性: 1. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.       2. 当一个线程同时访问object的一个synchronized(this)同步代码块时,其它线程仍然可以访问非修饰的方法或代码块.       3. 当多个线程同时访问object的synchronized(this)同步代码

[java] java synchronized 关键字详解

Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块.然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块. 一.synchronized同步方法 1.synchronized 同步方法,为对象锁 publ

Java synchronized 关键字的实现原理

数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令,大家可能会进一步追问:JVM底层又是如何实现synchronized的? 本文所指说的JVM是指Hotspot的6u23版本,下面首先介绍synchronized的实现: synrhronized关键字简洁.清晰.语义明确,因此即使有了Lock接口,使用的还是非常广泛.其应用层的语义是可以把任何一个非null 对象 作为"锁",当syn

java基础回顾(五)线程详解以及synchronized关键字

本文将从线程的使用方式.源码.synchronized关键字的使用方式和陷阱以及一些例子展开java线程和synchronized关键字的内容. 一.线程的概念 线程就是程序中单独顺序的流控制.线程本 身不能运行,它只能用于程序中. 二.线程的实现 线程的实现有两种方式: 1.继承Thread类并重写run方法 2.通过定义实现Runnable接口的类进而实现run方法 当用第一种方式时我们需要重写run方法因为Thread类里的run方法什么也不做(见下边的源码),当用第二种方式时我们需要实现