Java线程同步和锁定
同步和锁定
Java中每个对象都有一个内置锁。
1、synchronize
当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
释放锁是指持锁线程退出了synchronized同步方法或代码块。
关于锁和同步的几个要点
- 只能同步方法,而不能同步变量和类;
- 每个对象只有一个锁;当提到同步时,应该清楚在哪个对象上同步。
- synchronized 锁的是对象,在应用数据共享时,要保证被锁的对象不要发生变化。
- 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
- 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
- 线程睡眠时,它所持的任何锁都不会释放。
- 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。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线程同步和锁定相关推荐
- java线程 同步与异步 线程池
1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线 程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有时为了解 决此问题,优先考虑 ...
- java线程同步——条件对象+synchronized 关键字
[0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java线程同步--条件对象+synchronized 关键字 的相关知识: 0.2)for ...
- Java --- 线程同步和异步的区别
1. Java 线程 同步与异步 多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有 ...
- Java线程--同步和异步的区别
本文转自https://blog.csdn.net/u011033906/article/details/53840525 1. Java 线程 同步与异步 多线程并发时,多个线程同时请求同一个资源, ...
- Java线程同步内存实现
Java线程同步内存实现 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何生成可 ...
- (转) Java线程同步阻塞, sleep(), suspend(), resume(), yield(), wait(), notify()
为了解决对共享存储区的访问冲突,Java 引入了同步机制.但显然不够,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个. 为解决访问控制问题,Java ...
- 关于java线程同步的笔记_线程同步(JAVA笔记-线程基础篇)
在多线程应用程序中经常会遇到线程同步的问题.比如:两个线程A.线程B可能会 "同时" 执行同一段代码,或修改同一个变量.而很多时候我们是不希望这样的. 这时候,就需要用到线程同步. ...
- java线程同步——竞争条件的荔枝+锁对象
[0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java线程同步--竞争条件的荔枝+锁对象 的相关知识: 0.2) for full sou ...
- Java线程同步-模拟买票
文章首发于 2020-11-29 知乎文章:Java线程同步-模拟买票 作者:落雨湿红尘(也是我o) 01 导语 本文使用JAVA代码模拟买票场景下的业务交互,通过示例讲解线程的初始化.线程同步等ja ...
- Java线程同步的几种方式
Java线程同步的几种方式 1.使用synchronized关键字 它的工作是对同步的代码加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全性. synchronized关键字的用法: ...
最新文章
- 关于reference to ‘XXXX’ is ambiguous的解决办法
- Ground Truth
- 硬盘为何会丢失数据?
- 牛客网NOIP赛前集训营-提高组(第六场)B-选择题
- HDU 3613 Best Reward 正反两次扩展KMP
- php向后兼容,PHP: 不向后兼容的变更 - Manual
- Kubernetes 网络部分
- java案例代码20--斗地主V2
- HDU-5889 Barricade
- 小米max2怎么长截屏?
- Android视频桌面,动态桌面开发
- ERP库存管理 华夏
- 汉印HPRT XT130 打印机驱动
- Kafka报错:Couldn't find leaders for Set
- linux 筛选重复数据,Linux下uniq筛选
- c语言 dct变换,DCT, IDCT变换--C语言实现
- 什么是DBMS以及DBMS的分类
- html 实现在线选房,线上开盘选房技巧有哪些
- SQL中的连接(左、右、内连接)
- 月下夜想曲200.6(攻略1)