java并发编程:线程同步和锁
一、锁的原理
java中每个对象都有一个内置锁。当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this)有关的锁。获得一个对象的锁也称为获取锁,当程序运行到synchronized同步方法或代码块时该对象的锁才起作用。
一个对象只有一个锁。所以,只能被一个线程获取,其他线程要想获取锁,必须等到这个线程释放锁。就是意味着其他线程不能进入该对象上的synchronized方法或代码块,直到锁被释放。释放锁就是指持有锁的线程退出了synchronized方法或代码块。
关于锁和同步,有几个要点:
- 只能同步方法,而不能同步变量和类;
- 每个对象只有一个锁;当提到同步时,应该清楚再什么上同步?也就是说,在那个对象上同步
- 不比同步类中所有的方法,类可以同时拥有同步和非同步方法
- 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需等待,直到锁被释放。也就是说:如果一个线程在对象上获取一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法
- 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制
- 线程睡眠时,它所持的任何锁都不会释放
- 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁
- 同步损害并发性,应尽可能的缩小同步的范围。尽量使用同步代码块
- 在使用同步代码块的时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁
1.1、静态方法同步
静态优先于对象加载,所以静态方法的同步锁就是这个对象的class
1.2、如果线程不能获取锁会怎么样
如果线程要进入同步方法,而其锁已被占用,则线程在该对象上被阻塞。实际上,线程进入该对象的一种池中,必须在那里等待,知道锁被释放,该线程再次变为可运行
当考虑阻塞时,一定要搞清楚持有哪个对象锁
- 调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象锁,线程之间互不干预
- 调用同一个类中的静态同步方法的线程将彼此阻塞,他们的对象锁相同,为当前class
- 静态同步方法和非静态同步方法之间不会彼此阻塞,因为静态锁是class,非静态的是当前对象
- 对于同步代码块,要看清楚对象锁。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永不会彼此阻塞
1.3、什么时候需要同步
在多个线程同时访问并需要更改数据时,应该同步以保护数据,确保两个线程不会同时修改它
1.4、线程安全类
当一个类用了同步措施来保护数据时,这个类就是“线程安全”的。即使是线程安全类,也应该特别小心
public class NameList {
private List nameList = Collections.synchronizedList(new LinkedList()); public void add(String name) {
nameList.add(name);
} public String removeFirst() {
if (nameList.size() > 0) {
return (String) nameList.remove(0);
} else {
return null;
}
}
}
public class Test {
public static void main(String[] args) {
final NameList nl = new NameList();
nl.add("aaa");
class NameDropper extends Thread{
public void run(){
String name = nl.removeFirst();
System.out.println(name);
}
} Thread t1 = new NameDropper();
Thread t2 = new NameDropper();
t1.start();
t2.start();
}
}
虽然集合对象private List nameList = Collections.synchronizedList(new LinkedList());是同步的,但是程序还不是线程安全的。出现这种事件的原因是,上例中一个线程操作列表过程中无法阻止另外一个线程对列表的其他操作。解决上面问题的办法是,在操作集合对象的NameList上面做一个同步。改写后的代码如下:
public class NameList {
private List nameList = Collections.synchronizedList(new LinkedList()); public synchronized void add(String name) {
nameList.add(name);
} public synchronized String removeFirst() {
if (nameList.size() > 0) {
return (String) nameList.remove(0);
} else {
return null;
}
}
}
1.5、线程死锁
当两个线程都被阻塞,每个线程都在等待获取另一个线程的锁时就发生了死锁
public class DeadlockRisk {
private static class Resource {
public int value;
} private Resource resourceA = new Resource();
private Resource resourceB = new Resource(); public int read() {
synchronized (resourceA) {
synchronized (resourceB) {
return resourceB.value + resourceA.value;
}
}
} public void write(int a, int b) {
synchronized (resourceB) {
synchronized (resourceA) {
resourceA.value = a;
resourceB.value = b;
}
}
}
}
死锁的根本原因就是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环;
避免的方法就是避免同步的嵌套使用,指定获取锁的顺序
二、总结
1、线程同步的目的是为了保护多个线程访问一个资源造成数据的不确定性
2、同步是同过锁来实现,每个对象有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法
3、对于静态同步方法,锁是这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象的锁
4、对于同步,要时刻清醒在哪个对象上同步,这是关键
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和完全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞
7、死锁是线程间相互等待锁而造成的
java并发编程:线程同步和锁的更多相关文章
- Java并发编程:同步容器
Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...
- 【转】Java并发编程:同步容器
为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...
- 8、Java并发编程:同步容器
Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...
- Java并发包——线程同步和锁
Java并发包——线程同步和锁 摘要:本文主要学习了Java并发包里有关线程同步的类和锁的一些相关概念. 部分内容来自以下博客: https://www.cnblogs.com/dolphin0520 ...
- java并发编程 线程基础
java并发编程 线程基础 1. java中的多线程 java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动 public class OnlyMain { ...
- Java 并发编程 | 线程池详解
原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...
- Python并发编程-线程同步(线程安全)
Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...
- Java并发编程实战 03互斥锁 解决原子性问题
文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和 ...
- java并发:线程同步机制之Lock
一.初识Lock Lock是一个接口,提供了无条件的.可轮询的.定时的.可中断的锁获取操作,所有加锁和解锁的方法都是显式的,其包路径是:java.util.concurrent.locks.Lock, ...
- Java并发编程:线程间通信wait、notify
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
随机推荐
- jQuery Validate 验证成功时的提示信息
大多数时候我们使用validate进行前台验证的时候,都是验证错误的时候给出提示信息,最近在做一个项目的时候,想这验证成功后给出一个验证成功的提示.于是在网上找了一些资料. <!doctype ...
- windows spark1.6
jdk1.7 scala 2.10.5 spark 1.6.1 http://spark.apache.org/downloads.html hadoop 2.6.4 只需要留bin https:// ...
- What is the !! (not not) operator in JavaScript?
What is the !! (not not) operator in JavaScript? 解答1 Coerces强制 oObject to boolean. If it was falsey ...
- 快读模板&&快出模板
inline int read() { ,b=; char c=getchar(); ') { if(c=='-') b=-; c=getchar(); } ') { a=(a<<)+(a ...
- Android之View的绘制流程
本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定实现细 ...
- leetcode 123. 买卖股票的最佳时机 III
使用动态规划的解法,空间复杂度O(2*2)如果交易k次则为O(2*k),时间复杂度O(2n),交易k次为O(n*k), 因此本题实际上可以退化为买卖一次的情况:去掉buy2和sell2,即leetco ...
- python - re正则匹配模块
re模块 re 模块使 Python 语言拥有全部的正则表达式功能. compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象.该对象拥有一系列方法用于正则表达式匹配和替换. re ...
- rocketMQ 订阅关系
场景:2 个消费者进程中,创建了 2 个消费者,同属于 1 个消费组,但是订阅了不同的 topic,会因为订阅信息相互覆盖,导致拉不到消息. 原因是 rocketMQ 的订阅关系,是根据 group ...
- jqGrid整理笔记
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- 微信分享图标设置,以及wx.config配置
最近公司要求我做一个关于页面分享微信显示小图和描述的功能,由于之前没有做过,所以说是从零开始,看jssdk说明文档,网上搜索各种资料,甚至连三四年前的内容都搜索出来了,也试过以前的简单方法,包括在页面 ...