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


猜你喜欢
- 背景相信很多人都使用过 start.spring.io 来初始化自己的 Spring Boot 工程,这个工具为开发者提供了丰富的可选组件,
- 本文实例讲述了Android实现跑马灯效果的方法。分享给大家供大家参考。具体如下:运行效果截图如下:直接在布局里写代码就好了:<Tex
- 每年实验课,总有同学问我,如何生成DLL、如何导出类,如何不花很多时间精力,就设计出一个给别人用的爽的功能库呢?结合这些年的实践,我们今天就
- 1.先定义一个Java对象Person:public class Person{ String name; int age; int num
- 单点登录三种方式单点登录的三种实现方式:分别为session广播机制;cookie+redis;tokensession广播机制指在一个集群
- 1. 背景Java Persistence with Hibernate 在12.2.1小节使用如下例子描述 n+1查询问题:List<
- 一、直接看效果二、直接上代码1.自定义控件部分package com.susan.project.myapplication;import
- 一.一维数组的定义1.创建数组数组定义有三种方法:int[] array1 = new int[10];//前面的int[]为数组的类型,后
- 首先介绍下JSON的定义,JSON是JavaScript Object Notation的缩写。一种轻量级的数据交换格式,具有良好的可读和便
- 在写接口实现时,有时会有多个实现类。这篇文章介绍在调用时通过传入字符串来指定具体的实现类。一.接口与实现类:// 接口public inte
- 在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现。下面LZ就其原理、实例
- 本文实例讲述了android电源信息查看方法。分享给大家供大家参考。具体如下:1. PowerTestActivity:import and
- java导出Excel通用方法的实例详解Java导出Excel通用方法,只需要一个list 集合。通用方法改进之处踊跃提出package o
- 本文实例总结了C#子线程更新UI控件的方法,对于桌面应用程序设计的UI界面控制来说非常有实用价值。分享给大家供大家参考之用。具体分析如下:一
- 一、线性布局LinearLayout有两种排序方式orientation属性值为horizontal时,内部视图在水平方向从左往右排列。or
- 网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。java.net包中J2SE的API包含有类和接口,它们提供低层
- CardView介绍CardView是Android 5.0系统引入的控件,相当于FragmentLayout布局控件然后添加圆角及阴影的效
- Excel知识点一、添加引用和命名空间添加Microsoft.Office.Interop.Excel引用,它的默认路径是C:\Progra
- 本文实例为大家分享了Java猜拳游戏的具体代码,供大家参考,具体内容如下先来看一下效果图: 首先我们创建一个Person类,这个类
- 第一步:获取存储的路径 我们用/sdcard/Android/data/包名/的路径 方便我们测试查看 String path=MyAppl