java synchronized关键字的用法

0.先导的问题代码

下面的代码演示了一个计数器,两个线程同时对i进行累加的操作,各执行1000000次.我们期望的结果肯定是i=2000000.但是我们多次执行以后,会发现i的值永远小于2000000.这是因为,两个线程同时对i进行写入的时候,其中一个线程的结果会覆盖另外一个.

public class AccountingSync implements Runnable {
  static int i = 0;
  public void increase() {
    i++;
  }

  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }

  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();

    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println(i);
  }
}

要从根本上解决这个问题,我们必须保证多个线程在对i进行操作的时候,要完全的同步.也就是说到A线程对i进行写入的时候,B线程不仅不可以写入,连读取都不可以.

1.synchronized关键字的作用

关键字synchronized的作用其实就是实现线程间的同步.它的工作就是对同步的代码进行加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性.就像上面的代码中,i++的操作只能同时又一个线程在执行.

2.synchronized关键字的用法

指定对象加锁:对给定的对象进行加锁,进入同步代码块要获得给定对象的锁

直接作用于实例方法:相当于对当前实例加锁,进入同步代码块要获得当前实例的锁(这要求创建Thread的时候,要用同一个Runnable的实例才可以)

直接作用于静态方法:相当于给当前类加锁,进入同步代码块前要获得当前类的锁

2.1指定对象加锁

下面的代码,将synchronized作用于一个给定的对象.这里有一个注意的,给定对象一定要是static的,否则我们每次new一个线程出来,彼此并不共享该对象,加锁的意义也就不存在了.

public class AccountingSync implements Runnable {
  final static Object OBJECT = new Object();

  static int i = 0;
  public void increase() {
    i++;
  }

  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (OBJECT) {
        increase();
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println(i);
  }
}

2.2直接作用于实例方法

synchronized关键字作用于实例方法,就是说在进入increase()方法之前,线程必须获得当前实例的锁.这就要求我们,在创建Thread实例的时候,要使用同一个Runnable的对象实例.否则,线程的锁都不在同一个实例上面,无从去谈加锁/同步的问题了.

public class AccountingSync implements Runnable {
  static int i = 0;
  public synchronized void increase() {
    i++;
  }

  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }

  public static void main(String[] args) throws InterruptedException {
    AccountingSync accountingSync = new AccountingSync();

    Thread t1 = new Thread(accountingSync);
    Thread t2 = new Thread(accountingSync);

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println(i);
  }
}

请注意main方法的前三行,说明关键字作用于实例方法上的正确用法.

2.3直接作用于静态方法

将synchronized关键字作用在static方法上,就不用像上面的例子中,两个线程要指向同一个Runnable方法.因为方法块需要请求的是当前类的锁,而不是当前实例,线程间还是可以正确同步的.

public class AccountingSync implements Runnable {
  static int i = 0;
  public static synchronized void increase() {
    i++;
  }

  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      increase();
    }
  }

  public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new AccountingSync());
    Thread t2 = new Thread(new AccountingSync());

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println(i);
  }
}

3.错误的加锁

从上面的例子里,我们知道,如果我们需要一个计数器应用,为了保证数据的正确性,我们自然会需要对计数器加锁,因此,我们可能会写出下面的代码:

public class BadLockOnInteger implements Runnable {
  static Integer i = 0;
  @Override
  public void run() {
    for (int j = 0; j < 1000000; j++) {
      synchronized (i) {
        i++;
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    BadLockOnInteger badLockOnInteger = new BadLockOnInteger();

    Thread t1 = new Thread(badLockOnInteger);
    Thread t2 = new Thread(badLockOnInteger);

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    System.out.println(i);
  }
}

当我们运行上面代码的时候,会发现输出的i很小.这说明线程并没有安全.

要解释这个问题,要从Integer说起:在Java中,Integer属于不变对象,和String一样,对象一旦被创建,就不能被修改了.如果你有一个Integer=1,那么它就永远都是1.如果你想让这个对象=2呢?只能重新创建一个Integer.每次i++之后,相当于调用了Integer的valueOf方法,我们看一下Integer的valueOf方法的源码:

public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}

Integer.valueOf()实际上是一个工厂方法,他会倾向于返回一个新的Integer对象,并把值重新复制给i;

所以,我们就知道问题所在的原因,由于在多个线程之间,由于i++之后,i都指向了一个新的对象,所以线程每次加锁可能都加载了不同的对象实例上面.解决方法很简单,使用上面的3种synchronize的方法之一就可以解决了.

(0)

相关推荐

