[Getting to give up -Java] Concurrent programming - thread-safe

Outline

Concurrent programming, namely multiple threads within the same period, "simultaneously" run.

In a multiprocessor system already popular today, multithreading can play out its advantages, such as: an 8-core cpu servers, if only a single thread, it will have seven processor is idle, the server can only play eight one of the ability (ignoring other resource consumption).
At the same time, the use of multi-threading, we can simplify the complex task of processing logic to reduce the complexity of the business model.

Therefore, concurrent programming plays a vital role in improving server resource utilization, improve system throughput, reduce coding difficulty.

These are the advantages of concurrent programming, but it also introduces a very important question: thread safety.

What is the thread-safety issues

When concurrent execution threads, because the cpu scheduling and other reasons, alternately thread execution. FIG example is shown below

public class SelfIncremental {
    private static int count;

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i< 10000; i++) {
                count++;
                System.out.println(count);

            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i< 10000; i++) {
                count++;
                System.out.println(count);

            }
        });

        thread1.start();
        thread2.start();
    }
}

After the implementation of the value of count it does not always equal to 20,000, is less than 20,000 will appear, because thread1 and thread2 may be performed alternately.

as the picture shows:

  • a time t1: thread1 read count = 100
  • time t2: thread2 read count = 100
  • t3 time: thread1 to count + 1
  • t4 time: thread2 to count + 1
  • t5 time: thread1 will write count 101
  • t5 time: thread2 will write count 101

Because the count ++ is not an atomic operation, it will actually perform three steps:

  • 1, to obtain the value of count
  • 2, the count plus 1
  • 3, the calculation result is written count

Therefore, when the concurrent execution of two threads simultaneously read, could read the same value, the same value plus one, leading to the results do not meet expectations, this situation is not thread safe.

Thread safety: When multiple threads access a class, no matter what kind of scheduling runtime environment using or how these threads will alternate execution, and does not require the use of additional synchronization operation is called, the class can show proper behavior , then call this class is thread-safe.

Triggered

Cause of thread safety problem is mainly shared memory can be read by multiple threads, because there is time to read and modify uncertainty, resulting in a thread read stale data, and write back the dirty data post-processing on the basis of shared memory, resulting in erroneous results.

Race conditions

在并发编程中,因为不恰当的执行时序而出现不正确的结果的情况被称为竟态条件。

常见的静态条件类型:

  • 先检查后执行:首先观察到某个条件为真。根据这个观察结果采用相应的动作,但实际上在你观察到这个结果和采用相应动作之间,观察的结果可能发生改变变得无效,导致后续的所有操作都变得不可预期。(比如延迟初始化)
  • 读取-修改-写入:基于对象之前的状态来定义对象状态的转换。但在读取到结果和修改之间,对象可能已被更改。这样就会基于错误的数据修改得出错误的结果并被写入。(比如递增操作)

发布与逸出

发布:使对象能够在当前作用域之外的代码中使用。如将该对象的引用保存到其它代码可以访问的地方、在一个非私有的方法中返回该引用,将引用传递到其它类的方法中。如:

public static Student student;

public void init() { 
    student = new Student;
}

这里 student对象就被发布了。

逸出:当不该被发布的对象被发布了,就称为逸出。如

private String name = "xxx";

public String getString() {
    return name;
}

这里name原为private类型但是却被getString方法发布了,就可以被视为逸出。

如何避免

线程封闭

线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只有这个对象能修改。

线程封闭即不共享数据,仅在单线程内访问数据,这是实现线程安全最简单的方式之一。
实现线程封闭可以通过:

  • Ad-hoc线程封闭:即维护线程封闭性的职责完全由成熟实现承担。
  • 栈封闭:通过局部变量才能访问对象,该局部变量被保存在执行线程的栈中,其他线程无法访问。
  • ThreadLocal类:将共享的全局变量转换为ThreadLocal对象,当线程终止后,这些值会被垃圾回收。

只读共享

在没有额外同步的情况下,共享的对象可以由多个线程并发访问,但是任何线程都不能修改。共享的对象包括不可变对象和事实不可变对象。

不可变对象:如果某个对象在被创建后就不能修改,那么这个对象就是不可变对象。不可变对象一定是线程安全的。

线程安全共享

线程安全的对象在其内部实现同步,因此多线程可以通过对象的公有接口来进行访问而不需要自己做同步。

保护对象

被保护的对象只能通过持有特定的锁来访问。即通过加锁机制,确保对象的可见性及原子性。

  • 内置锁:即通过synchronized关键字同步代码块。线程在进入同步代码块之前会自动获得锁,并在退出同步代码块时自动释放锁。内置锁是一种互斥锁。
  • 重入锁:当线程视图获取一个已经持有的锁时,就会给锁的计数器加一,释放锁时计数器会减一。当计数器为0时,释放锁
  • volatile:访问volatile变量时,不会加锁,也不会阻塞线程执行。他只确保变量的可见性,是一种比synchronized更轻量级的同步机制。

总结

本文主要是记录了学习《Java并发编程实站》前几章中,并发编程相关的一些概念。简单介绍了线程安全、锁机制等,接下来 我们会深入JUC源码,来深刻学习并发编程相关知识。

备注:本文主要源自对《Java并发编程实战》的学习笔记。

更多文章见:https://nc2era.com

Guess you like

Origin yq.aliyun.com/articles/708851