Java并发编程(学习笔记)

  • 并发概念:

并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划

分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称

之为并发(Concurrent)。

并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,

两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

高并发:服务能同时处理很多请求,提高程序性能。

案例Demo:

package com.mmall.concurrency;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j
@NotThreadSafe
public class ConcurrencyTest {

    //请求总数
    public static int clientTotal=5000;

    //同时并发执行的线程数
    public static int threadTotal=50;

    public static int count=0;

    public static void main(String[] args) throws Exception{
        ExecutorService executorService= Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for(int i=0;i<clientTotal;i++){
            executorService.execute(()->{
                try{
                    semaphore.acquire();
                    add();
                    semaphore.release();
                }catch (Exception e){
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}",count);
    }

    private static void add(){
        count++;
    }


}

基础知识:

CPU多级缓存:CPU速度远大于内存速度。

CPU多级缓存的问题:
    缓存一致性(MESI协议):用于保证多核CPU cache缓存共享数据的一致性问题。

    乱序执行优化:处理器为提高运算速度而作出违背代码原有顺序的优化。(单核稳定,多核易错)

如何解决?=====》首先弄清部分概念:Java内存模型(Java Memory Model)

Java内存模型:

为了屏蔽各种硬件和操作系统内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,Java虚拟机规范中定义

了Java内存模型;Java内存模型本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,规定了一个线程如何和何

时可以看到其他线程修改后的共享变量的值,以及在必须时如何同步访问共享变量。

同步的八种操作和规则:

操作:

    1.锁定(lock):作用于主内存的变量,把一个变量标识为一条线程独占状态。

    2.解锁(unlock):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

    3.读取(read):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用。

    4.load(载入):作用于工作内存的变量,他把read操作从主内存中得到的变量值放入工作内存的变量副本中。

    5.使用(use):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎。

    6.赋值(assign):作用于工作内存的变量,把一个从执行引擎接收到的值赋值给工作内存的变量。

    7.存储(store):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。

    8.写入(write):作用于主内存的变量,把store操作从工作内存中一个变量的值传送到主内存的变量中。

规则:
    1.如果把一个变量从主内存中复制到工作内存中,就需要按顺序地执行read和load操作;如果把变量从工作内存中同步回主内存中,就要按顺序的执行store和write操作。但是Java内存模型只要求上述操作必须按照顺序执行,而没有保证必须是连续执行的。

    2.不允许read和load、store和write操作之一单独出现。

    3.不允许一个线程丢弃它最近assign操作,即变量在工作内存中改变了之后必须同步到主内存中。

    4.不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存中同步回主内存中。

    5.一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是一个变量实施use和store操作之前,必循先执行assign和load操作。

    6.一个变量在同一时刻只允许一条线程对其进行lock操作,但是lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操纵,变量才能被解锁。lock和unlock必须成对出现。

    7.如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。

    8.如果一个变量事先没有被lock操作锁定,则不允许对它进行unlock操作,也不允许unlock一个被其他线程锁定的变量。

    9.对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)


并发的优势和风险:

    优势1:速度:同时处理多个请求,响应更快;复杂的操作可以分成多个进程同时进行。

    优势2:设计:程序设计在某些情况下可以有更多的选择。

    优势3:资源利用:CPU能够在等待IO的时候做一些其他的事情。

    风险1:安全性:多个线程共享数据可能会产生期望不相符的结果。

    风险2:活跃性:某个操作无法继续执行下去会产生活跃性问题,比如死锁、饥饿等问题。

    风险3:性能:线程过多使的CPU频繁切换,调度时间增多;同步机制;消耗过多内存


解决方案体系:

一、线程安全性:

定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要额外的同步或者协同,这些类都能表现出正确行为,那么这个类是线程安全的。

    体现在三个方面:

    1.原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作。

        原子性---Atomic包:

        (1) AtomicXXX:CAS/Unsafe.compareAndSwapInt            

public static AtomicInteger count=new AtomicInteger(0);

AtomicInteger类中的方法:
public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

unsafe中该方法实现:
//var1表示当前对象,如本例中count,var2当前对象值,与var4执行相加
public final int getAndAddInt(Object var1, long var2, int var4) {
      int var5;
      do {
            //var5底层变量
          var5 = this.getIntVolatile(var1, var2);
      //工作内存中的var2值和主内存中var5值不断比较,相同执行var5 + var4操作
      } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

       return var5;
      }

        (2) AutomicLong、LongAdder(区别)

            LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好 

           的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。

           缺点是LongAdder在统计的时候如果   有并发更新,可能导致统计的数据有误差。

           在使用时候优先考虑使用LongAdder,低并发情况下可以考虑使用AutomicLong。

           两者区别详见:https://blog.csdn.net/yao123long/article/details/63683991

      (3)AtomicReference、AtomicReferenceFieldUpdater类====》更新数据             

package com.mmall.concurrency.example.atomic;
import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
@ThreadSafe
public class AtomicExample4 {

    private static AtomicReference<Integer> count=new AtomicReference<Integer>(0);

    public static void main(String[] args) {
        count.compareAndSet(0,2);
        count.compareAndSet(0,1);
        count.compareAndSet(1,3);
        count.compareAndSet(2,4);
        count.compareAndSet(3,5);
        log.info("count:{}",count.get());
    }
}
输出:4

 

package com.mmall.concurrency.example.atomic;
import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
@ThreadSafe
public class AtomicExample5 {

    //原子性更新类的某个实例指定的某个字段count,count需要特殊关键词volatile修饰
    private static AtomicIntegerFieldUpdater<AtomicExample5> updater=
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class,"count");

    @Getter
    public volatile int count=100;

    public static void main(String[] args) {
        AtomicExample5 example5=new AtomicExample5();
        if(updater.compareAndSet(example5,100,120)){
            log.info("update success1,{}",example5.getCount());
        }
        if(updater.compareAndSet(example5,100,120)){
            log.info("update success2,{}",example5.getCount());
        }else {
            log.info("update fail,{}",example5.getCount());
        }
    }
}
输出:
14:39:54.287 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample5 - update success1,120
14:39:54.294 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample5 - update fail,120

