[笔记]-java-JUC学习笔记

13 篇文章 1 订阅

学习juc时的个人笔记

1、线程

Enum Thread.State

A thread state. A thread can be in one of the following states:
NEW
A thread that has not yet started is in this state.
RUNNABLE
A thread executing in the Java virtual machine is in this state.
BLOCKED
A thread that is blocked waiting for a monitor lock is in this state.
WAITING
A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
TIMED_WAITING
A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
TERMINATED
A thread that has exited is in this state.
A thread can be in only one state at a given point in time. These states are virtual machine states which do not reflect any operating system thread states.

wait/sleep区别

实际开发中一般使用juc.TimeUnit来休眠

来自不同类

  • wait:object
  • sleep:thread

  • wait: 释放锁
  • sleep:不释放锁

使用范围

  • wait:仅能在同步代码块中使用
  • sleep: 任何地方

是否捕获异常

  • wait:不需要 (这里我查到的资料有说也需要抛出异常,有IllegalMonitorStateException和InterruptedException)
  • sleep:需要捕获超时等待异常

使用

一般情况下,使用wait的格式是:

synchronized (obj){
	while(condition does not hold){
		obj.wait(timeout)
	}
	// ohter perform
	obj.notifyAll()
}

这里使用 while 判断的原因是,如果使用 if 可能存在多个线程并发出现虚假唤醒情况。(就是多个线程在判断时满足条件,但都执行了)

Threadlocal

这里只是我对 threadlocal的个人理解(不保证正确):

首先,每一个 thread 中都维护一个 threadlocalmap (这里的map并不是和hashmap一样的map,仅是相似)

其中map的每一个 Key 是所用到的 threadlocal 对象的引用,value即为threadlocal 对象所设置的共享变量的副本值

当线程要使用变量副本值时,将直接查找到对应 threadlocal 的引用对应的 entry ,然后获取到值

OOM问题:map中的entry 是一个弱引用,当 key 没有引用后就会被gc,但是value是一个强引用,所以需要手动调用 remove() 来保证使用完后销毁,否着,如果线程一直存在并且总是使用threadlocal的值, map就会越来越大,出现OOM异常

普通的多线程例子

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Time;

public class Main {

    public static void main(String[] args) throws IOException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");

        Ticket ticket = new Ticket();
        new Thread(() -> {
            try {
                for (int i = 0; i < 30; ++i) {
                    ticket.sale();
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 30; ++i) {
                    ticket.sale();
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "B").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 30; ++i) {
                    ticket.sale();
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "C").start();

    }
}

class Ticket {
    private int number = 20;

    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + " get " + (number--) + " th");
        }
    }
}

2、LOCK

创建锁的方式:

Lock lock = new ReentrantLock();


默认情况下,建立的锁是 非公平锁NonfairSync()

  • 公平锁:先来后到
  • 非公平锁:可以插队

使用lock 加解锁的方式要使用trycatchfinally语句块,其中要将 unlock() 放在finally中

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Time;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {

    public static void main(String[] args) throws IOException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");

        Ticket ticket = new Ticket();
        // new Thread(()->{for(int i = 0; i < 20; ++i){ticket.sale();}}, "A").start();
        // new Thread(()->{for(int i = 0; i < 20; ++i){ticket.sale();}}, "B").start();
        // new Thread(()->{for(int i = 0; i < 20; ++i){ticket.sale();}}, "C").start();

        new Thread(() -> {
            try {
                for (int i = 0; i < 30; ++i) {
                    ticket.sale();
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 30; ++i) {
                    ticket.sale();
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "B").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 30; ++i) {
                    ticket.sale();
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "C").start();
    }

        
}

class Ticket {
    private int number = 20;
    Lock lock = new ReentrantLock();

    public synchronized void sale() {
        lock.lock();
        try {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + " get " + (number--) + " th");
            }
        } catch (Exception e) {
            //TODO: handle exception
        }
        finally{
            lock.unlock();
        }
    }
}

