JAVA线程安全学习笔记之线程安全
Posted 诗萧尘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA线程安全学习笔记之线程安全相关的知识,希望对你有一定的参考价值。
问题: 线程安全,线程同步。为什么发生线程安全,线程同步问题。如何解决?
核心思想: 上锁。 代码从哪里上锁?----可能会发生线程安全的地方进行上锁。
通俗的讲就是我们更改数据的地方。那 是锁方法,锁类,锁代码块 ?
锁:分布式锁、公平锁,非公平锁、重入锁、悲观锁、乐观锁。
锁的机制:
在同一个JVM中,多个线程需要竞争锁资源。多个线程同时抢同一把锁,谁拿到锁资源,谁执行相关代码。
如果没有获取成功,中间需要经历锁升级过程,如果一直没有获取到锁就一直阻塞等待。
加锁的缺点:可能影响到程序的执行效率。
加锁的代码:
2、使用 synchronized 关键字,完成上述功能。
注意:synchronized 获取锁与释放锁都由底层虚拟机实现好了。
在多线程的情况下 需要是同一个对象锁:且对象锁不能重复 Synchronized( 对象锁 ) 需要保证线程安全的代码3、synchronized 3种用法。
1.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码快前要获得 给定对象 的锁。
对象可以是。this, 也可以是任意对象。
2.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得 当前实例 的锁,
加在实例方法上使用的是 this 锁。
加在静态方法上使用的是 当前类名.class
3.修饰静态方法,作用于当前类对象(当前类.class)加锁,进入同步代码前要获得 当前类对象 的锁
/** 同一把锁 **/
如果是同一把锁 在多线程的情况下 最终只能够给一个线程使用。
如果有线程持有了该锁 意味着其他的线程 不能够在继续获取锁。
核心思想:当多个线程共享同一个全局变量时,将可能会发生线程安全的代码
上锁,保证只有拿到锁的线程才可以执行,没有拿到锁的线程不可以执行,需要阻塞等待。
死锁:
线程1 | 线程2 |
先获取到自定义对象的lock锁 | 先获取this锁 |
需要线程2已经持有的this锁 | 线程1已经持有自定义对象的lock锁 |
代码
synchronized 的死锁问题。
public class DeadlockThread implements Runnable
private int count = 1;
private String lock = "lock";
@Override
public void run()
while (true)
count++;
if (count % 2 == 0)
// 线程1需要获取 lock 在获取 a方法this锁
// 线程2需要获取this 锁在 获取B方法lock锁
synchronized (lock)
a();
else
synchronized (this)
b();
public synchronized void a()
System.out.println(Thread.currentThread().getName() + ",a方法...");
public void b()
synchronized (lock)
System.out.println(Thread.currentThread().getName() + ",b方法...");
public static void main(String[] args)
DeadlockThread deadlockThread = new DeadlockThread();
Thread thread1 = new Thread(deadlockThread);
Thread thread2 = new Thread(deadlockThread);
thread1.start();
thread2.start();
检测死锁的工具:
synchronized 死锁诊断工具
jconsole.exe 默认在 jdk 安装目录的 bin 文件夹下
线程1 先获取到自定义对象的lock锁,进入到a方法需要获取this锁
线程2 先获取this锁, 进入到b方法需要自定义对象的lock锁
线程1 线程2 是在同时执行
线程同步:
- 使用synchronized锁,JDK1.6开始 锁的升级过程
-
使用Lock锁 ,需要自己实现锁的升级过程。底层是基于aqs实现
-
使用Threadlocal,需要注意内存泄漏的问题。
-
手写 原子类 cas 非阻塞式锁。
有个升级过程记录下:
synchronized 偏向锁--> 轻量级锁(cas)-->重量级锁。
多线程通信
多线程之间通信:
等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法如下:
1.notify() :通知一个在对象上等待的线程,使其从main()方法返回,而返回的前提是该线程获取到了对象的锁
2.notifyAll():通知所有等待在该对象的线程
3.wait():调用该方法的线程进入WAITING状态,只有等待其他线程的通知或者被中断,才会返回。需要注意调用wait()方法后,会释放对象的锁 。
代码例子:
public class Thread03 extends Thread
@Override
public void run()
try
synchronized (this)
System.out.println(Thread.currentThread().getName() + ">>当前线程阻塞,同时释放锁!<<");
this.wait();
System.out.println(">>run()<<");
catch (InterruptedException e)
public static void main(String[] args)
Thread03 thread = new Thread03();
thread.start();
try
Thread.sleep(3000);
catch (Exception e)
synchronized (thread)
// 唤醒正在阻塞的线程
thread.notify();
多线程通讯实现生产者与消费者
public class Thread04
class Res
/**
* 姓名
*/
private String userName;
/**
* 性别
*/
private char sex;
/**
* 标记
*/
private boolean flag = false;
class InputThread extends Thread
private Res res;
public InputThread(Res res)
this.res = res;
@Override
public void run()
int count = 0;
while (true)
synchronized (res)
//flag = false 写入输入 flag = true 则不能写入数据 只能读取数据
try
// 如果flag = true 则不能写入数据 只能读取数据 同时释放锁!
if (res.flag)
res.wait();
catch (Exception e)
if (count == 0)
this.res.userName = "余胜军";
this.res.sex = '男';
else
this.res.userName = "小薇";
this.res.sex = '女';
res.flag = true;
res.notify();
count = (count + 1) % 2;
class OutThread extends Thread
private Res res;
public OutThread(Res res)
this.res = res;
@Override
public void run()
while (true)
synchronized (res)
try
if (!res.flag)
res.wait();
catch (Exception e)
System.out.println(res.userName + "," + res.sex);
res.flag = false;
res.notify();
public static void main(String[] args)
new Thread04().print();
public void print()
Res res = new Res();
InputThread inputThread = new InputThread(res);
OutThread outThread = new OutThread(res);
inputThread.start();
outThread.start();
个人理解:
wait()和 notify() : wait 阻塞当前线程,并且释放锁。notify() 唤醒对应释放锁的线程。 应用场景可以用于并行计算,A,B 同时计算。 A计算完毕,需要B 的结果时,阻塞当前线程,并释放锁,返回B 线程中计算完毕后唤醒A线程。
在 spring mvc controller 中使用 sychronized 关键字。
需要注意:
Spring MVC Controller默认是单例的 需要注意线程安全问题
单例的原因有二:
1、为了性能。
2、不需要多例。
3、省内存。
设置多例
@Scope(value = "prototype") 设置为多例子。
@RestController
@Slf4j
//@Scope(value = "prototype")
public class CountService
private int count = 0;
@RequestMapping("/count")
public synchronized String count()
try
log.info(">count<" + count++);
try
Thread.sleep(3000);
catch (Exception e)
catch (Exception e)
return "count";
以上是关于JAVA线程安全学习笔记之线程安全的主要内容,如果未能解决你的问题,请参考以下文章