java synchronized关键字的用法

作者:wulei 时间:2022-11-19 13:45:25 

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的方法之一就可以解决了.

标签:java,synchronized
0
投稿

猜你喜欢

  • 深入了解c# 迭代器和列举器

    2022-04-24 16:32:37
  • Android编程设计模式之备忘录模式详解

    2023-06-29 14:55:15
  • Java基础之Web服务器与Http详解

    2021-08-13 16:39:42
  • maven scope provided和runtime的例子说明

    2023-04-28 02:15:00
  • tcp、udp、ip协议分析_动力节点Java学院整理

    2023-05-17 18:00:17
  • Java内存区域管理详解

    2023-11-10 23:44:42
  • Spring源码解密之自定义标签与解析

    2023-11-25 01:11:34
  • Springboot中如何使用Jackson

    2021-07-29 03:27:34
  • 轻松学习C#的异常处理

    2022-09-14 22:10:20
  • Java中JUC 的 Exchange 交换器详情

    2023-09-17 18:46:40
  • Java之Rsync并发迁移数据并校验详解

    2023-07-17 23:01:22
  • Spring Boot 整合 Apache Dubbo的示例代码

    2021-10-09 03:52:07
  • Java框架解说之BIO NIO AIO不同IO模型演进之路

    2021-06-20 22:56:31
  • java使用计算md5校验码方式比较两个文件是否相同

    2023-05-10 23:43:16
  • mybatis多个plugins的执行顺序解析

    2021-12-09 18:53:27
  • 理解Java当中的回调机制(翻译)

    2023-03-15 04:21:00
  • springboot+springmvc实现登录拦截

    2023-04-26 19:23:26
  • springmvc+shiro自定义过滤器的实现代码

    2021-08-11 21:23:11
  • Java负载均衡算法实现之轮询和加权轮询

    2023-07-16 15:27:17
  • Android实现左侧滑动菜单

    2022-10-10 14:58:41
  • asp之家 软件编程 m.aspxhome.com