        (4)AtmoicStampReference:CAS的ABA问题:变量值虽然相同,但是版本不同了。


        (5)AtomicBoolean
            //如何让某段代码只执行一次

package com.mmall.concurrency.example.atomic;
import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

@Slf4j
@ThreadSafe
public class AtomicExample6 {

    private static AtomicBoolean ishappend=new AtomicBoolean(false);

    //请求总数
    public static int clientTotal=5000;

    //同时并发执行的线程数
    public static int threadTotal=50;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });

        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}",ishappend.get());

    }

    private static void test(){
        if(ishappend.compareAndSet(false,true)){
            log.info("execute");
        }
    }
}
输出:
14:45:51.127 [pool-1-thread-1] INFO com.mmall.concurrency.example.atomic.AtomicExample6 - execute
14:45:51.412 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample6 - count:true


     原子性---锁:

           synchronized:依赖JVM---不可中断锁,适合竞争不激烈,可读性好。

                1.修饰代码块:大括号起来的代码,作用于调用的对象

                2.修饰方法:整个方法,作用于调用的对象 -----子类无法继承父类的synchronized

                3.修饰静态方法:整个静态方法,作用于所有的对象

                4.修饰类:括号起来的部分,作用于所有对象


            Lock:依赖特殊的CPU指令,代码实现,ReentrantLock---可中断锁,多样化同步,竞争激烈时能维持常态


            Atomic:竞争激烈时能维持常态,比Lock性能好,只能同步一个值。

   2.可见性:一个线程对内存的修改可以及时的被其他线程观察到。

        导致共享变量在线程中不可见的原因:线程交叉执行;重排序结合线程交叉执行;共享变量更新后的值没有在工作内存与主存间及时更新。

        可见性:

        synchronized--JMM关于synchronized的两条规定:

            线程解锁前,必须把共享变量的最新值刷新到主内存中;

            线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值。

        volatile--通过加入内存屏障和禁止重排序优化来实现

            对volatile变量写操作时,会在写操作后加入一条store平渣指令,将本地内存中的共享变量值刷新到主内存中。

            对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。

            ---CPU指令级实现,并不是线程安全的,不具有原子性。      

 适合场景:状态标识量;doubleCheck(安全发布对象双重检测机制);

 状态标识量例子伪代码:

       volatile boolean inited=false;

      //线程1:
       context=loadContext();
       inited=true;

       //线程2:
       while(!inited){
           sleep();
       }
       doSomethingWithConfig(context);


   3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。

        Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响单线程程序的执行,却会影响到多线程并发执行的正确性。

        Java先天有序性:happen...before原则

            1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。

                -----虽然java虚拟机会重排序,但是只是对数据不依赖的指令重排序,最后执行的顺序看起来就是代码书写的顺序;无法保证多线程执行的正确性。

            2.锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。

            3.volatile变量原则:对一个变量的写操作先行发生于后面对这个变量的读操作。

            4.传递原则:A在B前,B在C前,则A在C前。

            5-8:线程启动原则、线程中断原则、线程终结原则、对象终结原则。

        -----如果两个操作的执行次序无法从该八个原则中推导出来,就不能保证有序性,虚拟机可以对他们进行重排序。