synchronized 与 Lock的区别

  • synchronized 内置关键字;lock是一个类
  • synchronized 无法判断锁的状态;lock可以判断是否获取到锁(tryLock()
  • synchronized 会自动释放锁;lock需要通过 unlock 来手动释放,(不释放可能死锁)
  • synchronized 如果一个线程在获取到锁阻塞,其他线程会等待;lock可以使用 tryLock 尝试获取锁
  • synchronized 可重入锁,不可中断,非公平;可重入锁,可以判断中断,可以手动设置是否公平(默认构造非公平,false设置为公平)
  • synchronized 适合锁少量的代码同步;lock适合锁大量同步的代码(灵活)

生产者消费者模型

synchronized实现

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Time;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {

    public static void main(String[] args) throws IOException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");

        Data data = new Data();
        
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                try {
                    data.inc();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                try {
                    data.dec();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                try {
                    data.dec();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                try {
                    data.inc();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }, "D").start();
        
    }

        
}

class Data{
    private int number = 0;

    public synchronized void inc() throws InterruptedException{
        while(number != 0){
            this.wait();
        }
        ++number;
        System.out.println(Thread.currentThread().getName() + " -> " + number);
        this.notifyAll();
    }
    public synchronized void dec() throws InterruptedException{
        while(number == 0){
            this.wait();
        }
        --number;
        System.out.println(Thread.currentThread().getName() + " -> " + number);
        this.notifyAll();
    }
}

(注意同步方法的写法:循环判断等待、业务、通知唤醒;其中循环判断的原因是防止出现虚假唤醒)

Lock实现

如图,Lock相当于是synchronized,而wait和notifyAll相当于condition中的await和signal

jdk中的示例如下:


还是循环判断条件await等待、业务、signal唤醒(signalAll可以唤醒全部);最后再将lock、unlock加上

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Time;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {

    public static void main(String[] args) throws IOException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");

        Data data = new Data();
        
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                try {
                    data.inc();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                try {
                    data.dec();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                try {
                    data.dec();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                try {
                    data.inc();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }, "D").start();
        
    }

        
}

class Data{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void inc() throws InterruptedException{

        lock.lock();
        try {
            while(number != 0){
                condition.await();
            }
            ++number;
            System.out.println(Thread.currentThread().getName() + " -> " + number);
            condition.signalAll();
            
        } catch (Exception e) {
            //TODO: handle exception
        }
        finally{
            lock.unlock();
        }

    }
    public void dec() throws InterruptedException{
        lock.lock();
        try {
            while(number == 0){
                condition.await();
            }
            --number;
            System.out.println(Thread.currentThread().getName() + " -> " + number);
            condition.signalAll();
            
        } catch (Exception e) {
            //TODO: handle exception
        }
        finally{
            lock.unlock();
        }
    }
}

多通知循环线程:

即使用多个信号量来处理。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Time;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {

    public static void main(String[] args) throws IOException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");

        Data data = new Data();
        
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                    data.printA();
            }
        }, "A").start();
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                    data.printB();
            }
        }, "B").start();
        new Thread(()->{
            for(int i = 0; i < 10; ++i){
                    data.printC();
            }
        }, "C").start();    
    }

        
}

class Data{
    private int number = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void printA(){
        lock.lock();
        try {
            while(number != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "AAAAAAAAA");
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            //TODO: handle exception
        }
        finally{
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            while(number != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "BBBBBBBBB");
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            //TODO: handle exception
        }
        finally{
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            while(number != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "CCCCCCCCC");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            //TODO: handle exception
        }
        finally{
            lock.unlock();
        }
    }
}

3、八锁问题

其实就是对 synchronized 所锁的情况进行讨论即可:参考

4、并发集合操作

CopyOnWriteArrayList

对于普通的集合多线程操作可能会出现 java.util.ConcurrentModificationException并发修改异常 如:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {

    public static void main(String[] args) throws IOException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");

        List<String> list = new ArrayList<>();
        for(int i = 0; i < 10; ++i){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();;
        }
    }
}


(可能需要多次运行才能复现,jdk版本较高的话也不容易出现,我的是jdk11)

解决方案:

  • List<String> list = new Vector<>(); 这个是之前常用的,但是很慢(它比arraylist出现的要早)
  • List<String> list = Collections.synchronizedList(new ArrayList<>());
  • JUC

如:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {

    public static void main(String[] args) throws IOException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");

        // List<String> list = new ArrayList<>();
        // List<String> list = new Vector<>();
        // List<String> list = Collections.synchronizedList(new ArrayList<>());
        List<String> list = new CopyOnWriteArrayList<>();
        for(int i = 0; i < 10; ++i){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();;
        }
    }

        
}

(在jdk11后,CopyOnWriteArrayList中的是通过 synchronized 实现的,因为 synchronized 升级了):


CopyOnWriteArraySet

同理,set:

Set<String> set = new HashSet<>();
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();

ConcurrentHashMap

map:

Map<String, String> map = new HashMap<>();
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
Map<String, String> map = new ConcurrentHashMap<>();

(不支持key、value为null)

Callable

与 runnable 区别:

  • 可以有返回值
  • 可以抛出异常
  • 方法不同: call
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;


public class Main {

    public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");
        
        Mythread thread = new Mythread();
        FutureTask futureTask = new FutureTask<>(thread);
        new Thread(futureTask, "A").start();
        System.out.println(futureTask.get());

    }

        
}


class Mythread implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("call....");
        return "233333333";
    }
}

注意:

  • get方法可能会阻塞
  • 多个调用时,后调用因为状态会不执行,直接使用保存的返回值(未认真确认)

5、常用辅助类

CountDownLatch

减法计数器,当计数器被子线程(子任务)完成后减一归零后,包含计数器的线程才会执行。

示例:

public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");
        
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for(int i = 0; i < 5; ++i){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName());
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();

        System.out.println("main....");
        
    }
   

CyclicBarrier

加法计数器(每指定个数个执行一次父线程)

CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
  System.out.println("main...");
});
for(int i = 0; i < 7; ++i){
    final int tmp = i;
    new Thread(()->{
        System.out.println(Thread.currentThread().getName() + " " + tmp);
        try {
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + tmp + "....");
    }).start();
}

cyclicBarrier.await(); : 加一操作,会阻塞
new CyclicBarrier(int, runnable) 初始化计数器大小,以及所有子任务执行完后要调用的线程

Semaphore

信号量

public static void main(String[] args) throws IOException, ExecutionException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");
        
        Semaphore semaphore = new Semaphore(3);
        for(int i = 0; i < 10; ++i){
            new Thread(()->{
                try {
                    semaphore.acquire();
                    int time = new Random().nextInt(5) + 1;
                    System.out.println(Thread.currentThread().getName() + " acquire... for " + time);
                    TimeUnit.SECONDS.sleep(time);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                finally{
                    System.out.println(Thread.currentThread().getName() + " release...");
                    semaphore.release();
                }
                
            }, String.valueOf(i)).start();
        }
        
    }
  • Semaphore semaphore = new Semaphore(3); 创建信号量个数
  • semaphore.acquire(); 获取到一个信号量,
  • semaphore.release(); 释放

6、ReadWriteLock

没有锁的情况:

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;

public class Main {

    public static void main(String[] args) throws IOException, ExecutionException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");
        
        Cache cache = new Cache();
        for(int i = 1; i <= 5; ++i){
            final int tmp = i;
            new Thread(()->{
                cache.put(tmp + "", tmp + "");
            }, String.valueOf(i)).start();
        }
        for(int i = 1; i <= 5; ++i){
            final int tmp = i;
            new Thread(()->{
                cache.get(tmp + "");
            }, String.valueOf(i)).start();
        }
        
        
    }

        
}

class Cache{
    private volatile Map<String, Object> map = new HashMap<>();

    public void put(String key, Object val){
        System.out.println(Thread.currentThread().getName() + " write " + key);
        map.put(key, val);
        System.out.println(Thread.currentThread().getName() + " write done!");
    }
    public void get(String key){
        System.out.println(Thread.currentThread().getName() + " get " + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + " get done!");
    }
}

使用读写锁后可以保证并发操作的原子性:

class CacheLock{
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object val){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " write " + key);
            map.put(key, val);
            System.out.println(Thread.currentThread().getName() + " write done!");
            
        } catch (Exception e) {
            //TODO: handle exception
            e.printStackTrace();
        }
        finally{
            readWriteLock.writeLock().unlock();
        }

    }
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " get " + key);
            map.get(key);
            System.out.println(Thread.currentThread().getName() + " get done!");
            
        } catch (Exception e) {
            //TODO: handle exception
            e.printStackTrace();
        }
        finally{
            readWriteLock.readLock().unlock();
        }
    }
}

其实也就是之前 lock中的一种更加具体的所得使用。这里对于写操作,使用写锁,保证每一个线程的写操作是原子性的,不会被其他写操作插队。对于读操作,使用锁的目的是不会出现写操作时读数据出现幻读。即写锁未独占锁,读锁未共享锁。

7、阻塞队列 BlockingQueue

阻塞队列的四种方式:

方式抛出异常有返回值,但不抛出异常阻塞并等待超时等待
添加add()offer()put()offer(时间)
移除remove()poll()take()poll(时间)
队首elementpeek()--

8、SynchronousQueue

同步队列,只能放入一个元素,再取走后才能继续放入。

9、线程池

线程池的好处:

  • 降低资源消耗
  • 提高响应速度
  • 方便管理

即,线程复用、控制最大并发数、管理线程

线程池的主要知识点: 三大方法、七大参数、四种拒绝策略

三大方法

(注意,线程池的创建在项目中一般是不允许使用 Excutors 来创建,因为 FixedThreadPoolSingleThreadPool 允许的请求队列长度最大为intmax;其次 CacheThreadPoolScheduledThreadPool 允许创建的线程数为 intmax 可能创建大量线程;两者都会导致OOM,故建议使用 ThreadPoolExecutor

三种基本的实现方式是:

ExecutorService threadPool = Executors.newSingleThreadExecutor();       // 单个线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);           // 指定大小的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();           // 可自动扩充的线程池

添加线程的方式是: threadPool.execute(Runnable);

最后要在结束处使用 threadPool.shutdown(); 关闭线程池

七大参数

首先对于以上三种创建线程池的方式,查看其具体实现如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
}

可以看出,具体的实现方式都是通过 ThreadPoolExecutor 这个方法实现的,仅仅是调用时的参数不同,其参数共有七个,查看其实际的构造方法:

public ThreadPoolExecutor(int corePoolSize,             // 核心线程池大小
                            int maximumPoolSize,        // 最大核心线程池大小
                            long keepAliveTime,         // 超时时没有调用释放
                            TimeUnit unit,              // 超时单位
                            BlockingQueue<Runnable> workQueue,  // 阻塞队列
                            ThreadFactory threadFactory,    // 线程工厂,一般使用默认的
                            RejectedExecutionHandler handler // 拒绝策略) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

整个线程池的结构大致如下:

其中核心线程数表示的就是当前可以使用的线程资源,最大线程数就是最多可以使用的线程资源
阻塞队列是一个暂存区域

当任务到来时,如果核心线程数的线程资源可以使用,就直接使用即可,
如果都在使用,这时会添加到阻塞队列中,
如果阻塞队列也满了,并且核心线程数未达到最大线程数,此时扩大核心线程数
如果此时全都满了,线程池都在使用、阻塞队列满了,此时会根据预先设定的拒绝策略来进行操作(即最大承载量为队列容量+最大核心线程数)

超时时间是指,当并发量减少后,一定时间里没有任务添加,,会收回大与核心线程数的资源

四种拒绝策略

new ThreadPoolExecutor.AbortPolicy(): 不处理,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy():哪来去哪,让线程调用者处理
new ThreadPoolExecutor.DiscardPolicy():不处理,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy():替换最早的,不抛出异常

使用方式:

ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());

CPU密集型和IO密集型

最大线程如何定义:

看情况,看当前使用到线程池的任务情况,

  • 如果是 CPU密集型的任务,可以动态获取当前代码运行环境的CPU核数来设置为最大线程数: Runtime.getRuntime().availableProcessors()
  • 如果是 IO密集型任务,即有很多需要IO操作的任务,可以设置为IO任务数两倍左右

