Java(高阶)——线程安全

Posted 风口上的猪A

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java(高阶)——线程安全相关的知识,希望对你有一定的参考价值。

多线程带来的风险

什么是线程安全

有关线程安全的定义是复杂的,但是我们通常可以这样认为:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的.

线程不安全的原因

1.原子性

我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人,如果没有任何机制保证,A进入房间之后,还没有出来,B就想进去,打断A在房间里面的隐私,这就不具有原子性,但是我们在A进这个房间之后,给房间加上一把锁,那这样就保证了A的隐私,这就保证了这段代码的原子性.这种现象也叫做同步互斥,表示操作是相互排斥的.

2.可见性

以下是主内存在工作时的示意图:

为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题.

3.代码顺序性

一段代码是这样的:
(1).去菜鸟驿站取快递
(2).去图书馆学习10分钟
(3).去菜鸟驿站寄快递
如果是在单线程情况下,JVM,CPU指令集会对其进行优化,比如,按3->1->2的方式执行也是没有问题的,可以少跑一次菜鸟驿站.这叫做指令重排序.

如何解决线程不安全的问题?

1.synchronized关键字-监视器锁monitor lock

synchronized的底层时使用操作系统的mutex lock实现的.

  • 当线程释放锁时,JVM会把线程对应的工作内存中的共享变量刷新到主内存中
  • 当线程获取锁时,JVM会把线程对应的的本地内存置为无效.从而使得被监视器保护的临界区代码必须从主内存中读取共享变量
  • synchronized用的锁是存在Java对象头里的
  • synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题
  • 同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入
//锁的 SynchronizedDemo 对象
public class SynchronizedDemo 
	public synchronized void methond() 
	
	public static void main(String[] args) 
		SynchronizedDemo demo = new SynchronizedDemo();
		demo.method(); 
// 进入方法会锁 demo 指向对象中的锁;出方法会释放 demo 指向的对象中的锁
	

public class SynchronizedDemo 
	public synchronized static void methond() 
	
	public static void main(String[] args) 
		method(); // 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放
		SynchronizedDemo.class 指向的对象中的锁
	

public class SynchronizedDemo 
	public void methond() 
	// 进入代码块会锁 this 指向对象中的锁;出代码块会释放 this 指向的对象中的锁
	synchronized (this) 
	

	public static void main(String[] args) 
		SynchronizedDemo demo = new SynchronizedDemo();
		demo.method();
	

2.volatile关键字

修饰的共享变量,可以保证可见性,部分保证顺序性

class ThraedDemo 
	private volatile int n;

对象的等待集wait set

wait()方法

wait()方法就是使线程停止运行

  1. wait()方法的作用是使当前代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将当前线程置入"预执行队列"中,并且在wait()所在的代码出停止执行,直到接到通知或被中断为之.
  2. wait()方法只能在同步方法中或同步块中调用.如果调用wait()方法时,没有持有适当的锁,就会抛出异常
  3. wait()方法执行之后,当前线程释放锁,线程与其他线程竞争重新获取锁.
public static void main(String[] args) throws InterruptedException 
	Object object = new Object();
	
	synchronized (object) 
		System.out.println("等待中...");
		object.wait();
		System.out.println("等待已过...");
	
	System.out.println("main方法结束...");

运行结果:
这样在执行到Object.wait()之后就一直等待下去,但是程序肯定不能一直这么等待下去,这个时候就需要使用另一个方法(notify())来唤醒它

notify()方法

notify()方法就是时停止的线程继续执行

  • notify()方法也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的那些其他线程,该方法向这些其他线程发出通知notify,并使它们重新获取该对象的对象锁,.如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程
  • 在notify()方法之后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁.
  • wait(),notify()必须使用在synchronized同步方法或者代码块内.

notifyAll()方法

多个线程在等待就可以用notifyAll()方法一次性唤醒所有的等待线程.

[面试题]:wait()和sleep()的对比

实际上wait()方法与sleep()方法是完全没有可比性的,前者用于线程之间的通信,后者是让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间,说白了就是放弃线程执行知识wait的一小段现象.具体总结如下:

  1. wait()方法执行前需要请求锁,而wait()执行时会先释放锁,等被唤醒时再重新请求锁,这个锁是wait()对象上的monitor lock.
  2. sleep()方法是无视锁的存在的,即之前请求的锁不会释放,即使没有锁也不会去请求锁.
  3. wait()方法是Object的方法.
  4. sleep()方法是Thread的静态方法.

以上是关于Java(高阶)——线程安全的主要内容,如果未能解决你的问题,请参考以下文章

java多线程

Java(高阶)——线程安全

Java线程同步那些事

Java线程同步那些事

增加最大房间入住人数 smack api

wait() notify()搭配synchronize的使用