二、安全发布对象:

    发布对象:使一个对象能够被当前范围之外的代码所使用。

    对象逸出:一个错误的发布。当一个对象还没有构造完成时,就使它被其他线程所见。


    安全发布对象的四种方法(对应视频5-2-5.3):
        
        1.在静态初始化函数中初始化一个对象引用。

        2.将对象的引用保存到volatile类型域或者AtomicReference对象中。

        3.将对象的引用保存到某个正确构造对象的final类型域中。

        4.将对象的引用保存到一个由锁保护的域中。


        example1: 懒汉模式,单例实例在第一次使用时创建===>线程不安全
        
        example2: 饿汉模式,单例实例在类装载创建=>线程安全;不足:构造方法中有过多处理会使得类加载慢;如果不调用会造成资源浪费;
        
        example3: synchronized使懒汉模式线程安全,但是不推荐    

        example4:懒汉模式+双重检测机制+同步锁===》线程不安全

        example5:懒汉模式+双重检测机制+同步锁+volatile===》线程安全

        example6: 饿汉模式,使用静态块

        example7:  枚举模式:最安全的===》对象的创建放在枚举内

package com.mmall.concurrency.example.singleton;

import com.mmall.concurrency.annoations.Recommend;

import javax.annotation.concurrent.ThreadSafe;

@ThreadSafe
@Recommend
/*
   枚举模式:最安全的
 */
public class SingletonExample7 {
    //私有构造函数
    private SingletonExample7(){
    }
    //静态的工厂方法
    public static SingletonExample7 getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;
        private SingletonExample7 singleton;

        //jvm保证这个方法只会被调用一次
        Singleton(){
            singleton=new SingletonExample7();
        }
        public SingletonExample7 getInstance(){
            return singleton;
        }
    }

}

三、不可变对象:
    
    1.对象创建后其状态不能修改

    2.对象所有域都是final类型

    3.对象是正确创建的(创建期间,this引用没有逸出)

    补充知识点:

          final关键字:类、方法、变量

          1. 修饰类不能被继承;

          2.修饰方法:final修饰方法的目的:锁定方法不被继承类修改;提高代码执行效率。注意:一个类的privte方法回你被隐式               指定为final方法。

          3.修饰变量:基本数据类型;引用型数据类型(初始化后不能指向另外一个对象,但可以修改内部的值)

          
    创建不可变对象的三种方法:

     1.final(如果是引用型变量初始化以后不能指向另外一个对象,但是可以修改)

package com.mmall.concurrency.example.immutable;

import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
@Slf4j
public class ImmutableExample1 {

    private final static Integer a=1;

    private final static String b="2";

    private final static Map<Integer,Integer> map=Maps.newHashMap();

    static {
        map.put(1,2);
        map.put(3,4);
        map.put(5,6);

    }
    public static void main(String[] args) {
//        a=2;
//        b="3";
        map.put(1,3);
        log.info("{}",map.get(1));
    }

    private void test(final int a){
//         a=1;
    }
}

     2.Collections.unmodifiableXXX:Collection、List、Set、Map...(Collections.unmodifiableMap处理后不可修改,如果修改执行会抛出异常)

package com.mmall.concurrency.example.immutable;
import com.google.common.collect.Maps;
import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;


import java.util.Collections;
import java.util.Map;

@Slf4j
@ThreadSafe
public class ImmutableExample2 {


    private static Map<Integer,Integer> map=Maps.newHashMap();

    static {
        map.put(1,2);
        map.put(3,4);
        map.put(5,6);
        //Collections.unmodifiableMap处理后不可修改
        map = Collections.unmodifiableMap(map);
    }
    public static void main(String[] args) {
        map.put(1,3);
        log.info("{}",map.get(1));
    }
}

     3.Guava:ImmutableXXX:Collection、List、Set、Map...(生成对象初始化以后不可以修改,安全的,如果修改执行会抛出异常)

package com.mmall.concurrency.example.immutable;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.mmall.concurrency.annoations.ThreadSafe;

@ThreadSafe
public class ImmutableExample3 {

    private final static ImmutableList list=ImmutableList.of(1,2,3);

    private final static ImmutableSet set=ImmutableSet.copyOf(list);

//    private final static ImmutableMap<Integer,Integer> map=ImmutableMap.of(1,2,3,4);

    private final static ImmutableMap<Integer,Integer> map=ImmutableMap.<Integer,Integer>
            builder().put(1,2).put(3,4).build();


    public static void main(String[] args) {
//        list.add(4);
}
}


四、线程封闭:将对象封装到一个线程中来保证线程安全。

    1.堆栈封闭:定义局部变量不会被多个线程共享,无并发问题,经常在用却未察觉。

    2.ThreadLocal线程封闭:特别好的封闭方法;Map-->key对应线程,value对应对象。

        ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

  1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

  2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

  3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

  4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

  ThreadLocal用法详解和原理:https://www.cnblogs.com/coshaho/p/5127135.html

应用场景:将前端请求request的相关信息放入ThreadLocal中。

package com.mmall.concurrency.example.threadLocal;