10、四大函数式接口

lambda表达式、链式编程、函数式接口、stream流式计算

四大原生函数式接口:

Function
Consumer
Predicate
Supplier

Function


传入参数T,返回R类型结果,及一个函数 R=T,使用时重写 apply即可(如同使用runnable接口重写run方法一样)

Function<String, Integer> function = new Function<String,Integer>(){
    @Override
    public Integer apply(String t) {
        return t.hashCode();
    }
};

// Function<String, Integer> function = (str)->{
//     return str.hashCode();
// };

System.out.println(function.apply("2333"));

(因为是函数式接口,所以可以使用lambda简化)

Predicate


断定性接口

Predicate<String> predicate = new Predicate<String>(){
    @Override
    public boolean test(String t) {
        return (t != null && !t.isEmpty());
    }
};
// Predicate<String> predicate = (str)->{
//     return (str != null && !str.isEmpty());
// };
System.out.println(predicate.test("2333"));
System.out.println(predicate.test(""));

Consumer

消费型接口,只有一个参数,没有返回值。

Consumer<String> consumer = new Consumer<String>(){
    @Override
    public void accept(String t) {
        System.out.println(t);
    }
};
// Consumer<String> consumer = (str)->{System.out.println(str);};
consumer.accept("2333333");

Supplier

在这里插入图片描述
供给式接口,仅有返回值。

Supplier<String> supplier = new Supplier<String>(){
    @Override
    public String get() {
        System.out.println("get");
        return "233333333";
    }
};
// Supplier<String> supplier = ()->{return "233333333";};
System.out.println(supplier.get());

stream

import java.io.IOException;
import java.util.Arrays;
import java.util.List;


public class Main {

    public static void main(String[] args) throws IOException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");
        
        User u1 = new User(1, "AA", 20);
        User u2 = new User(2, "BB", 21);
        User u3 = new User(3, "CC", 22);
        User u4 = new User(4, "DD", 23);
        User u5 = new User(5, "EE", 24);
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        list.stream()
            .filter(u->{return u.getId() % 2 == 0;})
            .filter(u->{return u.getAge() > 21;})
            .map(u->{return u.getName().toLowerCase();})
            .forEach(System.out::println);
        
    }

        
}

class User{
    int id;
    String name;
    int age;
    User(int id, String name, int age){
        this.id = id;
        this.name = name;
        this.age = age;
    }
    public int getId(){
        return id;
    }
    public String getName(){
        return name;
    }
    public int getAge(){
        return age;
    }
    public String toString(){
        return "[user: " + "id: " + id + ", name: " + name + ", age: " + age + "]";
    }
}

11、ForkJoin

分治思想,将大任务拆分为小任务操作,适合大数据处理情景


forkjoin:工作窃取,当某个任务执行完毕后,他会窃取其他未完成的任务,提高效率:

使用:

  • forkjoinpool
  • forkjoinpool.executor(forkjointask)
  • 继承forkjointask,重写compute方法,使用fork,join计算
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;


public class Main {

    private static long END = 10_0000_0000L;

    public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");
        
        test1();
        test2();
        test3();
        
    }

    public static void test1(){
        long start = System.currentTimeMillis();
        long sum = 0;

        for(long i = 1; i <= END; ++i){
            sum += i;
        }
        
        long end = System.currentTimeMillis();
        System.out.println("sum: " + sum + " time: " + (end - start));
    }
    // forkjoin
    public static void test2() throws InterruptedException, ExecutionException{
        long start = System.currentTimeMillis();
        
        long sum = 0;
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        sum = forkJoinPool.submit(new ForkJoinDemo(0, END)).get();

        long end = System.currentTimeMillis();
        System.out.println("sum: " + sum + " time: " + (end - start));
    }
    //并行流 
    public static void test3(){
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0, END).parallel().reduce(0, Long::sum);

        long end = System.currentTimeMillis();
        System.out.println("sum: " + sum + " time: " + (end - start));
    }

        
}


