Thorough explanation: the advantages and disadvantages of concurrent programming

I have compiled Java advanced materials for free, covering Java, Redis, MongoDB, MySQL, Zookeeper, Spring Cloud, Dubbo high concurrency and distributed tutorials, a total of 30G, and you need to get it yourself.
Portal: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

 

Concurrent programming has always been unfathomable to newcomers, so I wanted to write something and record it to improve my understanding and cognition of concurrent programming. Why do you need concurrency? There are always good and bad sides to everything, what is the trade-off between them, that is to say, what are the disadvantages of concurrent programming? And what are the concepts you should know and master when doing concurrent programming? This article focuses on these three issues.

1. Why use concurrency

For a long time, the development of hardware has been extremely rapid, and there is also a very famous "Moore's Law". It may be strange to discuss why concurrent programming is related to the development of hardware. The relationship between this should be that the development of multi-core CPU provides concurrent programming. hardware base. Moore's Law is not a natural law or a physical law, it is just a prediction of the future based on observational data. According to the predicted speed, our computing power will increase at an exponential rate, and we will have super computing power in the near future. It is when we imagine the future. In 2004, Intel announced that the plan for 4GHz chips was postponed to 2005. Then in the fall of 2004, Intel announced the complete cancellation of the 4GHz plan, which means that the effectiveness of Moore's Law has come to an abrupt end for more than half a century. However, smart hardware engineers have not stopped the pace of research and development. In order to further improve the computing speed, instead of pursuing separate computing units, they have integrated multiple computing units together, that is, forming a multi-core CPU. In just over ten years, home CPUs, such as Intel i7, can reach 4 cores or even 8 cores. Professional servers can usually reach several independent CPUs, and each CPU even has as many as 8 or more cores. As such, Moore's Law seems to continue to be experienced on CPU core scaling. Therefore, under the background of multi-core CPU, the trend of concurrent programming has been born. Through the form of concurrent programming, the computing power of multi-core CPU can be maximized and the performance can be improved.

Top computer scientist Donald Ervin Knuth commented on the situation this way: In my opinion, this phenomenon (concurrency) is more or less caused by hardware designers who are out of control, and they put the blame for Moore's Law on the software developers.

In addition, it is innately suitable for concurrent programming in special business scenarios. For example, in the field of image processing, a 1024X768 pixel picture contains more than 786,000 pixels. It takes a long time to traverse all the pixels in real time. In the face of such a complex calculation, it is necessary to make full use of the computing power of multiple cores. Another example is when we are shopping online, in order to improve the response speed, we need to split, reduce inventory, generate orders and other operations, we can split and use multi-threaded technology to complete. In the face of complex business models, parallel programs are more suitable for business needs than serial programs, and concurrent programming is more suitable for this business split. It is precisely because of these advantages that multi-threading technology can be paid attention to, and it is also what a CS learner should master:

  • Make full use of the computing power of multi-core CPUs;
  • Facilitate business splitting and improve application performance

2. What are the disadvantages of concurrent programming

There are so many benefits of multi-threading technology, is there no disadvantage, and it must be applicable in any scenario? Obviously not.

2.1 Frequent context switches

The time slice is the time allocated by the CPU to each thread. Because the time is very short, the CPU constantly switches threads, making us feel that multiple threads are executing at the same time. The time slice is generally tens of milliseconds. Every time you switch, you need to save the current state so that you can restore the previous state, and this switching is very performance-intensive, and it is too frequent to take advantage of multi-threaded programming. Often context switching can be reduced by using lock-free concurrent programming, CAS algorithms, using minimal threads and using coroutines.

  • Lock-free concurrent programming: You can refer to the idea of ​​concurrentHashMap lock segmentation, different threads process data in different segments, so that under the condition of multi-thread competition, the time for context switching can be reduced.

  • The CAS algorithm uses the CAS algorithm to update data under Atomic, and uses optimistic locking, which can effectively reduce the context switching caused by some unnecessary lock competition.

  • Use the fewest threads: Avoid creating unnecessary threads, such as few tasks, but creating a lot of threads, which will cause a large number of threads to be in a waiting state

  • Coroutine: realize multi-task scheduling in a single thread, and maintain switching between multiple tasks in a single thread