/**
 * TreadLocal所在的类,请求数据处理的类
 */
public class RequestHolder {

    private final static ThreadLocal<Long> requestHolder=new ThreadLocal<>();

    public static void add(Long id){
        requestHolder.set(id);
    }

    public static Long getId(){
        return requestHolder.get();
    }
    //防止内存泄露
    public static void remove(){
        requestHolder.remove();
    }
}



package com.mmall.concurrency;


import com.mmall.concurrency.example.threadLocal.RequestHolder;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 过滤器
 */
@Slf4j
public class HttpFilter  implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request=(HttpServletRequest)servletRequest;
        log.info("do filter,{},{}",Thread.currentThread().getId(),request.getServletPath());
        RequestHolder.add(Thread.currentThread().getId());
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}


package com.mmall.concurrency;

import com.mmall.concurrency.example.threadLocal.RequestHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 拦截器
 */
@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle");
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestHolder.remove();
        log.info("afterCompletion");
        return;

    }
}


package com.mmall.concurrency.example.threadLocal;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/threadLocal")
@Slf4j
public class ThreadLocalController {

    @RequestMapping("/test")
    @ResponseBody
    public Long test(){
        log.info("test");
        return  RequestHolder.getId();

    }
}



package com.mmall.concurrency;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
public class ConcurrencyApplication extends WebMvcConfigurerAdapter{

	public static void main(String[] args) {
		SpringApplication.run(ConcurrencyApplication.class, args);
	}

	/**
	 *启动类配置过滤器和拦截器
	 */
	@Bean
	public FilterRegistrationBean httpFilter(){
		FilterRegistrationBean registrationBean=new FilterRegistrationBean();
		registrationBean.setFilter(new HttpFilter());
		registrationBean.addUrlPatterns("/threadLocal/*");
		return registrationBean;

	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");
	}
}

执行过程:前端请求->过滤器(do filter,44,/threadLocal/test)将相关信息添加到TreadLocal对象中
->拦截器preHandle处理->Controller层打印test->拦截器afterCompletion执行TreadLocal对象remove方法,防止内存泄漏
输出:
2018-08-14 21:23:57.610  INFO 35187 --- [nio-8080-exec-1] com.mmall.concurrency.HttpFilter         : do filter,44,/threadLocal/test
2018-08-14 21:23:57.623  INFO 35187 --- [nio-8080-exec-1] com.mmall.concurrency.HttpInterceptor    : preHandle
2018-08-14 21:23:57.724  INFO 35187 --- [nio-8080-exec-1] c.m.c.e.t.ThreadLocalController          : test
2018-08-14 21:23:57.902  INFO 35187 --- [nio-8080-exec-1] com.mmall.concurrency.HttpInterceptor    : afterCompletion


    总结和补充:

    常见线程不安全类与写法:

        1.StringBuilder---->StringBuffer(方法都使用了synchronized)

        2.SimpleDateFormat---->JodaTime

        3.ArrayList、HashSet、HashMap等Collections

        4.先检查后执行:if(condition(a)){handle(a)}


五、线程安全---同步容器类(并不一定线程安全)

    1.ArrayList--->Vector,Stack

    2.HashMap--->HashTable(key和value不能为null)

    3.Collections.synchronizedXXX(List、Set、Map)

    缺陷:多数容器类都是非线程安全的,即使部分容器是线程安全的,由于使用sychronized进行锁控制,导致读/写均需进行锁操作,性能很低。

    同步容器类详情:https://www.cnblogs.com/dolphin0520/p/3933404.html


六、线程安全---并发容器J.U.C
    
    ArrayList-->CopyOnwriteArrayList(适合读多写少)

    HashSet、TreeSet--->CopyOnWriteArraySet、ConcurrentSkipListSet

    HashMap、TreeMap--->ConcurrentHashMap(性能非常好)、ConcurrentSkipListMap(key排序性能好)

    并发容器J.U.C相对于同步器:https://www.cnblogs.com/daoqidelv/p/6753162.html

    总结和补充:

    安全共享对象策略:

    1.线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改。

    2.共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它。

    3.线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它。

    4.被守护对象:被守护对象只能通过获取特定的锁来访问。

 

J.U.C(java.util.concurrent)包:

    核心:AQS(AbstractQueuedSynchronized)

    AQS设计思想:详见相关博客:https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html 

        1.使用Node实现FIFO队列,可以用于构建或者其他同步装置的基础框架。

        2.利用一个int类型表示状态。

        3.使用方法是继承,子类通过继承并通过实现它的方法管理其状态,(acquire和release)的方法操纵状态。

        4.可以同时实现排他锁和共享锁模式(独占和共享)。

