线程安全问题:简单来说,就是在多线程的调度下,导致出现了一些随机性,随机性使代码出现 了一些bug =>线程不安全
造成线程不安全的原因有哪些呢?
1)抢占式执行,调度过程随机(也是万恶之源,无法解决)
2)多个线程同时修改同一个变量(可以适当调整代码结构,避免这种情况)
3)针对变量的操作,不是原子的(加锁,synchronized)
4)内存可见性,一个线程频繁读,一个线程写(使用volatile)
5)指令重排序(使用synchronized)
解决方法:
1)使用synchronized关键字以及synchronized的基本使用
synchronized的本质操作,是修改了Object对象中的“对象头”里面的一个标记,只有当两个线程同时针对一个对象加锁,才会产生竞争
把synchronized加到普通的方法上,也就相当于把锁对象指定为this了
把synchronized加到代码块上,就需要手动锁定,锁对象是啥(针对那个对象加锁)
把synchronized加到静态方法上,所谓的“静态方法”更严谨的叫法,应该叫做“类方法”,普通的方法,更严谨的叫法,叫做”实例方法“ ,针对类对象加锁如下:
类对象就是运行程序的时候,.class文件被加载到JVM内存中的模样
2)synchronized的特性
1.互斥
synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到
同一个对象 synchronized 就会阻塞等待.
进入 synchronized 修饰的代码块, 相当于 加锁
退出 synchronized 修饰的代码块, 相当于 解锁
2.刷新内存~synchronized 的工作过程
1. 获得互斥锁
2. 从主内存拷贝变量的最新副本到工作的内存
3. 执行代码
4. 将更改后的共享变量的值刷新到主内存
5. 释放互斥锁
3.可重入
可重入直观上来讲,就是同一个线程针对同一个锁,连续加锁了两次,如果出现了死锁,就是不可重入,如果不会死锁,就是可重入的~~
死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止
死锁的四个必要条件:
1)互斥使用~ 一个锁被一个线程占用了之后,其他线程占用不了(锁的本质,保证原子性)
2)不可抢占~ 一个锁被一个线程占用了之后,其他线程不能把这个锁给抢走
3)请求与保持~ 当一个线程占据了多把锁之后,除非显式的释放锁,否则这些锁是始终都是被该线程所持有的
4)环路等待,等待关系(为避免环路等待,只需要约定好,针对多把锁加锁的时候,有固定的顺序即可)
2)volatile关键字
volatile保证内存可见性,禁止编译器优化
volatile只是处理一个线程读,一个线程写的情况
volatile不保证原子性,也不会引起线程阻塞
3)wait和notify
由于线程之间是抢占式执行的,因此线程之间执行的顺序难以预知,因此我们需要合理的协调多个线程之间的执行先后顺序
完成这个协调工作, 主要涉及到三个方法
wait() / wait(long timeout): 让当前线程进入等待状态.
notify() / notifyAll(): 唤醒在当前对象上等待的线程.
注意: wait, notify, notifyAll 都是 Object 类的方法.
1,wait()方法
wait 做的事情:
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println("等待中");
object.wait();
System.out.println("等待结束");
}
}
1.使当前执行代码的线程进行等待. (把线程放到等待队列中)
2.释放当前的锁
3.满足一定条件时被唤醒,重新尝试获取这个锁
注意:wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.
wait 结束等待的条件:
1.其他线程调用该对象的 notify 方法.
2.wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
3.其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常
2,notify()方法:
notify 方法是唤醒等待的线程.
static class WaitTask implements Runnable {
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
while (true) {
try {
System.out.println("wait 开始");
locker.wait();
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class NotifyTask implements Runnable {
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
System.out.println("notify 开始");
locker.notify();
System.out.println("notify 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
Thread.sleep(1000);
t2.start();
}
1.方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的
其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
2.如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到")
3.在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行
完,也就是退出同步代码块之后才会释放对象锁。
notifyAll()方法
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程
wait和sleep的对比(简单了解即可)
wait用于线程之间的通信的,sleep是让线程阻塞一段时间
唯一的相同点就是都可以让线程放弃执行一段时间.
1. wait 需要搭配 synchronized 使用. sleep 不需要.
2. wait 是 Object 的方法 sleep 是 Thread 的静态方法