Since context switching is also a relatively time-consuming operation, there is an experiment in the book "The Art of Java Concurrent Programming" that concurrent accumulation may not be faster than serial accumulation. You can use Lmbench3 to measure the duration of context switches and vmstat to measure the number of context switches

2.2 线程安全

多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的情况,一旦产生死锁就会造成系统功能不可用。

public class DeadLockDemo {
    private static String resource_a = "A";
    private static String resource_b = "B";

    public static void main(String[] args) {
        deadLock();
    }

    public static void deadLock() {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resource_a) {
                    System.out.println("get resource a");
                    try {
                        Thread.sleep(3000);
                        synchronized (resource_b) {
                            System.out.println("get resource b");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resource_b) {
                    System.out.println("get resource b");
                    synchronized (resource_a) {
                        System.out.println("get resource a");
                    }
                }
            }
        });
        threadA.start();
        threadB.start();

    }
}

 

在上面的这个demo中,开启了两个线程threadA, threadB,其中threadA占用了resource_a, 并等待被threadB释放的resource _b。threadB占用了resource _b正在等待被threadA释放的resource _a。因此threadA,threadB出现线程安全的问题,形成死锁。同样可以通过jps,jstack证明这种推论:

"Thread-1":
  waiting to lock monitor 0x000000000b695360 (object 0x00000007d5ff53a8, a java.lang.String),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x000000000b697c10 (object 0x00000007d5ff53d8, a java.lang.String),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at learn.DeadLockDemo$2.run(DeadLockDemo.java:34)
        - waiting to lock <0x00000007d5ff53a8(a java.lang.String)
        - locked <0x00000007d5ff53d8(a java.lang.String)
        at java.lang.Thread.run(Thread.java:722)
"Thread-0":
        at learn.DeadLockDemo$1.run(DeadLockDemo.java:20)
        - waiting to lock <0x00000007d5ff53d8(a java.lang.String)
        - locked <0x00000007d5ff53a8(a java.lang.String)
        at java.lang.Thread.run(Thread.java:722)

Found 1 deadlock.

 

如上所述,完全可以看出当前死锁的情况。

那么,通常可以用如下方式避免死锁的情况:

  1. 避免一个线程同时获得多个锁;
  2. 避免一个线程在锁内部占有多个资源,尽量保证每个锁只占用一个资源;
  3. 尝试使用定时锁,使用lock.tryLock(timeOut),当超时等待时当前线程不会阻塞;
  4. 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

所以,如何正确的使用多线程编程技术有很大的学问,比如如何保证线程安全,如何正确理解由于JMM内存模型在原子性,有序性,可见性带来的问题,比如数据脏读,DCL等这些问题(在后续篇幅会讲述)。而在学习多线程编程技术的过程中也会让你收获颇丰。

3. 应该了解的概念

3.1 同步VS异步

同步和异步通常用来形容一次方法调用。同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。而异步调用,指的是,调用者不用管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。比如,在超时购物,如果一件物品没了,你得等仓库人员跟你调货,直到仓库人员跟你把货物送过来,你才能继续去收银台付款,这就类似同步调用。而异步调用了,就像网购,你在网上付款下单后,什么事就不用管了,该干嘛就干嘛去了,当货物到达后你收到通知去取就好。

3.2 并发与并行

并发和并行是十分容易混淆的概念。并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。

3.3 阻塞和非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响,比如一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞,而非阻塞就恰好相反,它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试地往前运行。

3.4 临界区

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。

转载于:https://www.cnblogs.com/yuxiang1/p/11563328.html

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324782647&siteId=291194637