AQS是JUC中很多同步组件的构建基础,简单来讲,它内部实现主要是状态变量state和一个FIFO队列来完成,同步队列的头结点是当前获取到同步状态的结点,获取同步状态state失败的线程,会被构造成一个结点(或共享式或独占式)加入到同步队列尾部(采用自旋CAS来保证此操作的线程安全),随后线程会阻塞;释放时唤醒头结点的后继结点,使其加入对同步状态的争夺中。

AQS为我们定义好了顶层的处理实现逻辑,我们在使用AQS构建符合我们需求的同步组件时,只需重写tryAcquire,tryAcquireShared,tryRelease,tryReleaseShared几个方法,来决定同步状态的释放和获取即可,至于背后复杂的线程排队,线程阻塞/唤醒,如何保证线程安全,都由AQS为我们完成了,这也是非常典型的模板方法的应用。AQS定义好顶级逻辑的骨架,并提取出公用的线程入队列/出队列,阻塞/唤醒等一系列复杂逻辑的实现,将部分简单的可由使用者决定的操作逻辑延迟到子类中去实现。

    AQS同步组件:CountDownLatch、Semaphore、CyclicBarrier、ReentrantLock、Condition、FutureTask

    CountDownLatch:

        CountDownLatch是一个同步工具,它主要用线程执行之间的协作。CountDownLatch 的作用和 Thread.join()方法类似,让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。在直接创建线程的年代(Java 5.0 之前),我们可以使用 Thread.join()。在线程池出现后,因为线程池中的线程不能直接被引用,所以就必须使用 CountDownLatch了。
        实现原理:计数器的值由构造函数传入,并用它初始化AQS的state值。当线程调用await方法时会检查state的值是否为0,如果是就直接返回(即不会阻塞);如果不是,将表示该节点的线程入列,然后将自身阻塞。当其它线程调用countDown方法会将计数器减1,然后判断计数器的值是否为0,当它为0时,会唤醒队列中的第一个节点,由于CountDownLatch使用了AQS的共享模式,所以第一个节点被唤醒后又会唤醒第二个节点,以此类推,使得所有因await方法阻塞的线程都能被唤醒而继续执行。

    应用场景1:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为n new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行;另外 await() 可以限制时间,如果其他不能在规定时间内完成,该等待主线程也不再等待了。

       应用场景2:实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。

      缺点:CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。

应用场景1例子:

package com.mmall.concurrency.example.aos;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
public class CountDownLatchExample2 {

    private final static int threadTotal=200;

    public static void main(String[] args) throws Exception {

        ExecutorService executorService=Executors.newCachedThreadPool();

        CountDownLatch count=new CountDownLatch(threadTotal);

        for(int i=0;i<threadTotal;i++){
            final int threadNum=i;
            executorService.execute(()->{
                try {
                    test(threadNum);
                }catch (Exception e){
                    log.error("Exception",e);
                }finally {
                    count.countDown();
                }

            });
        }
        count.await(10,TimeUnit.MILLISECONDS);
        log.info("finished");
        System.out.println(count);
        executorService.shutdown();
    }

    public static void test(int threadNum) throws Exception{
        Thread.sleep(100);
        log.info("threadNum={}",threadNum);

    }
}
输出:
15:22:20.581 [pool-1-thread-2] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=1
15:22:20.581 [pool-1-thread-1] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=0
15:22:20.581 [pool-1-thread-3] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=2
15:22:20.586 [pool-1-thread-4] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=3
15:22:20.678 [pool-1-thread-5] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=4
15:22:20.678 [pool-1-thread-6] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=5
15:22:20.679 [pool-1-thread-7] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=6
15:22:20.683 [pool-1-thread-8] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=7
15:22:20.683 [pool-1-thread-11] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=10
15:22:20.683 [pool-1-thread-12] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=11
15:22:20.683 [pool-1-thread-14] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=13
15:22:20.683 [pool-1-thread-9] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=8
15:22:20.683 [pool-1-thread-10] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=9
15:22:20.683 [pool-1-thread-13] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=12
15:22:20.684 [pool-1-thread-17] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=16
15:22:20.684 [pool-1-thread-19] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=18
15:22:20.684 [pool-1-thread-21] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=20
15:22:20.683 [pool-1-thread-15] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=14
15:22:20.683 [pool-1-thread-16] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=15
15:22:20.684 [pool-1-thread-18] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=17
15:22:20.684 [pool-1-thread-22] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=21
15:22:20.684 [pool-1-thread-23] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=22
15:22:20.688 [pool-1-thread-20] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=19
15:22:20.688 [pool-1-thread-26] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=25
15:22:20.688 [pool-1-thread-27] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=26
15:22:20.688 [pool-1-thread-28] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=27
15:22:20.688 [pool-1-thread-29] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=28
15:22:20.688 [pool-1-thread-30] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=29
15:22:20.688 [pool-1-thread-33] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=32
15:22:20.688 [main] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - finished
java.util.concurrent.CountDownLatch@47d384ee[Count = 171]
15:22:20.689 [pool-1-thread-24] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=23
15:22:20.689 [pool-1-thread-34] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=33
15:22:20.689 [pool-1-thread-35] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=34
15:22:20.689 [pool-1-thread-36] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=35
15:22:20.689 [pool-1-thread-4] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=40
15:22:20.690 [pool-1-thread-3] INFO com.mmall.concurrency.example.aos.CountDownLatchExample2 - threadNum=41
......

   

    Semaphore:

        Semaphore是一个计数信号量,它的本质是一个”共享锁”。

        信号量维护了一个信号量许可集。线程可以通过调用acquire(),来获取信号量的许可;当信号量中有可用的许可时,线程能获