class ForkJoinDemo extends RecursiveTask<Long>{
    private long start;
    private long end;
    private long temp = 10000;
    public ForkJoinDemo(long start, long end){
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        if((end - start) <= temp){
            long sum = 0;
            for(long i = start; i <= end; ++i){
                sum += i;
            }
            return sum;
        }
        else{
            long mid = (start + end) / 2;
            ForkJoinDemo task1 = new ForkJoinDemo(start, mid);
            ForkJoinDemo task2 = new ForkJoinDemo(mid + 1, end);
            task1.fork();
            task2.fork();
            return task1.join() + task2.join();

        }
        
    }
}

并行流最快,其次是forkjoin,最后是普通方法。

12、异步调用

使用 CompletableFuture 可以实现异步调用(即实现类似ajax的带有返回值的异步调用)

13、JMM

关于JMM的一些同步约定

  • 线程解锁前,必须把共享变量立刻刷回主存(线程工作时,回将共享变量从主存拷贝到自己的工作内存空间中,间接操作变量)
  • 线程加锁前,必须读取主存中的最新值到工作内存中
  • 加锁解锁是用一把锁

JMM中的八种操作


(write和store反了)

因为各操作不是原子性的,所以可能出现主存值与线程的工作内存中的值不同的情况:

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

  • 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

14、volatile

volatile 是Java虚拟机的一个轻量级的 同步机制

  • 保证可见性(即线程读取修饰的变量都会从主内存中读取,而不是直接使用工作内存中的副本)
  • 不保证原子性(可以使用synchronized、lock、atomic解决)
  • 禁止指令重排(通过内存屏障实现)

使用atomic实现原子性的 num++ 操作:

其内部实现是通过unsafe类中的一些 cas操作实现,对内存修改值。

public class Main {

    private static volatile AtomicInteger num = new AtomicInteger();
    public static void add(){
        num.getAndIncrement();
    }

    public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
        // BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        // String par[] = bf.readLine().trim().split(" ");
        
        for(int i = 1; i <= 20; ++i){
            new Thread(()->{
                for(int j = 1; j <= 20000; ++j){
                    add();
                }
            }).start();
        }
        while(Thread.activeCount() >= 2);
        System.out.println(Thread.currentThread().getName() + " num: " + num);
        
    }

}

15、单例模式

饿汉式单例模式

class Hungry{
    private static final Hungry HUNGRY = new Hungry();
    private byte[] data1 = new byte[1024 * 1024];
    private byte[] data2 = new byte[1024 * 1024];
    private byte[] data3 = new byte[1024 * 1024];
    private Hungry(){

    }
    public Hungry getInstance(){
        return this.HUNGRY;
    }
}

缺点:启动时就加载,可能会造成内存空间的浪费

懒汉式单例模式

普通的饿汉模式在多线程的情况下会失效:

class LazyMan{
    private static LazyMan LAZYMAN;
    private LazyMan(){
        System.out.println(Thread.currentThread().getName());
    }
    public static LazyMan getInstance(){
        if(LAZYMAN == null){
            LAZYMAN = new LazyMan();
        }
        return LAZYMAN;
    }
}

一种解决方式就是对 getInstance() 加锁,即使用 双重检验+volatile实现:

// DCL实现
class LazyMan{
    private static volatile LazyMan LAZYMAN;
    private LazyMan(){
        System.out.println(Thread.currentThread().getName());
    }
    public static LazyMan getInstance(){
        if(LAZYMAN == null){
            synchronized(LazyMan.class){
                if(LAZYMAN == null){
                    LAZYMAN = new LazyMan();
                }
            }
        }
        return LAZYMAN;
    }
}

其中使用 synchronized 来加锁同步整个类对象;

同时使用 volatile 保证在实例化过程中的指令不重排:实例化对象过程并不是原子操作,有很多指令实现,如:

  • 分配内存空间
  • 执行构造方法,初始化对象
  • 将对象指向内存空间

如果进行指令重排,可能会出现先指向内存地址,在初始化的情况,此时如果在操作中间访问,对象是不为null的,直接返回的结果就是一个为初始化构造的对象

静态内部类

基于类加载机制的懒汉模式

