一、锁的原理

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. LeetCode 40. 组合总和 II(Combination Sum II)

    题目描述 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能 ...

  2. forms authentication原理

    细说ASP.NET Forms身份认证 asp.net 登陆验证 Form表单验证的3种方式 Understanding and Implementing ASP.NET Custom Forms A ...

  3. MySQL 建表时 date 类型的默认值设置

    在执行下面 SQL 语句时发现报错 CREATE TABLE `jc_site_access_pages` ( `access_date` date NOT NULL DEFAULT '0000-00 ...

  4. c++11多线程---线程锁(mutex)

    #include<mutex> 包含四类锁: 1      std::mutex    最基本也是最常用的互斥类 2      std::recursive_mutex  同一线程内可递归 ...

  5. Learn The Architecture Memory Management 译文

    1.概述 本文档介绍了ARMv8-A架构内存管理的关键——内存地址转换,包括虚拟地址(VA)到物理地址(PA)的转换.页表(或称地址转换表)格式以及TLBs(Translation Lookaside ...

  6. VIM速查表-转

    在linux上一直使用vim,慢慢熟悉了它的命令,才终于领悟了什么是编辑器之神. 最近抽空整理了这份速查表,收获颇丰,并分享给大家. 进入vim vim配置 移动光标 屏幕滚动 插入文本类 删除命令 ...

  7. 使用zipkin2在SpringCloud2.0环境下追踪服务调用情况

    1.目的: 使用zipkin2.0在Spring Cloud 2.0环境下,追踪服务调用情况. 2.所需组件: zipkin2.0,Spring Cloud 2.0,Eureka Server,Eur ...

  8. VSCode添加 console.log 快捷键

    file - preferences - keyboard shortcuts - keybindings.json:   添加:   {   "key": "ctrl+ ...

  9. nmon服务器监控工具的使用安装

    nmon是一个监控服务器性能的工具 目录 1.安装nmon 2.数据采集 1.安装nmon nmon是一种linux服务器性能监控工具,他还提供了很好的图表结果展示功能.本篇以centos6.5系统为 ...

  10. ProxySQL 常见表配置

    ProxySQL 常见表配置 [root@mgr1 opt]# rpm -ivh proxysql-1.4.14-1.1.el6.x86_64.rpm warning: proxysql-1.4.14 ...