synchronized学习
现代软件开发中并发已经成为一项基础能力,而Java精心设计的高效并发机制,正是构建大规模应用的基础之一。本文中我们将学习synchronized关键字的基本用法。
synchronized是Java内建的同步机制,也称为Intrinsic Locking,它提供了互斥的语义和可见性,当任务要执行被synchronized关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁;同时,其他试图获取锁的线程只能等待或者阻塞在那里。
synchronized用法
synchronized可以加在普通方法前、代码块上、静态方法前、类上,加在不同的地方锁是不一样的,如下:
- 加在普通方法上,锁是当前实例对象;
private synchronized void f(){
// doSomething
}
注意:synchronized关键字是不能继承的,也就是说,基类的方法 synchronized fun(){} 在继承类中并不自动是 synchronized fun(){} ,而是变成了 fun(){} 。继承时,需要显式的指定它的某个方法为 synchronized 方法。
- 加在静态方法和类上,锁是当前类的class对象;
public synchronized class F{
// doSomething
}
public class E{
public static synchronized void f(){
// doSomething
}
}
- 同步方法块,锁是括号里面的对象,可以是普通对象,也可以是class对象;
public class F{
public void f(){
synchronized(this){
// doSomething
}
}
public void e(){
synchronized(Object.class){
// doSomething
}
}
}
我们先看一个简单的示例:
public class SynLockTest {
private static int index = 0;
public static void runTest() {
Thread t1 = new Thread(new Runnable() {
public void run() {
for(int i=0 ; i<1000 ; i++) {
synchronized(SynLockTest.class) {
index++;
}
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
for(int i=0 ; i<1000 ; i++) {
synchronized(SynLockTest.class) {
index++;
}
}
}
});
t1.start();
t2.start();
}
public static void main(String[] args) throws Exception{
runTest();
Thread.sleep(2000);
System.out.println(index);
}
}
如上代码中,主线程会启动两个子线程(t1、t2),每个线程的任务是一样的,都是对共享变量index自增1000次,接着主线程休眠2s,再输出index的值,代码中对自增操作进行了同步(synchronized代码块包围),同步锁是SynLockTest这个类的class对象,最终程序输出结果将是2000,如果这里不进行同步或者将同步代码块中的锁改为this,输出结果大多数情况下应该是小于2000的,这是为什么呢?
首先,Java中的自增操作并不是一次完成的,虚拟机在执行的时候首先要读取index的值,然后将index的值加1,最后将index的值更新,这三步是分开进行的,如果线程t1读取了index的值,这时候线程t1的时间片用完了,被挂起,t2开始执行。。。吭哧吭哧一堆自增,结束之后,t1继续执行,这时t1进行一次自增之后会更新index的值,注意,这里更新的是t1之前所持有的index的值,相当于把t2刚才所做的操作全部覆盖了,相当于t2白做了,所以最终输出结果小于2000,因为部分自增的结果被覆盖了。
再说把锁换成this之后,这时虽然自增操作虽然被同步块保护了,但是这里获取的锁是匿名类这个对象(Runnable)的锁,而t1和t2中的这个匿名类是不一样的(都是new出来的),所以并没有互斥效果,也就相当于和没有加锁一个效果。
synchronized作用
synchronized的作用是通过互斥来实现线程安全,关于线程安全,需要保证几个基本特性,本文简单介绍一下(详细可以参考Java内存模型一文):
- 原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
- 可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile就是负责保证可见性的。
- 有序性,是保证线程内串行语义,避免指令重排等。
关于原子性,可参考如上例子,在自增操作上加上同步控制,保证同一时刻只能有一个线程执行自增操作,并且执行的过程不会被其他线程打断。
关于可见性,线程在获取到锁时,JVM会把该线程对应的本地内存置为无效,并且会从主内存中读取共享变量。线程释放锁时,JVM会把该线程对应的本地内存中的共享变量立即刷新到主内存中。通过这种方式来保证变量的可见性。
关于有序性,被同步的代码,同一时刻只能有一个线程会执行,而Java本身是能保证这一点的(线程内表现为串行的语义,Within-Thread As-If-Serial Sematics),所以说在这个层面上synchronized是能实现有序性的。
这一部分关于synchronized如何实现原子性、可见性、有序性只是简单介绍,后面会从底层实现来详细总结synchronized是如何实现这些功能的。
总结
- synchronized的基本用法,修饰代码块,以及分别加什么锁;
- synchronized的作用,可以保证原子性、可见性和有序性的;
综上,synchronized是万精油,使用起来很方便,直接在要保护的代码块上加上synchronized修饰即可,可读性很高。虽然早期synchronized的性能问题多为人诟病,但是现代JDK对synchronized进行了很大优化,在通用场景下,我们无需过多关注这点。因此,一般以synchronized关键字入手,只有在性能调优时才考虑替换为Lock对象或采用原子类。
本文只是简单总结了synchronized的用法及作用,并未涉及其底层原理,这部分内容会在后面撰文详述。
synchronized学习的更多相关文章
- Java Keyword Synchronized 学习记录
Synchronized Java编程思想:每个对象都包含了一把锁(也叫作"监视器"),它自动成为对象的一部分,调用任何synchronized方法时,对象就会被锁定,不可再调用那 ...
- synchronized学习笔记
概述 我们都知道加锁的目的就是:序列化访问临界资源,即同一时刻只能有一个线程访问临界资源(同步互斥访问).在java对象中,每一个对象有且只有一个同步锁.这也意味着,同步锁依赖于对象而存在,当我们访问 ...
- ReentrantLock学习
对于并发工作,你需要某种方式来防止两个任务访问相同的资源,至少在关键阶段不能出现这种冲突情况.防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁.在前面的文章--synchronized学习中, ...
- 码农会锁,synchronized 对象头结构(mark-word、Klass Pointer)、指针压缩、锁竞争,源码解毒、深度分析!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 感觉什么都不会,从哪开始呀! 这是最近我总能被问到的问题,也确实是.一个初入编程职场 ...
- ThreadLocal使用和原理简析
1. 解决共享资源冲突 对于并发工作,需要某种方式来防止两个任务同时访问相同的资源,至少在关键阶段不能出现这种冲突情况. 方法之一就是当资源被一个任务使用时,在其上加锁.第一个访问某项资源的任务必须锁 ...
- synchronized底层实现学习
上文我们总结了 synchronized 关键字的基本用法以及作用,并未涉及 synchronized 底层是如何实现的,所谓刨根问底,本文我们就开始 synchronized 原理的探索之旅吧(*& ...
- 理解java关键字Synchronized(学习笔记)
之前学习了线程的一些相关知识,今天系统的总结下来 目录 1. Java对象在堆内存中的存储结构 2. Monitor管程 3. synchronized锁的状态变换以及优化 4. synchroniz ...
- Java多线程学习(二)synchronized关键字(2)
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79670775 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- Java多线程学习(二)synchronized关键字(1)
转载请备注地址: https://blog.csdn.net/qq_34337272/article/details/79655194 Java多线程学习(二)将分为两篇文章介绍synchronize ...
随机推荐
- Spark环境搭建(五)-----------Spark生态圈概述与Hadoop对比
Spark:快速的通用的分布式计算框架 概述和特点: 1) Speed,(开发和执行)速度快.基于内存的计算:DAG(有向无环图)的计算引擎:基于线程模型: 2)Easy of use,易用 . 多语 ...
- Linux虚拟机搭建本地yum源
Yum本地源的配置 本教程是在虚拟机里安装Red Hat Enterprise Linux 7 ,以其为例使用iso文件进行Yum本地源的配置.所使用的软件如下: (1)虚拟机:Vmware work ...
- NOIP2008 立体图
题目描述 小渊是个聪明的孩子,他经常会给周围的小朋友们将写自己认为有趣的内容.最近,他准备给小朋友们讲解立体图,请你帮他画出立体图. 小渊有一块面积为m*n的矩形区域,上面有m*n个边长为1的格子,每 ...
- # xrdp 在linux deploy 折腾记录
xrdp 在linux deploy 折腾记录 1. xrdp connect fail vnc 不管用tiger or vnc4server都可以正常用,但xrdp无法连接 netstat -tnl ...
- Win 10 Revit 2019 安装过程,亲自踩的一遍坑,有你想要的细节
首先就是安装吖,不管是管理员权限还是普通权限,都是以下这个问题,跟权限没关系 failed to load .....revitcontentpackui.dll (126) 尝试了网上能查到的各种方 ...
- myeclipse中的HTML页面在浏览器中显示为乱码
myeclipse中的HTML页面在浏览器中显示为乱码 在通过myeclipse开发项目的过程中,如果用HTML页面书写前端,可能出现中文乱码现象,需要怎么解决呢?下面是我从网上搜的方法: 解决办法: ...
- h5的video下载按钮如何隐藏
h5的video下载按钮在部分手机系统上是不可用的,想要把这个按钮屏蔽掉,可苦于没有像屏蔽全屏按钮那样的伪类video::-webkit-media-controls-fullscreen-butto ...
- Flutter 页面入栈和出栈
Docs demo import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp ...
- layui 表格在排序之后没有重新渲染问题
问题描述: 在layui表格中,最后一列增加了操作按钮,并且在某些行设置了样式,但是在排序之后,按钮的点击事件失效了,样式也没有了,可能是没有执行done回调 原因: done回调只有在render和 ...
- CentOS6 安装 MySQL5.7
CentOS 6.10 编译安装 Mysql 5.7.23 X64 1.添加用户组和用户 1) 添加用户组和用户 groupadd mysql 2) 添加用户 useradd -g mysql -s ...