读书笔记——java并发编程实战-第二章 线程安全性

存在线程安全问题的前提:

该变量可被修改

该变量被多个线程访问

当多个线程访问同一个可变状态的变量时,没有进行合适的同步,那么程序就会出现错误。共有三种方法解决该问题:

  • 不在线程之间共享该状态变量
  • 将状态变量修改为不可变的变量
  • 在访问时,使用同步

一、什么是线程安全性?

线程安全性:当多个线程访问某个类时,这个类始都能出现正确的行为,那么我们就称这个类是线程安全的。

无状态的对象一定是线程安全的

(有状态与无状态:有状态就是有存储数据的功能的类。无状态就是该类中的属性不存储数据)

二、原子性

1.竞态条件: 当多个线程访问同一资源,线程执行的先后顺序不同会造成返回结果不一致的结果,此时就发生了竞态现象。多个线程访问的代码资源,叫做临界区。

  • 先检查后执行:基于一种可能失效的观察结果,来做出判断,或执行某个运算。这种竞态条件,被称为先检查后执行。

2.延迟初始化中的竞态条件: 延迟初始化,会在对象被需要的时候才进行初始化操作,以此来节约内存,同时我们必须确保对象只被初始化一次。但是在多线程情况下,两个线程同时执行初始化条件,当第一个线程初始化未完成时,第二个线程如果在此时进行条件判断。则得到的结果则是该对象还未被初始化,那么此时第二个线程会再次执行初始化。此时即发生了线程安全问题。本该执行一次的代码,被两个线程分别执行了一次。如果在1号与2号线程初始化完成前,又有线程对初始化条件进行了判断。则又会多执行一次初始化。这是我们不愿意看到的情况。

3.复合操作: 几个必须以原子方式执行的动作的组合,叫做复合操作。如先检查后执行中,就存在着一组要以原子方式执行的动作。所以先检查后执行是一个复合操作。如果我们想保证线程安全,那么就要保 证每组复合操作都以原子方式执行。一般来说,我们会通过加锁来达到这个目的。

三、加锁机制

1.内置锁: java提供了一种内置的锁机制来支持原子性:同步代码块。同步代码块包括两部分:一个作为锁的对象引用;一个作为由这个锁的保护代码块。用关键字Synchronized来修饰的方法,是一种包括了整个方法体的同步方式。该代码块的锁就是调用该方法的实例。也就是this。当Synchronized修饰的是静态方法时,代码块的锁就是当前类的.CLASS对象。

2.重入: 当某个线程请求一个由其他线程持有的锁时,发出请求的线程会被阻塞。然而, 由于内部锁时可重入的,因此如果某个线程试图获取一个它已经持有的锁,那么这个请求就会成功。重入的一种实现方式就是将锁与一个计数器还有一个所有者线程关联。当计数器为0时,该锁没有被任何线程持有,此时任何申请该锁的线程都可以成功,同时JVM将记录下该锁的持有者,并将计数器设置为1.当同一线程再次获取这个锁,计数器将递增,当线程退出同步代码块时,计数器递减,直到计数器为0,该锁被释放。

3.重入的好处:当子类重写父类的同步方法时,如下代码所示。当执行子类 LoggingWidget的doSomething时,线程会获得锁。当执行子类的doSomething过程中,又调用了父类的doSomething。若是内置锁不可重入,则父类的doSomething会一直等待下去。重入则避免了这种死锁的发生。

public class Widget{
    
    
    public synchronized void doSomething(){
    
    
        ...
    }
}

public class LoggingWidget extends Widget{
    
    
    public synchronized void doSomething(){
    
    
        system.out.println("=================");
        super.doSomething();
    }
}

四、用锁来保护状态

变量可以用锁来保护,这样可以确保同一时间只有一个线程在操作这个变量。只有当该线程完成对变量的操作时,其他线程才可以对该变量进行操作。

五、活跃性与性能

  • 加锁确实可以保证我们程序的安全性。我们需要考虑安全性是因为:为了提升性能而使用了多线程,多线程情况下会发生线程安全性问题,所以我们要加锁来保证同步。但这并不代表着我们要为了安全性而牺牲性能。所以我们应该在保证性能的情况下,来保证程序的安全性。千万不要为了盲目的安全,而牺牲性能,那我们引入多线程就没有任何意义。
  • 所以我们在加锁的时候,要对同步代码块的大小进行合理的判断。此时我们需要在安全性,简单性,与性能三者之间找到一个权衡的方法。而简单性与性能之间,经常会发生冲突。

注意:当执行较长时间的计算,或者可能无法快速完成的操作时(网络I/O或者控制台I/O)一定不要持有锁

猜你喜欢

转载自blog.csdn.net/weixin_45373852/article/details/108726264
今日推荐