取该许可;否则线程必须等待,直到有可用的许可为止。线程可以通过release()来释放它所持有的信号量许可。注意acquire()可

以传入多个参数,例如一次获取多个许可,超时时间等。

        适用场景:例如数据库连接的数量有限,远低于并发访个数。

package com.mmall.concurrency.example.aos;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class SemaphoreExample1 {

    private final static int threadTotal=20;

    private final static int threadConcurrent=3;

    public static void main(String[] args) throws Exception {

        ExecutorService executorService=Executors.newCachedThreadPool();


        final Semaphore semaphore=new Semaphore(threadConcurrent);

        for(int i=0;i<threadTotal;i++){
            final int threadNum=i;
            executorService.execute(()->{

                try {
                    semaphore.acquire();//获取一个许可
                    test(threadNum);
                    semaphore.release();//释放一个许可
                }catch (Exception e){
                    log.error("Exception",e);
                }

            });
        }

        executorService.shutdown();
    }

    public static void test(int threadNum) throws Exception{
        log.info("threadNum={}",threadNum);
        Thread.sleep(1000);

    }
}


    CyclicBarrier:

        CyclicBarrier字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

    
        CountDownLatch和CyclicBarrier的区别:

        CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 

        CyclicBarrier: N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。

        这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。

        而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。

        CountDownLatch 是计数器, 线程完成一个就记一个, 就像报数一样, 只不过是递减的。

        而CyclicBarrier更像一个水闸, 线程执行就想水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流。

        CyclicBarrier计数器可以重置,循环使用。

package com.mmall.concurrency.example.aos;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class CyclicBarrierExample2 {

    private final static int threadTotal=20;


    private static CyclicBarrier cyclicBarrier=new CyclicBarrier(5,()->{
        log.info("start~~~~~");
    });

    public static void main(String[] args) throws Exception {

        ExecutorService executorService=Executors.newCachedThreadPool();

        for(int i=0;i<threadTotal;i++){
            final int threadNum=i;
            Thread.sleep(1000);
            executorService.execute(()->{
                try {
                    test(threadNum);
                }catch (Exception e){
                    log.error("Exception",e);
                }

            });
        }

        executorService.shutdown();
    }

    public static void test(int threadNum) throws Exception{
        Thread.sleep(1000);
        log.info("threadNum,wait={}",threadNum);
        cyclicBarrier.await();
        log.info("threadNum,continue={}",threadNum);
    }
}


    ReentrantLock:    

        synchronized是jvm层面的锁,是不会引发死锁的。ReentrantLock需要加锁和解锁,有死锁可能。

        在JDK5.0版本之前,重入锁的性能远远好于synchronized关键字,JDK6.0版本之后synchronized 得到了大量的优化,二者

性能也不分伯仲,但是重入锁是可以完全替代synchronized关键字的。此外,重入锁又自带一系列高逼格:可中断响应、锁申请

等待限时、可以指定非公平锁和公平锁(先等待的先执行),另外可以结合Condition来使用,可以分组唤醒需要唤醒的线程。


    适用场景:

        1.只有少量线程的时候适合Synchronized

        2.线程不少,但是增长可以预估的适合ReentrantLock

 private static void add(){
        lock.lock();
        try {
            count++;
        }catch (Exception e){
            log.info("exception={}",e);
        }finally {
            lock.unlock();
        }
    }

    
    Condition:

        Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法

相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法.不同的是Object中的wait(),notify(),notifyAll()