class LazyMan{
    private static class InnerClass{
        private static final LazyMan LAZYMAN = new LazyMan();
    }
    private LazyMan(){}
    public static LazyMan getInstance(){
        return InnerClass.LAZYMAN;
    }
}

以上几种对于反射机制不安全:

枚举

可以使用枚举类来实现单例模式,保证不会被反射实例化:

// enum:

enum LazyMan{
    INSTANCE;
    public LazyMan getInstance(){
        return INSTANCE;
    }
}

原因:
反射类中实例化时会判断是否为枚举类的反射:

注意 此处如要使用枚举类进行反射测试:

反射构造器的参数不能为空或null,必须为 String.class, int.class,否则此时异常为:

而不是因为枚举类反射实例化所抛出的异常:

因为enum实际会产生一个 String.class, int.class 为形参的构造器,而不是空的构造器,这里需要通过 jdx来查看反汇编后的代码

更加具体的讲解强烈建议观看此视频:

16、CAS

比较并交换:当所期望的值相同时才进行更新:compareAndSet

底层是通过c++编写的更靠近底层的代码实现(具体到CPU指令,unsafe类)

例如 atomicInteger中的加一线程安全的加一操作的内部实现是通过 cas自旋实现(注意不同jdk源码可能不一致):


其中while就是一种自旋操作,通过循环判断来实现一种比加锁效率更高的方式;字段解释:判断 o 对象中偏移值为 offset处的数据值是否为 v ,如果是就加 delta 。这种实现方式就是自旋锁。

总结:CAS是不断比较当前内存中的值和期望的值是否一致,如果一致,则进行set操作;如果不一致就通过循环实现自旋。

缺点:

  • 循环会有一定的耗时
  • 一次仅能保证对一个共享变量的原子性
  • ABA问题

ABA问题

ABA问题就是指,多个线程对共享变量进行操作,其中A和B都读取到变量a=1,但是B线程做了两次操作:a=3,a=1,对于A线程来说,可能并不知道A在期间被修改过(狸猫换太子)

解决方式:原子引用

通过使用一个带有时间戳(版本号)的原子引用即可以解决ABA问题,思想是 乐观锁

原子引用: AtomicStampedReference 来实现,可以通过获取当前的stamp来保证不会出现ABA问题

17、锁

公平锁、非公平锁

  • 公平锁: 非常公平,不可以插队,即必须按照先来后到执行
  • 非公平锁:非常不公平,可以插队(默认的锁是非公平锁)

可重入锁

可重入锁即当调用加锁的方法后,该方法又调用其他方法,相当于加锁多次,但不产生死锁;某个线程获取某个锁后,可以再次获取锁而不死锁。即可理解为一种递归锁,外层函数获取锁,内层函数自动获取,解决死锁。

对于 synchronized 的可重入锁直接使用即可,对于 Lock 的锁,需要保证前后加解锁次数一致,否则会出现死锁现象。

自旋锁 spinlock

就是通过一个循环CAS操作实现自旋,使用循环来代替加解锁的较低效操作。

死锁

多线程下出现:

  • 互斥

  • 循环等待

  • 请求与保持

  • 不可剥夺

  • (查看Java的进程可以使用 jps 命令)

  • 使用 jstack 可以查看进程的堆栈信息,可以发现死锁等

18、AQS

这里我只是简单的学习了一下,具体的学习建议查看其它博主的学习笔记。

AQS 即 抽象 队列 同步器

是juc包中几乎所有类的基础,可以实现独占锁(lock)或者是共享锁(信号量、计数器等等)

他的内部结构主要有一个 state 状态、一个 CLH队列

原理:

当新增一个线程时,首先会尝试获取资源,如果可以获取到 state 会为1,当然因为可重入原因,同一个线程多次获取会增加

如果再来多个线程,此时会通过 封装为一个 node 节点,插入队列后,同时设定自身的状态,等待唤醒

当头节点的下一个节点(即头节点为空节点)使用完毕资源并释放后,他会从后往前找到最近的一个可以获取资源的线程,并唤醒,同时设置后继等待状态,如此循环下去,实现多线程对资源的操作。

所有操作都是CAS实现的。

最后贴个思维导图

来源

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值