造成线程不安全的原因有哪些呢?
- 抢占式执行,调度过程随机(也是万恶之源,无法解决)
- 多个线程同时修改同一个变量(可以适当调整代码结构,避免这种情况)
- 针对变量的操作,不是原子的(加锁,synchronized)
- 内存可见性,一个线程频繁读,一个线程写(使用volatile)
- 指令重排序(使用synchronized)
解决方法:
1)使用synchronized关键字以及synchronized的基本使用
synchronized的本质操作,是修改了Object对象中的“对象头”里面的一个标记,只有当两个线程同时针对一个对象加锁,才会产生竞争
把synchronized加到普通的方法上,也就相当于把锁对象指定为this了
把synchronized加到代码块上,就需要手动锁定,针对那个对象加锁
把synchronized加到静态方法上,所谓的“静态方法”更严谨的叫法,应该叫做“类方法”,普通的方法,更严谨的叫法,叫做”实例方法“?,针对类对象加锁如下:
2)synchronized的特性
1.互斥
synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到
同一个对象 synchronized 就会阻塞等待.
进入 synchronized 修饰的代码块, 相当于 加锁
退出 synchronized 修饰的代码块, 相当于 解锁
2.刷新内存~synchronized 的工作过程
1. 获得互斥锁
2. 从主内存拷贝变量的最新副本到工作的内存
3. 执行代码
4. 将更改后的共享变量的值刷新到主内存
5. 释放互斥锁3.可重入
死锁的四个必要条件:
- 互斥使用~ 一个锁被一个线程占用了之后,其他线程占用不了(锁的本质,保证原子性)
- 不可抢占~ 一个锁被一个线程占用了之后,其他线程不能把这个锁给抢走
- 请求与保持~ 当一个线程占据了多把锁之后,除非显式的释放锁,否则这些锁是始终都是被该线程所持有的
- 环路等待,等待关系(为避免环路等待,只需要约定好,针对多把锁加锁的时候,有固定的顺序即可)
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("等待结束");
}
}
- 使当前执行代码的线程进行等待. (把线程放到等待队列中)
- 释放当前的锁
- 满足一定条件时被唤醒,重新尝试获取这个锁
注意: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 的静态方法