JAVA并发编程与高并发解决方案 - 视频第10章(多线程并发拓展)

1、死锁

转自
https://blog.csdn.net/u014419806/article/details/52856589
https://www.cnblogs.com/Kevin-ZhangCG/p/9038223.html
https://blog.csdn.net/t_x_l_/article/details/73159636

1.1 产生死锁的四个必要条件?

(1)互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
(2)请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
(3)不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
(4)环路等待条件:是指进程发生死锁后,必然存在一个进程–资源之间的环形链

1.2 处理死锁的基本方法

1.预防死锁:通过设置一些限制条件,去破坏产生死锁的必要条件
2.避免死锁:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁
3.检测死锁:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉
4.解除死锁:该方法与检测死锁配合使用

package com.mmall.concurrency.example.deadLock;

import lombok.extern.slf4j.Slf4j;

/**
 * 一个简单的死锁类
 * 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒
 * 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒
 * td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;
 * td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;
 * td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
 */

@Slf4j
public class DeadLock implements Runnable {
    public int flag = 1;
    //静态对象是类的所有对象共享的
    private static Object o1 = new Object(), o2 = new Object();

    @Override
    public void run() {
        log.info("flag:{}", flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    log.info("1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    log.info("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock td1 = new DeadLock();
        DeadLock td2 = new DeadLock();
        td1.flag = 1;
        td2.flag = 0;
        //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
        //td2的run()可能在td1的run()之前运行
        new Thread(td1).start();
        new Thread(td2).start();
    }
}

2、多线程并发最佳实践

转自 https://blog.csdn.net/IbelieveSmile/article/details/81204545

  • 使用本地变量
    尽量使用本地变量,而不是创建一个类或实例的变量。
  • 使用不可变类
    String、Integer等。不可变类可以降低代码中需要的同步数量。
  • 最小化锁的作用域范围:S=1/(1-a+a/n)
    a:并行计算部分所占比例
    n:并行处理结点个数
    S:加速比
    当1-a等于0时,没有串行只有并行,最大加速比 S=n
    当a=0时,只有串行没有并行,最小加速比 S = 1
    当n→∞时,极限加速比 s→ 1/(1-a)
    例如,若串行代码占整个代码的25%,则并行处理的总体性能不可能超过4。
    该公式称为:“阿姆达尔定律"或"安达尔定理”。
  • 使用线程池的Executor,而不是直接new Thread 执行
    创建一个线程的代价是昂贵的,如果要创建一个可伸缩的Java应用,那么你需要使用线程池。
  • 宁可使用同步也不要使用线程的wait和notify
    从Java1.5以后,增加了许多同步工具,如:CountDownLatch、CyclicBarrier、Semaphore等,应该优先使用这些同步工具。
  • 使用BlockingQueue实现生产-消费模式
    阻塞队列不仅可以处理单个生产、单个消费,也可以处理多个生产和消费。
  • 使用并发集合而不是加了锁的同步集合
    Java提供了下面几种并发集合框架:
    ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentLinkedQueue 、ConcurrentLinkedDeque等(相关介绍请见Java 并发编程(九)并发集合框架
  • 使用Semaphone创建有界的访问
    为了建立稳定可靠的系统,对于数据库、文件系统和socket等资源必须要做有机的访问,Semaphone可以限制这些资源开销的选择,Semaphone可以以最低的代价阻塞线程等待,可以通过Semaphone来控制同时访问指定资源的线程数。
  • 宁可使用同步代码块,也不使用同步的方法
    主要针对synchronized关键字。使用synchronized关键字同步代码块只会锁定一个对象,而不会将整个方法锁定。如果更改共同的变量或类的字段,首先应该选择的是原子型变量,然后使用volatile。如果需要互斥锁,可以考虑使用ReentrantLock。
  • 避免使用静态变量
    静态变量在并发执行环境下会制造很多问题,如果必须使用静态变量,那么优先是它成为final变量,如果用来保存集合collection,那么可以考虑使用只读集合,否则一定要做特别多的同步处理和并发处理操作。

3、Spring与线程安全

详见 https://mp.csdn.net/mdeditor/83094756#

猜你喜欢

转载自blog.csdn.net/csdnlijingran/article/details/83061670