往期回顾:
❀❀多线程(一):认识和创建一个线程❀❀
❀❀多线程(二):Thread类的常见方法❀❀
synchronized解决线程不安全
一. 为什么会出现线程不安全
1. 什么是线程不安全
简单来说:如果多线程环境下代码运行的结果总是符合我们预期的,就说明这个程序是线程安全的.
2. 演示线程不安全
public class SafeThread {
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread0 = new Thread(){
@Override
public void run() {
for (; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
};
Thread thread1 = new Thread(){
@Override
public void run() {
for (; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
};
thread0.start();
thread1.start();
thread0.join();
thread1.join();
System.out.println("i = " + i);
}
}
输出结果:
可以发现,程序的输出与我们预期的输出结果不符合,当i < 10
时,程序应该结束循环,但是输出的i
值却为11
,说明程序出现问题,这就是多线程并行运行中所带来的问题.
3. 线程不安全的原因
1.线程之间抢占式执行
2.修改共享数据
3.某些操作不满足原子性
4.内存可见性(可见性: 一个线程对共享变量值的修改,能够及时地被其他线程看到)
5.指令重排序
三. 利用synchronized解决线程不安全
1.synchronized的特性
1.互斥
使用
synchronized
加锁的代码块会互斥的访问,当一个线程获得锁🔒时,其他线程运行到synchronized
会进入阻塞等待状态,直到该线程释放锁🔒资源,其他线程才能被OS来唤醒.2.内存刷新
synchronized
的工作流程:
1.获得🔒
2.将变量从主内存拷贝到工作内存
3.执行代码
4.将变量从工作内存拷贝到主内存
5.释放🔒
所以,使用synchronized
关键字可以解决内存可见性问题,除此之外,volatile
关键字也可以解决内存可见性问题3.可重入
synchronized
保证线程不会把自己锁死,即是可重入的.public class ReentrantLock { private static synchronized void fun1(){ System.out.println("fun1"); fun2(); } private static synchronized void fun2() { System.out.println("fun2"); } public static void main(String[] args) { fun1(); } }
这是因为,在
synchronized
的的内部, 包含了 “线程持有者” 和 “计数器” 两个信息.
如果线程加锁时,发现已经被占用,而占用着是自己的时候,就可以继续使用锁,并且使计数器加1,解锁的时候计数器依次递减,等于0时释放锁资源
2.synchronized解决线程不安全
对于上面的例子,可以对
i++
的操作进行加锁
public class SafeThread {
private static int i = 0;
private static final Object Lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread0 = new Thread() {
@Override
public void run() {
for (int j = 0 ; j < 5 ; ++j) {
synchronized (Lock){
i++;
System.out.println(Thread.currentThread().getName() + ": i = " + i);
}
}
}
};
Thread thread1 = new Thread() {
@Override
public void run() {
for (int j = 0 ; j < 5 ; ++j) {
synchronized (Lock){
i++;
System.out.println(Thread.currentThread().getName() + ": i = " + i);
}
}
}
};
thread0.start();
thread1.start();
thread0.join();
thread1.join();
System.out.println("i = " + i);
}
}
3.synchronized的使用
1.直接修饰普通方法
public class Synchronized {
public synchronized void methond() {
}
}
2.修饰静态方法
public class Synchronized {
public synchronized static void method() {
}
}
3.修饰代码块
1.锁当前对象
public class Synchronized {
public void method() {
synchronized (this) {
// 代码块
}
}
}
2.锁类对象
public class Synchronized {
public void method() {
synchronized (Synchronized.class) {
// 任意类对象
// 代码块
}
}
}
3.任意对象加锁
public class Synchronized {
private final static Object Lock = new Object();
public void method() {
synchronized (Lock) {
//任意对象
// 代码块
}
}
}
系列文章 ❀❀❀多线程(一):认识和创建一个线程❀❀❀ ❀❀❀多线程(二):Thread类的常见方法❀❀❀