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 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
随机推荐
- 移动端 iphone手机在中文情况下不执行keyup事件
问题:移动端 在
- i 是一个修饰符 (搜索不区分大小写)
什么是正则表达式? 正则表达式是由一个字符序列形成的搜索模式. 当你在文本中搜索数据时,你可以用搜索模式来描述你要查询的内容. 正则表达式可以是一个简单的字符,或一个更复杂的模式. 正则表达式可用于所 ...
- 使用KFold进行训练集和验证集的拆分,使用准确率和召回率来挑选合适的阈值(threshold) 1.KFold(进行交叉验证) 2.np.logical_and(两bool数组都是正即为正) 3.np.logical_not(bool数组为正即为反,为反即为正)
---恢复内容开始--- 1. k_fold = KFold(n_split, shuffle) 构造KFold的索引切割器 k_fold.split(indices) 对索引进行切割. 参数说明:n ...
- C语言转义字符表和ASCII码表
主要参考 http://www.51hei.com/mcu/4342.html 以及 https://www.cnblogs.com/jason207489550/p/6663444.html
- 域名查询是否注册的demo
import json import multiprocessing import threading import requests import xmltodict # 万网查询 def chec ...
- java:(监听,上传,下载)
1.监听: index.jsp: <%@ page language="java" import="java.util.*" pageEncoding=& ...
- unity shader 波动圈
c# //////////////////////////////////////////// // CameraFilterPack - by VETASOFT 2018 ///// /////// ...
- 【VS开发】使用 NuGet 管理项目库
NuGet 使用 NuGet 管理项目库 Phil Haack 无论多么努力,Microsoft 也没办法提供开发人员所需要的每一个库. 虽然 Microsoft 在全球的员工人数接近 90,000, ...
- 【安卓开发】一个简单快递查询APP实例的实现摘要
前言 做毕业设计涉及到安卓开发,决定好好学习安卓开发.在正式做毕业设计之前,有必要先设计和完成一个与毕业设计最终成果相关的demo或者说样例APP.最终毕业设计需要实现的功能包括通过调用PHP端API ...
- 模板变量设置 set 和 with
from flask import Flask,render_template app = Flask(__name__) @app.route('/') def hello_world(): ret ...