10. 线程安全问题 / synchronized 关键字(重点)

1. 线程不安全

线程不安全代码:


public class ThreadDemo13 {
    static class Counter{
        public static int count = 0;

        public void increase(){
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++){
                    counter.increase();
                }
            }
        };

        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++){
                    counter.increase();
                }
            }
        };

        t1.start();
        t2.start();

        t1.join();//当执行这里的时候,线程就阻塞了,一直到t1结束,才会往下执行
        t2.join();

        System.out.println(Counter.count);
    }
}

以上代码有两个新的线程,此时最终的运行结果不唯一

2. 为什么会出现线程不安全的情况呢?

1.线程是抢占式执行的(根本原因)

2.自增操作不是原子的,每次++,都能分为以下三个步骤
a)把内存中的数据读取到CPU (load)
b)把CPU中的数据+1 (incr)
c)把计算结束的数据写回到内存 (save)
当CPU执行到任意一步骤的时候,调度器随时都有可能调度走,来让其他线程来执行

3.多个线程尝试修改同一个变量

4.内存可见性导致的线程安全问题

5.指令重排序(在编译器编译代码时,会对指针进行优化,调整指令的先后顺序,保证原有逻辑不变的情况下提高程序的运行效率)

3. 加锁 synchronized

1)实现了原子性 性能比较低

加上 synchronized 之后不一定立即就能成功,如果发现当前的锁已经被占用,该代码就会阻塞等待,一直等到之前的线程释放,才可能会获取到这个锁

以下代码假设线程一先获取到锁,那么线程二再尝试获取锁就会阻塞等待,线程一的运行不会受到影响。当线程一释放锁之后,线程二才有可能获取到锁。如果此时线程一中出现死锁,一旦锁死,就解不开了


public class ThreadDemo13 {
    static class Counter{
        public static int count = 0;

        //表示进入此方法之前会尝试加锁
        //increase方法执行完毕后会自动解锁
  
        //这里就相当于是针对counter这个对象进行加锁
        //进入方法内部,把加锁状态设为true,执行完成这个方法之后,就把加锁状态设为false
        synchronized public void increase(){
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++){
                    counter.increase();
                }
            }
        };

        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++){
                    counter.increase();
                }
            }
        };

        t1.start();
        t2.start();

        t1.join();//当执行这里的时候,线程就阻塞了,一直到t1结束,才会往下执行
        t2.join();

        System.out.println(Counter.count);
    }
}

此时运行结果就唯一了

2)以下是加锁和非加锁的对照:
StringBuffer(加锁了) / StringBuilder(没加锁)
Vector(加锁了) / ArrayList(没加锁)
HashTable (加锁了)/ HashMap(没加锁)

3)理解synchronized具体使用
synchronized是可以灵活加锁的

  1. 加到普通方法前:表示锁this
    如果synchronized关键字写到方法前面,那就相当于是给当前对象来加锁
    在这里插入图片描述
  2. 加到静态方法前:表示锁当前类的类对象——反射
  3. 加到某一代码块之前:显示指定给某个对象加锁
public class ThreadDemo14 {
    static class Test{
        public void method(){
            //这里括号为this,就相当于是给当前创建的对象加锁(t)
            synchronized (this){
                System.out.println("hh");
            }
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.method();
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值