java-并发-同步
浏览以下内容前,请点击并阅读 声明
线程间的通信主要是通过开放对于字段以及字段引用的对象的访问权限实现,这种形式的通信非常高效,但是会产生两种可能的错误:线程冲突和内存一致性错误,防止这些错误的工具就是同步。
然而,同步可能引入线程争夺,当两个两个或者连个以上线程试图同时访问同一资源时就会发生资源线程争夺,这样会导致java运行时执行一个或多个线程时更加缓慢,甚至是暂停执行,饥饿以及活锁是线程争夺的一种形式。
线程冲突
//定义一个类
class Counter {
private int c = 0; public void increment() {
c++;
} public void decrement() {
c--;
}
public int value() {
return c;
}
}
上述代码中定义的类,如果调用increment,则c加一,调用decrement,则c减一,当多个线程同时引用同一个一个Counter实例时,就可能产生冲突。当连个线程对一个对象同时进行操作时,产生交叉,得到的是不可预料的结果,线程冲突的bug很难检测修复。
内存一致性错误
当不同的线程对于什么才是相同的数据有不一致的观点时,就会产生内存一致性错误,程序员无需知道产生内存一致性错误的原因,只需要知道如何避免这些错误。
避免内存一致性错误的关键是了解发生前关系,这种关系就是简单的保证被一个特定语句写入的内存对于另一个特定的语句是可见的。如:
//定义并初始化
int counter = 0;
....
//字段加1
counter++;
//打印字段
System.out.println(counter);
以上所有语句如果是在同一个线程内发生,则输出时认为counter为1没有什么问题,如果counter加1 和输出在两个线程内进行,那么打印的值很可能是0,因为没办法保证两个线程内的操作是互相可见的,除非程序员在两个语句执行之前建立发生前关系。 建立发生前关系的动作有多种,其中一种就是同步。
之前我们已经见识到了两种建立发生前关系的动作了:
- 当一个语句调用Thread.start方法时,所有与该语句有发生前关系的语句将与新的线程中所有执行的语句有发生前关系,所有导致新的线程产生的语句所产生的影响对于新的线程是可见的。
- 当一个线程执行完毕,并使另一个线程的Thread.join方法返回时,已经执行完毕的线程中执行的语句和调用join方法的线程的调用join方法以下的语句有发生前关系,即其影响对于以后的语句可见。
请参考Summary page of the java.util.concurrent
包查看产生发生前关系的动作列表。
同步方法
java编程语言提供两种基本的同步用法:同步方法和同步语句,后者更加复杂。
要使一个方法同步,只要在方法声明中加入synchronized关键词即可,如:
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
使方法同步的作用有两个:
- 首先,不能有两次同时调用一个对象的同步方法,当一个线程正在执行一个对象的同步方法时,其他调用该对象的同步方法的线程就会被封堵(暂停执行),直到前面的线程完成了对该对象的操作为止。
- 其次,当一个同步方法执行完毕返回时,该同步方法自动与随后被调用的同一个对象的同步方法建立发生前关系。保证了对于该对象状态的改变对于所有的线程都是可见的。
需要注意的是,构造器无法被同步,对构造器使用synchronized关键词是一个语法错误,同步构造器没有意义,因为一个对象在创建时,只有创造该对象的线程能对其进行访问。
内置的锁和同步
同步是围绕一个叫做内置锁或者监视锁的内部实体所建立的,内置锁负责同步的两个方面:加强对一个对象状态的排他访问和建立对于可见性至关重要的发生前关系。
每一个对象都有一个自身相关的内置锁,一般情况下,一个线程如果要对一个对象的字段进行排他的和一致的访问,要先请求对象的内置锁,当访问结束时再释放所。只要一个线程拥有一个锁,其他的线程就不能请求同样的锁,其他试图请求该锁的线程将会被阻止。当一个线程释放了一个内置锁,该动作和任何随后请求同样锁的动作就会建立发生前关系。
同步方法中的锁
当一个线程调用一个同步方法时就会自动请求该方法所在对象的内置锁,当方法返回时再释放其内置锁,即使是由于未捕获的异常导致的方法返回,锁也会被释放。
当一个静态的同步方法被调用时,因为静态方法与一个类相关,而不是与一个实例对象相关,所有该线程就会请求与该类相关的Class对象的内置锁,因此访问一个类的静态字段是由与实例相关的锁不同的内置锁控制的。
同步语句
除同步方法意外,另外一个创建同步代码的方法就是使用同步语句,与同步方法不同的是,同步语句必须指定提供内部锁的对象:
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
以上方法声明中需要对lastName和nameCount字段进行同步,同时需要避免同步调用其他对象的方法,没有同步语句,则仅仅是语句nameList.add(name)就需要另外再定义非同步方法。
同步语句也可以用来改善含有同步并发。例如,以下代码中类MsLunch中有两个实例字段c1和c2,所有对于这些字段的更新必须同步,而两个字段从来不会同时使用,所以一个线程在更新c1时,没有必要阻止另外一个线程对c2的更新,此时可以分别创建两个对象来提供锁。
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++;
}
}
}
再入同步
线程不能请求其他线程拥有的锁,但是可以再次请求已拥有的锁,允许一个线程多次请求同一个锁使得再入同步成为可能。一段同步代码直接或间接调用一个含有同步代码的方法,而两端同步代码使用同一个锁,这种情况就叫再入同步。没有再入同步,同步代码就要采取许多额外的预防措施以避免一个线程自己被锁定。
原子访问
编程语言中,一个原子动作就是所有一次性有效发生的动作, 原子动作不能在中间停止,要么完成,或者干脆不发生。原子动作没有完成,那么其任何影响都是不可见的。
我们之前见到的自增加表达式 i++ ,不是一个原子动作,所有的简单的表达式可以细分为多个的动作。另外一些就可以视为原子动作:
- 对于引用变量和大部分基本数据类型的变量(除了long和double之外)的读和写操作是原子的
- 对于所有声明为volatile(包括long和double)的变量的读和写操作都是原子的
原子动作不能交叉执行,因此不必担心线程的冲突问题。然而这并不表示可以省去所有的同步原子动作,因为内存一致性错误依然可能出现。使用volatile变量可减少内存一致性错误,因为所有对于一个volatile变量的写操作都将和接下来的读操作建立发生前关系。这意味着一个volatile变量的变化对于其他的线程是可见的,另外,这也意味着当一个线程读取一个volatile变量的时候,该线程不仅知道对于volatile变量的最后一次更改,而且也知道更改volatile变量的代码执行产生的影响。
使用简单的原子变量访问比通过同步代码访问这些变量更加高效,但是需要程序员花作更多工作以避免内存一致性错误,这些工作是否值得取决于程序的体量以及复杂程度。
java.util.concurrent包中的一些类提供了一些不依赖于同步的原子方法,一下的高并发对象会有介绍。
java-并发-同步的更多相关文章
- Java并发-同步容器篇
作者:汤圆 个人博客:javalover.cc 前言 官人们好啊,我是汤圆,今天给大家带来的是<Java并发-同步容器篇>,希望有所帮助,谢谢 文章如果有问题,欢迎大家批评指正,在此谢过啦 ...
- Java并发--同步容器
为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...
- Java并发—同步容器和并发容器
简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同步的容器,比如Vector.Ha ...
- Java并发——同步容器与并发容器
同步容器类 早期版本的JDK提供的同步容器类为Vector和Hashtable,JDK1.2 提供了Collections.synchronizedXxx等工程方法,将普通的容器继续包装.对每个共有方 ...
- Java并发——同步工具类
CountDownLatch 同步倒数计数器 CountDownLatch是一个同步倒数计数器.CountDownLatch允许一个或多个线程等待其他线程完成操作. CountDownLatch对象 ...
- java并发-同步容器类
java平台类库包含了丰富的并发基础构建模块,如线程安全的容器类以及各种用于协调多个相互协作的线程控制流的同步工具类. 同步容器类 同步容器类包括Vector和Hashtable,是早期JDK的一部分 ...
- JAVA并发同步互斥实现方式总结
大家都知道加锁是用来在并发情况防止同一个资源被多方抢占的有效手段,加锁其实就是同步互斥(或称独占)也行,即:同一时间不论有多少并发请求,只有一个能处理,其余要么排队等待,要么放弃执行.关于锁的实现网上 ...
- Java 并发同步工具(转)
转自:https://www.jianshu.com/p/e80043ac4115 在 java 1.5 中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如 CountDownLatch,Cy ...
- Java 并发同步器之CountDownLatch、CyclicBarrier
一.简介 1.CountDownLatch是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞 ...
- 多线程的通信和同步(Java并发编程的艺术--笔记)
1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递. 2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...
随机推荐
- FFT NNT
算算劳资已经多久没学新算法了,又要重新开始学辣.直接扔板子,跑...话说FFT算法导论里讲的真不错,去看下就懂了. //FFT#include <cstdio> #include < ...
- 【Android】Android如何一进入一个activity就弹出输入法键盘
在AndroidManife.xml中的Activity配置中加入 android:windowSoftInputMode="stateVisible|adjustResize"
- gulp工具rename
gulp 对文件批量重命名 gulp-rename重命名 var gulp = require('gulp'); var rename = require("gulp-rename" ...
- 从sum()求和引发的思考
sum()求和是一个非常简单的函数,以前我的写法是这样,我想大部分和我一样刚开始学习JS的同学写出来的也会是这样. function sum() { var total=null; for(var i ...
- Redis常用操作及客户端工具
修改redis密码 打开redis.windows.conf,找到requirepass 设置密码重启服务即可 将redis安装为windows服务,批处理如下: redis-server.exe ...
- Java基本数据类型总结
基本类型,或者叫做内置类型,是JAVA中不同于类的特殊类型.它们是我们编程中使用最频繁的类型.java是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化. 1. Java ...
- Web干货存档
今天看了某乎,介绍了web一些基本东西,讲的很好,随手留下https://www.zhihu.com/question/22689579 web开发者文档,纯干货 https://develope ...
- python 输出大文本文件
输出固定函数 >>> with open(r'd:\test.txt','r') as f: for i , v in enumerate(f): if i>10: break ...
- 出售Illustrator脚本插件面板(包含面板源码,以及面板上所有的功能源码)
出售Illustrator脚本插件面板(包含面板源码,以及面板上所有的功能源码) 购买后可提供相应的小修改,以及教你使用往这个多列面里再加上按钮功能! 这套源码可作为工作使用,也可用为新手学习AI脚面 ...
- andriod刷机
有句古话叫常在河边走,难免会翻船.对于经常刷机的Android刷友来说,难免会碰到刷机失败损坏recovery程序乃至手机无法启动的情况,也就是传说中的手机变砖块.不过刷机失败手机变砖并不是世界末日, ...