方法是和"同步锁" (synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。

         CountDownLatch、CyclicBarrier、Condition都是一个同步工具,它主要用线程执行之间的协作。

ReentrantLock配合Condition使用:

package com.mmall.concurrency.example.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class LockExample2 {
    public static void main(String[] args) {
        ReentrantLock reentrantLock=new ReentrantLock();
        Condition condition=reentrantLock.newCondition();

        new Thread(()->{
            try{
                reentrantLock.lock();
                log.info("wait signal");//执行顺序1
                condition.await();//等待状态,其他线程可以获取锁
            }catch (Exception e){
                e.printStackTrace();
            }
            log.info("get signal");//执行顺序4
            reentrantLock.unlock();

        }).start();

        new Thread(()->{
            reentrantLock.lock();
            log.info("get lock");//执行顺序2
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition.signalAll();
            log.info("send signal");//执行顺序3
            reentrantLock.unlock();
        }).start();
    }

}


    FutureTask(并不是AQS子类): 

        在Java中一般通过继承Thread类或者实现Runnable接口这两种方式来创建多线程,但是这两种方式都有个缺陷,就是不能

在执行完成后获取执行的结果,因此Java1.5之后提供了Callable和Future接口,通过它们就可以在任务执行完毕之后得到任务的

执行结果。

       详见: http://www.importnew.com/25286.html

package com.mmall.concurrency.example.future;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j
public class FutureExample1 {

    static class myCallable implements Callable<String> {

        @Override
        public String call() throws Exception {
            log.info("do something in callable");
            Thread.sleep(5000);
            return "done";
        }
    }

    public static void main(String[] args) throws Exception{
        ExecutorService executors=Executors.newCachedThreadPool();
        Future<String> future=executors.submit(new myCallable());
        log.info("do something in main");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String result=future.get();
        log.info("result={}",result);
    }
}
package com.mmall.concurrency.example.future;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j
public class FutureTaskExample2 {
    public static void main(String[] args) throws Exception{
        FutureTask<String> futureTask=new FutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                log.info("do something in callable");
                Thread.sleep(5000);
                return "done";
            }
        });

        new Thread(futureTask).start();
        log.info("do something in main");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String result=futureTask.get();
        log.info("result={}",result);
    }
}


    Fork/Join框架详解(与Future相关):

        Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。===>MapReduce

      Fork/Join框架要完成两件事情:

  1. 任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割。

     2. 执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行

         完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。

       详见: https://www.cnblogs.com/senlinyang/p/7885964.html

package com.mmall.concurrency.example.forkjoin;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
@Slf4j
public class ForkJoinExample1 extends RecursiveTask<Integer> {
    private static final int threshold=2;
    private int start;
    private int end;
    public ForkJoinExample1(int start,int end){
        this.start=start;
        this.end=end;
    }

    @Override
    protected Integer compute() {
        int n=start-end;
        int sum=0;
        if(n<threshold){
            for(int i=start;i<=end;i++){
                sum+=i;
            }
        }else{
            int mid=(start+end)/2;
            ForkJoinExample1 leftTask=new ForkJoinExample1(start,mid);
            ForkJoinExample1 rightTask=new ForkJoinExample1(mid+1,end);

            //执行子任务
            leftTask.fork();
            rightTask.fork();

            //等待任务执行结束 合并结果
            int leftResult=leftTask.join();
            int rightResult=rightTask.join();

            sum=leftResult+rightResult;
        }
        return sum;
    }

