线程安全
什么是线程安全
多线程同时对同一个全局变量做修改的操作,可能会受到其他线程的干扰,就会发生线程安全性问题。当多个线程共享同一个全局变量,做写的操作时,可能会受到其他的线程干扰,发生线程安全问题。
最简单的线程安全案例:在手机银行app和ATM机上对同一个账户同时取钱
模拟一个线程安全问题
public class ThreadCount implements Runnable {
private static Integer count = 100;
@Override
public void run() {
while (count > 1) {
cal();
}
}
private void cal() {
try {
Thread.sleep(20);
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
运行结果如下:可以看到有很明显的数据冲突
因为两个线程同时对全局静态变量做修改操作count所导致的
如何解决线程安全问题(多线程如何实现同步)
核心思想:使用线程锁
缺点:可能会影响到程序的执行效率。
代码从那一块需要上锁?
-----可能会发生线程安全性问题的代码需要上锁。
当多个线程共享同一个全局变量时,将可能会发生线程安全的代码上锁,保证只有拿到锁的线程才可以执行,没有拿到锁的线程不可以执行,需要阻塞等待。
在同一个jvm中,多个线程需要竞争锁的资源,最终只能够有一个线程能够获取到锁,多个线程同时抢同一把锁,谁(线程)能够获取到锁,谁就可以执行到该代码,如果没有获取锁成功 中间需要经历锁的升级过程如果一直没有获取到锁则会一直阻塞等待。
如果线程A获取锁成功 但是线程A一直不释放锁线程B一直获取不到锁,则会一直阻塞等待。
synchronized锁的基本用法
- 在多线程的情况下
需要是同一个对象锁
- this锁是一个对象锁,我们要保证的是多个线程执行同一个对象的线程安全性问题
- 线程0 线程1 同时获取 this锁,假设线程0 获取到this ,意味着线程1没有获取到锁则会阻塞等待。等我们线程0 执行完count-- 释放锁之后 就会唤醒 线程1从新进入到获取锁的资源。
Synchronized(对象锁){
需要保证线程安全的代码
}
修饰代码块
修饰代码块,
指定加锁对象,
对给定对象加锁,
进入同步代码库前要获得给定对象的锁。
拿上边的代码为例,使用synchronized锁修饰代码块
private void cal() {
synchronized (this) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
修饰实例方法
作用于当前实例加锁,进入同步代码前要获得 当前实例的锁在实例方法上默认加上synchronized ,默认使用this锁。
private synchronized void cal() {
try {
Thread.sleep(10);
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
修饰静态方法
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得 当前类对象的锁,默认使用当前类的类名.class 锁
private static void cal() {
synchronized (ThreadCount.class) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}