多线程(三):synchronized解决线程不安全

往期回顾:
❀❀多线程(一):认识和创建一个线程❀❀
❀❀多线程(二):Thread类的常见方法❀❀


一. 为什么会出现线程不安全

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类的常见方法❀❀❀
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力写代码的菜鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值