一、锁的原理

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并发编程:线程同步和锁的更多相关文章

  1. Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  2. 【转】Java并发编程:同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...

  3. 8、Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  4. Java并发包——线程同步和锁

    Java并发包——线程同步和锁 摘要:本文主要学习了Java并发包里有关线程同步的类和锁的一些相关概念. 部分内容来自以下博客: https://www.cnblogs.com/dolphin0520 ...

  5. java并发编程 线程基础

    java并发编程 线程基础 1. java中的多线程 java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动 public class OnlyMain { ...

  6. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  7. Python并发编程-线程同步(线程安全)

    Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...

  8. Java并发编程实战 03互斥锁 解决原子性问题

    文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和 ...

  9. java并发:线程同步机制之Lock

    一.初识Lock Lock是一个接口,提供了无条件的.可轮询的.定时的.可中断的锁获取操作,所有加锁和解锁的方法都是显式的,其包路径是:java.util.concurrent.locks.Lock, ...

  10. Java并发编程:线程间通信wait、notify

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

随机推荐

  1. 【Linux】GDB用法详解(5小时快速教程)

    GDB是一个强大的命令行调试工具.虽然X Window提供了GDB的图形版DDD,但是我仍然更钟爱在命令行模式下使用GDB.大家知道命令行的强大就是在于,其可以形成执行序列,形成脚本. UNIX下的软 ...

  2. vue-微信浏览器左上角返回按钮拦截

    [需求] 在微信公众号开发中,有时需要对浏览器左上角返回按钮进行拦截处理相关的页面逻辑,而并不是让页面直接返回上一页,之前在这个细节点上的一直实现得不是很好.但看到京东购物公众号上的效果却实现得非常好 ...

  3. 请描述一下 BroadcastReceiver?

    BroadCastReceiver 是 Android 四大组件之一,主要用于接收系统或者 app 发送的广播事件. 广播分两种:有序广播和无序广播. 内部通信实现机制:通过 Android 系统的 ...

  4. [mysql]设置创建时间,更新时间未生效

    问题描述: 新增一条case,create_time没有自动生成创建时间,值为空 原因 : create_time字段类型是DateTime(错误)而不是TIMESTAMP(正确)  解决办法: 把c ...

  5. flutter ListView嵌套高度问题

    ListView嵌套时高度无法自适应,需要设置高度才可以显示,设置以下属性可以解决上述问题 shrinkWrap: true, physics: NeverScrollableScrollPhysic ...

  6. UML学习笔记_01_基本概念

    1.什么是UML Unified Modeling Language (UML)又称统一建模语言或标准建模语言,是始于1997年一个OMG标准,它是一个支持模型化和软件系统开发的图形化语言,为软件开发 ...

  7. 【图形学手记】Inverse Transform Sampling 逆转换抽样

    需求: 我们通过调查,得知大多数人在20岁左右初恋,以20岁为基准,以随机变量X表示早于或晚于该时间的年数,为了简单,假设X值域为[-5,5],并且PDF(X)是一个正态分布函数(当然可以为任意分布, ...

  8. CSS3实用指南 初读笔记

    1.7.1  浏览器前缀 当一个浏览器实现了一个新的属性.值或者选择器,而这个特性还不是处于候选推荐标准状态的时候,在属性前面会添加一个前缀以便于它的渲染引擎识别. CSS属性的浏览器前缀:    前 ...

  9. Unity中的动画系统和Timeline(5) Timeline

    在前面的动画,都是控制单独的物体,比如说控制一个角色的运动.而Timeline,可以对多个物体实施动画,形成过场动画,或者电影效果.比如,很多赛车游戏比赛开始前都会播放一段开场动画,围绕自己车的几个方 ...

  10. P1596 【[USACO10OCT]湖计数Lake Counting】

    可爱的题面君~~ 个人感觉这题还是很简单的,就是一个完全不加工的找联通块个数 个人解题思路是先读入,然后循环一遍,遇到水就dfs,并把这个w所在的联通块“删除”,并在答案上加一 最后输出答案 具体注释 ...