    public static void main(String[] args) {
        ForkJoinExample1 task=new ForkJoinExample1(1,100);
        ForkJoinPool forkJoinPool=new ForkJoinPool();
        Future<Integer> future=forkJoinPool.submit(task);

        try {
            int result=future.get();
            log.info("result={}",result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}


    BlockingQueue:

        在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线

程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。多线程环境中,通过队列可以很容易实现数据共享,比

如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若

干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他

们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生

产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻

塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。然而,在concurrent包发布以前,在多线程环境下,

我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。好在此

时,强大的concurrent包横空出世了,而他也给我们带来了强大的BlockingQueue。

        详见:https://www.cnblogs.com/KingIceMou/p/8075343.html

七、线程池:

         在开发中,频繁的创建和销毁一个线程,是很耗资源的,为此找出了一个可以循环利用已经存在的线程来达到自己的目

         的,线程池顾名思义,也就是线程池的集合,通过线程池执行的线程任务,可以很有效的去规划线程的使用。

        new Thread()弊端:

            1.每次new Thread 新建对象,性能差。

            2.线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM(Out Of Memory)

            3.缺少更多功能,如更多执行、定期执行、线程中断。

        线程池的好处:

            1.重用存在的线程,减少对象创建、消亡的开销,性能佳。

            2.可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。

            3.提供定时执行、定期执行、单线程、并发数控制等功能。


    线程池类:ThreadPoolExecutor-->构造方法相关参数

            1.corePoolSize:核心线程数量

            2.maxmumPoolSize:线程最大线程数

            3.workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。

            4.keepAliveTime:线程没有任务执行时最多保持多久时间终止

            5.unit:keepAliveTime时间单位

            6.threadFactory:线程工厂,用来创建线程

            7.rejectHandler:当拒绝处理任务时的策略


    ThreadPoolExecutor提供的方法:

        基础方法:

            1.execute():提交任务,交给线程池执行

            2.submit():提交任务,能够返回执行结果=execute+Future

            3.shutdown():关闭线程池,等待任务都完成

            4.shutdownNow():关闭线程池,不等待任务执行完

        监控方法:

            1.getTaskCount():线程池已执行和未执行的任务总数。

            2.getCompletedTaskCount():已完成的任务数量

            3.getPoolSize():线程池当前的线程数量。

            4.getPoolSize():线程池当前的线程数量

            5.getActiveCount():当前线程池中正在执行任务的线程数量。

    线程池---Executor框架接口:(以下四种线程池都是由一个线程工具类Executors来创造的)

        1.Executors.newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过需要的线程数量,可灵活回收空闲线程,若无可回收,则新建线程。

        2.Executors.newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待,当创建的线程池数量为1的时候。也类似于单线程化的线程池,当为1的时候,也可控制线程的执行顺序

        3.Executrors.newSingleThreadPool:创建一个单线程化的线程池, 它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,可以控制线程的执行顺序

        4.Executors.newScheduledTreadPool:创建一个定长线程池,支持定时及周期性任务执行。,可以作一个定时器使用。


详见:https://www.cnblogs.com/qm-article/p/7821602.html

八、多线程扩展知识:

    多线程产生死锁的条件:互斥条件、请求和保持条件、不剥夺条件、环路等待条件

九、多线程并发最佳实践:

            1.使用本地变量

            2.使用不可变类

            3.最小化锁的作用于范围:s=1/(1-a+a/n)---->阿姆达尔公式

           阿姆达尔曾致力于并行处理系统的研究。对于固定负载情况下描述并行处理效果的加速比s,阿姆达尔经过深入研究给出了

S=1/(1-a+a/n)其中,a为并行计算部分所占比例,n为并行处理结点个数。这样,当1-a=0时,(即没有串行,只有并行)最大加

速比s=n;当a=0时(即只有串行,没有并行),最小加速比s=1;当n→∞时,极限加速比s→ 1/(1-a),这也就是加速比的上限

例如,若串行代码占整个代码的25%,则并行处理的总体性能不可能超过4。

            4.使用线程池的Executor,而不是直接new Thread执行

            5.宁可使用同步也不要使用线程的wait和notify

            6.使用BlockingQueue实现生产-消费模式

            7.使用并发集合而不是加了锁的同步集合

            8.使用Semaphore创建有界的访问

            9.宁可使用同步代码块也不要使用同步方法

            10.避免使用静态变量

重要专题讲解:

一、Spring与线程安全:

SpringMVC是基于方法的拦截,而Struts2是基于类的拦截。

对于Struts2来说,因为每次处理一个请求,struts就会实例化一个对象,这样就不会有线程安全的问题了;

而Spring的controller默认是Singleton的,这意味着每一个request过来,系统都会用原有的instance去处理。

好处:一是我们不用每次创建Controller,二是减少了对象创建和垃圾回收的时间;

缺点:只有一个Controller的instance,当多个线程调用它的时,里面的instance变量就不是线程安全的了,会发生窜数据问题。

 

当然大多数情况下,我们根本不需要考虑线程安全的问题,比如dao,service等,除非在bean中声明了实例变量。因此,我们在使用spring mvc 的contrller时,应避免在controller中定义实例变量。  

保证Spring线程安全的策略:
1、在控制器中不使用实例变量(可以使用方法参数的形式解决)
2、在spring配置文件Controller声明 scope="prototype",每次都创建新controller。默认Controller、Dao、Service都是单例的。3、在Controller中使用ThreadLocal变量

详见:https://blog.csdn.net/a236209186/article/details/61460211

二、HashMap和ConcurrentHashMap

HashMap不支持并发操作,线程不安全。HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。在插入新值的时候,如果当前的 size 已经达到了阈值,并且要插入的数组位置上已经有元素,那么就会触发扩容,扩容后,数组大小为原来的 2 倍。扩容就是用一个新的大数组替换原来的小数组,并将原来数组中的值重新哈希迁移到新的数组中,多线程进行该步骤操作会造成线程不安全。

Java7中ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。concurrencyLevel:并发数或者说Segment 数,默认是 16,也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,初始化以后不允许扩容的。而具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。

Java8 中对HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由数组+链表+红黑树组成。根据 Java7 HashMap 的介绍,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

Java8中ConcurrentHashMap 就不讨论了,太难了。

详见:http://www.importnew.com/28263.html

 

高并发之扩容思路:

1、垂直扩容(纵向扩展):提高系统部件能力以提高效率。

 

2、水平扩容(横向扩展):增加更多系统成员来实现。

 

数据库--扩容:

 

1、读操作扩展:memcache、redis、CDN等缓存

 

2、写操作扩展。

 

主要看自身业务需求,如果是博客系统,读操作远多于写操作,那就想办法进行读操作扩展。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值