线程安全问题的原因和解决方案

1.1线程安全的概念

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线 程安全的。

1.2线程不安全的原因

1)修改共享数据

数据是一个可以被多个线程访问到的数据,即共享数据。

2)原子性

可以简单的解释成,售票系统现在有一张票,现在有一个人买了,然后数据还没有传送回去,另一个在这个时候查看发现还有一张票,就也买了,这个时候就出现了一张票卖了两次的情况,违反了原子性。

那么如何解决呢?也很简单,只需要在每次售票的时候加一把锁,在一个人进入购买界面的时候,其他人就不能购买,只能等这个人购买结束或者退出购买界面。

3)可见性

可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到

4)代码顺序性

1.3解决线程不安全问题

1.3.1synchronized 的特性

1) 互斥

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到 同一个对象 synchronized 就会阻塞等待.

进入 synchronized 修饰的代码块, 相当于 加锁

退出 synchronized 修饰的代码块, 相当于 解锁

synchronized用的锁是存在Java对象头里的。

2) 刷新内存

synchronized 的工作过程:

1. 获得互斥锁

2. 从主内存拷贝变量的最新副本到工作的内存

3. 执行代码

4. 将更改后的共享变量的值刷新到主内存

5. 释放互斥锁

3) 可重入

Java 中的 synchronized 是 可重入锁

在可重入锁的内部, 包含了 "线程持有者" 和 "计数器" 两个信息. 如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取 到锁, 并让计数器自增. 解锁的时候计数器递减为 0 的时候, 才真正释放锁.

1.3.2synchronized 使用示例

1)直接修饰普通方法: 锁的 SynchronizedDemo 对象

public class SynchronizedDemo {
    public synchronized void methond() {
   }
}

2) 修饰静态方法: 锁的 SynchronizedDemo 类的对象

public class SynchronizedDemo {
    public synchronized static void method() {
   }
}

3) 修饰代码块: 明确指定锁哪个对象

锁当前对象

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            
       }
   }
}

锁类对象

public class SynchronizedDemo {
    public void method() {
        synchronized (SynchronizedDemo.class) {
       }
   }
}

1.4 Java 标准库中的线程安全类

Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施

ArrayList

LinkedList

HashMap

TreeMap

HashSet

TreeSet

StringBuilder

但是还有一些是线程安全的. 使用了一些锁机制来控制.

Vector (不推荐使用)

HashTable (不推荐使用)

ConcurrentHashMap

StringBuffer

还有的虽然没有加锁, 但是不涉及 "修改", 仍然是线程安全的 String

1.5. volatile 关键字

1.5.1volatile 能保证内存可见性

volatile 修饰的变量, 能够保证 "内存可见性".

1.5.2volatile 不保证原子性

volatile 和 synchronized 有着本质的区别.

synchronized 能够保证原子性, volatile 保证的是内存可见性.

synchronized 既能保证原子性, 也能保证内存可见性.

1.6wait 和 notify

1.6.1wait()方法

wait 方法: 使当前执行代码的线程进行等待. (把线程放到等待队列中) 释放当前的锁 满足一定条件时被唤醒, 重新尝试获取这个锁.

wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

wait 结束等待的条件:

1)其他线程调用该对象的 notify 方法.

2)wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).

3)其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

1.6.2notify()方法

notify 方法是唤醒等待的线程. 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。

如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

1.6.3notifyAll()方法

notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.

1.6.4wait 和 sleep 的对比

理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻 塞一段时间

唯一的相同点就是都可以让线程放弃执行一段时间

1. wait 需要搭配 synchronized 使用. sleep 不需要.

2. wait 是 Object 的方法 sleep 是 Thread 的静态方法.

  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值