  • Java多线程编程中synchronized线程同步的教程

    0.关于线程同步 (1)为什么需要同步多线程? 线程的同步是指让多个运行的线程在一起良好地协作,达到让多线程按要求合理地占用释放资源.我们采用Java中的同步代码块和同步方法达到这样的目的.比如这样的解决多线程无固定序执行的问题: public class TwoThreadTest { public static void main(String[] args) { Thread th1= new MyThread1(); Thread th2= new MyThread2(); th1.st

  • 举例讲解Java中synchronized关键字的用法

    synchronized关键字顾名思义,是用于同步互斥的作用的. 这里精简的记一下它的使用方法以及意义: 1. 当synchronized修饰 this或者非静态方法或者是一个实例的时候,所同步的锁是加在this或者实例对象引用上面的.比如a,b同为Main类的实例化对象,a调用被同步的方法,和b调用被同步的方法,没有形成互斥.但是不同线程的a对象调用被同步的方法就被互斥了. public synchronized void method(){ //-. } public void method

  • Java 同步锁(synchronized)详解及实例

    Java 同步锁(synchronized)详解及实例 Java中cpu分给每个线程的时间片是随机的并且在Java中好多都是多个线程共用一个资源,比如火车卖票,火车票是一定的,但卖火车票的窗口到处都有,每个窗口就相当于一个线程,这么多的线程共用所有的火车票这个资源.如果在一个时间点上,两个线程同时使用这个资源,那他们取出的火车票是一样的(座位号一样),这样就会给乘客造成麻烦.比如下面程序: package com.pakage.ThreadAndRunnable; public class Ru

  • Java中synchronized关键字修饰方法同步的用法详解

    Java的最基本的同步方式,即使用synchronized关键字来控制一个方法的并发访问. 每一个用synchronized关键字声明的方法都是临界区.在Java中,同一个对象的临界区,在同一时间只有一个允许被访问. 静态方法则有不同的行为.用synchronized关键字声明的静态方法,同时只能够被一个执行线程访问,但是其他线程可以访问这个对象的非静态的synchronized方法.必须非常谨慎这一点,因为两个线程可以同时访问一个对象的两个不同的synchronized方法,即其中一个是静态s

  • 详解Java中synchronized关键字的死锁和内存占用问题

    先看一段synchronized 的详解: synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以

  • Java多线程程序中synchronized修饰方法的使用实例

    在Java 5以前,是用synchronized关键字来实现锁的功能. synchronized关键字可以作为方法的修饰符(同步方法),也可作用于函数内的语句(同步代码块). 掌握synchronized,关键是要掌握把那个东西作为锁.对于类的非静态方法(成员方法)而言,意味着要取得对象实例的锁:对于类的静态方法(类方法)而言,要取得类的Class对象的锁:对于同步代码块,要指定取得的是哪个对象的锁.同步非静态方法可以视为包含整个方法的synchronized(this) { - }代码块.  

  • Java 多线程synchronized关键字详解(六)

    synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C. D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C .D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行.它包括两种用法:synchronized 方法和 synchronized 块. 多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多

  • 深入讲解java线程与synchronized关键字

    我们将会从以下的几点理解java线程的一些概念: 线程的基本概念和优劣之处 创建一个线程的两种方式 线程的属性 线程的状态 synchronized可修饰的方法 synchronized的重要特性 一.线程的基本概念 在计算机中有进程和线程这么两个概念,进程中可以有多个线程,它们是从属关系,进程往往更像是资源的占有者,线程才是程序的执行者,多个线程之间共享着进程中的资源.一个cpu同时只能运行一个线程,每个线程都有一个时间片,时间片用完了就会被阻塞并让出CPU的控制权,交给下一个线程使用.这样在

  • java中synchronized(同步代码块和同步方法)详解及区别

     java中synchronized(同步代码块和同步方法)详解及区别 问题的由来: 看到这样一个面试题: //下列两个方法有什么区别 public synchronized void method1(){} public void method2(){ synchronized (obj){} } synchronized用于解决同步问题,当有多条线程同时访问共享数据时,如果进行同步,就会发生错误,Java提供的解决方案是:只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他

  • java synchronized关键字的用法

    0.先导的问题代码 下面的代码演示了一个计数器,两个线程同时对i进行累加的操作,各执行1000000次.我们期望的结果肯定是i=2000000.但是我们多次执行以后,会发现i的值永远小于2000000.这是因为,两个线程同时对i进行写入的时候,其中一个线程的结果会覆盖另外一个. public class AccountingSync implements Runnable { static int i = 0; public void increase() { i++; } @Override

  • 深入分析JAVA Synchronized关键字

    并发一致性的概念? 是利用锁的机制来实现同步的,锁机制有如下两种特性: 互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问.互斥性我们也往往称为操作的原子性. 可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致. Synchronized的用法? 1.同步

  • Java synchronized关键字和Lock接口实现原理

    这篇文章主要介绍了Java synchronized关键字和Lock接口实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 引用 当开发过程中,我们遇到并发问题.怎么解决? 一种解决方式,简单粗暴:上锁.将千军万马都给拦下来,只允许一个人过独木桥.书面意思就是将并行的程序变成串行的程序.现实的锁有门锁.挂锁和抽屉锁等等.在Java中,我们的锁就是synchronized关键字和Lock接口. synchronized关键字 synchron

  • Java synchronized关键字使用方式及特性解析

    这篇文章主要介绍了Java synchronized关键字使用方式及特性解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 synchronized 关键字是实现锁的一种方式,是在jvm层面实现的非公平锁,以下是使用synchronized的四种方式 synchronized 特性: 1.非公平锁 2.可重入性 1.作用在方法上,保证了访问同一个对象的同一个方法的线程同步 public synchronized void testFun(Str

  • JAVA Static关键字的用法

    static关键字的作用: 用来修饰成员变量和方法,被修饰的成员是属于类的,而不单单是属于某个对象的,也就是说,可以不靠对象来调用. 首先我们来介绍类变量 当static修饰成员变量时,该变量称为类变量,该类的每个对象都共享同一个类变量的值,任何 对象都可以更改该变量的值,但也可以在不创建该类对象的情况下,通过类名对类变量进行操作 命名格式 static 数据类型 变量名: static int age; 下面我们来举个例子,让大家体会体会类变量的作用 就比如梁山有108位好汉我们给这108位好

  • Java super关键字的用法详解

    目录 super关键字的三种用法: 1. super.成员变量 2. super.成员方法 3. super():调用父类中的构造方法 3.1.1 隐式调用父类无参数构造方法super() 3.1.2 显式调用父类有参数构造方法super(id) super关键字作用:在子类内部调用父类对象 基础语法: 1.在类的继承中,当子类继承了父类, 在构造子类的时候,一定要先帮助父类进行构造: 2. 调用super()必须写在子类构造方法的第一行,以保证在执行任何动作前,对象已经完成了初始化,否则编译不

  • 谈Java static关键字的用法与好处

    本文针对Java static关键字的使用方法进行汇总,供大家参考,具体内容如下 1.对于static关键字的使用的时候对于修饰变量的时候,它相当于一个全局变量: 2.对于static修饰一个函数的时候他是在类被加载的时候首先会被类加载,并且只能加载一次,并且这个方法可以不需要通过实例进行调用例如: mystatic.java package study; public class mystatic { public static int sta=0; public int st=0; //st

  • 简单了解Java synchronized关键字同步

    synchronized synchronized可以用来同步块,同步方法.同步块可以用来更精确地控制对象锁,控制锁的作用域.(锁的作用域就是从锁的获得到锁的释放的时间,而且可以选择获取哪个对象的锁).但是在使用同步块机制时,过多的使用锁也会引发死锁问题,同时获取和释放也有代价. 而同步方法,它所拥有的就是该类的对象,换句话说,就是this对象,而且锁的作用域是整个方法,这可能导致锁的作用域太大,有可能导致死锁问题.同时也可能包括了不需要同步的代码块在内,也会降低程序的运行效率. 不管是同步方法

随机推荐