同步和锁定

Java中每个对象都有一个内置锁。

1、synchronize

当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

释放锁是指持锁线程退出了synchronized同步方法或代码块。

关于锁和同步的几个要点

  1. 只能同步方法,而不能同步变量和类;
  2. 每个对象只有一个锁;当提到同步时,应该清楚在哪个对象上同步。
  3. synchronized 锁的是对象,在应用数据共享时,要保证被锁的对象不要发生变化。
  4. 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
  5. 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
  6. 线程睡眠时,它所持的任何锁都不会释放。
  7. 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。synchronized (this) 和 用synchronized修饰方法 有相同的效果,例如:

public int add(int m) {  synchronized (this) {  n = m + n;  }return n;
}

等同于

public synchronized int add(int m) {  n = m + n;return n;
}

静态方法同步

同步静态方法,需要一个类对象的锁,即Class对象

public static int add(int m) {  synchronized (Test.class) {  n = m + n;  }return n;
}

等同于

public static synchronized int add(int m) {  n = m + n;return n;
}

什么情况下,线程无法获得锁?

当线程A试图调用同步方法或同步代码块,要清楚这个同步是在哪个对象上的锁,如果此时锁被线程B占用,那么线程A在该对象上被阻塞,等待,直到锁被释放,线程A再次变为可运行或运行状态。

  • 对于非静态的同步方法,当然是,调用同一个对象的同步方法会被阻塞,调用不同对象的同步方法不会被阻塞
  • 对于静态的同步方法,都是在Class对象上加锁,调用它的同步方法当然会彼此阻塞

下面的例子可以帮助理解。两个不同的线程thread1和thread2,想同时操作Worker里面的index

public static void main(String[] args) throws InterruptedException {Worker worker = new Worker(5); // 下面两个线程用的是同一个对象workerThread thread1 = new Thread(worker);Thread thread2 = new Thread(worker);thread1.start();thread2.start();
}private static class Worker implements Runnable {private Integer index;public Worker(Integer index) {this.index = index;}@Overridepublic void run() {// 这里的this,表示当前对象,不同的实例,this指代的对象是不同的synchronized (this) {index++;System.out.println(Thread.currentThread().getName() + "-------" + index);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "-------" + index + ", END");}}
}

synchronized (this),即对Worker对象加锁,打印结果如下:

Thread-0-------6
Thread-0-------6, END
Thread-1-------7
Thread-1-------7, END

上面new Thread用的是同一个worker对象,那么针对两个线程,一个获得对象锁后,另一个必然阻塞。

2、volatile关键字

  • 保证了不同线程对这个变量进行操作时的可见性。
    即一个线程修改了某个变量的值, 这新值对其他线程来说是立即可见的。
  • 不保证原子性
    即不能保证数据在多个线程下同时写时的线程安全

volatile最适用的场景: 一个线程写, 多个线程读
一个线程在修改某个变量的值,其他线程来读取该变量的值都是实时可见的。

3、ThreadLocal

1. 介绍

规避线程不安全的方法,除了加锁之外,还可以使用ThreadLocal 。
如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。可理解为:线程局部变量。

2. ThreadLocal使用

  public class UseThreadLocal {// int型ThreadLocal变量private static ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return 100;}};// String型ThreadLocal变量private static ThreadLocal<String> stringThreadLocal;// 开启线程public void StartThread(){Thread thread1 = new Thread(new TestRunnable(1));thread1.start();Thread thread2 = new Thread(new TestRunnable(2));thread2.start();}public static class TestRunnable implements Runnable{public int id;public TestRunnable(int id){this.id = id;}public void run() {System.out.println(Thread.currentThread().getName()+", intLocal.get:" + intLocal.get());Integer i = intLocal.get();i = i + id;intLocal.set(i);System.out.println(Thread.currentThread().getName() +", after set:"+ intLocal.get());// ThreadLocal变量不再使用时,须removeintLocal.remove();}}public static void main(String[] args){UseThreadLocal test = new UseThreadLocal();test.StartThread();}}

输出结果:

Thread-0, intLocal.get:100
Thread-1, intLocal.get:100
Thread-0, after set:101
Thread-1, after set:102

3. ThreadLocal的实现原理

(1) Thread类

主要看它的成员变量 threadLocals

  • 类型:ThreadLocal.ThreadLocalMap(类似于HashMap),key是ThreadLocal,value是set的值
  • 初始化:此变量初始为null,只有调用ThreadLocal.set或get方法时,才会创建它
  • 赋值:ThreadLocal.set()、get()、remove()
  • 作用:存放该线程的ThreadLocal类型的本地变量
  • 注意:不使用本地变量时,需要调用remove方法将此线程在threadLocals中的本地变量删除

(2)ThreadLocal类
这里看下set()、get()、remove()方法源码

set()

 public void set(T value) {// (1) 获取当前线程(调用者线程)Thread t = Thread.currentThread();// (2) 获取Thread的成员变量threadLocalsThreadLocalMap map = getMap(t);if (map != null) {//  (3) 直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为valuemap.set(this, value);} else {// (4) 当map为null,说明首次添加,需要首先创建出对应的mapcreateMap(t, value);}}ThreadLocalMap getMap(Thread t) {// 获取Thread的成员变量threadLocalsreturn t.threadLocals; }void createMap(Thread t, T firstValue) {// 创建threadLocalst.threadLocals = new ThreadLocalMap(this, firstValue);}

get()

 public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);T result = (T)e.value;return result;}return setInitialValue();}private T setInitialValue() {T value = initialValue(); // new ThreadLocal时会重写initialValue()进行赋值Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}

remove()

 public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}

ThreadLocalMap
ThreadLocalMap有一个内部类Entry,有一个Entry[]类型的变量,这个数组可以保存多个Entry对象。
值得注意的是,Entry持有ThreadLocal对象的弱引用。

弱引用:只要GC时,弱引用就会被回收。

因此,当GC时,Entry指向的ThreadLocal对象会被回收,那么Entry的key不在了,其value永远不会被访问,内存将暴增。因此,对于不再使用的线程本地变量,应及时remove。

Java线程同步和锁定相关推荐

  1. java线程 同步与异步 线程池

    1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线 程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有时为了解 决此问题,优先考虑 ...

  2. java线程同步——条件对象+synchronized 关键字

    [0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java线程同步--条件对象+synchronized 关键字 的相关知识: 0.2)for ...

  3. Java --- 线程同步和异步的区别

    1. Java 线程 同步与异步 多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有 ...

  4. Java线程--同步和异步的区别

    本文转自https://blog.csdn.net/u011033906/article/details/53840525 1. Java 线程 同步与异步 多线程并发时,多个线程同时请求同一个资源, ...

  5. Java线程同步内存实现

    Java线程同步内存实现 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何生成可 ...

  6. (转) Java线程同步阻塞, sleep(), suspend(), resume(), yield(), wait(), notify()

    为了解决对共享存储区的访问冲突,Java 引入了同步机制.但显然不够,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个. 为解决访问控制问题,Java ...

  7. 关于java线程同步的笔记_线程同步(JAVA笔记-线程基础篇)

    在多线程应用程序中经常会遇到线程同步的问题.比如:两个线程A.线程B可能会 "同时" 执行同一段代码,或修改同一个变量.而很多时候我们是不希望这样的. 这时候,就需要用到线程同步. ...

  8. java线程同步——竞争条件的荔枝+锁对象

    [0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java线程同步--竞争条件的荔枝+锁对象 的相关知识: 0.2) for full sou ...

  9. Java线程同步-模拟买票

    文章首发于 2020-11-29 知乎文章:Java线程同步-模拟买票 作者:落雨湿红尘(也是我o) 01 导语 本文使用JAVA代码模拟买票场景下的业务交互,通过示例讲解线程的初始化.线程同步等ja ...

  10. Java线程同步的几种方式

    Java线程同步的几种方式 1.使用synchronized关键字  它的工作是对同步的代码加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全性.  synchronized关键字的用法: ...

最新文章

  1. 关于reference to ‘XXXX’ is ambiguous的解决办法
  2. Ground Truth
  3. 硬盘为何会丢失数据?
  4. 牛客网NOIP赛前集训营-提高组(第六场)B-选择题
  5. HDU 3613 Best Reward 正反两次扩展KMP
  6. php向后兼容,PHP: 不向后兼容的变更 - Manual
  7. Kubernetes 网络部分
  8. java案例代码20--斗地主V2
  9. HDU-5889 Barricade
  10. 小米max2怎么长截屏?
  11. Android视频桌面,动态桌面开发
  12. ERP库存管理 华夏
  13. 汉印HPRT XT130 打印机驱动
  14. Kafka报错:Couldn't find leaders for Set
  15. linux 筛选重复数据,Linux下uniq筛选
  16. c语言 dct变换,DCT, IDCT变换--C语言实现
  17. 什么是DBMS以及DBMS的分类
  18. html 实现在线选房,线上开盘选房技巧有哪些
  19. SQL中的连接(左、右、内连接)
  20. 月下夜想曲200.6(攻略1)

热门文章

  1. 怎么注册DLL到注册表
  2. linux卸载+oracle客户端,官方的linux上卸载oracle步骤
  3. 大神u盘工具(win10PE)UEFI纯净版启动盘制作工具
  4. hough变换检测圆周_一种利用Hough变换的圆形目标检测方法与流程
  5. 人行地区代码一览表_全国各地区代码.
  6. 教你用Appium搭建Android自动化测试框架(详细教程)
  7. JavaScript制作标准计算器
  8. OV2640摄像头模块
  9. EC20模块GPGGA协议
  10. android 输入模糊匹配_Android 模糊搜索