java高并发(并发工具篇)

可重入锁

在jdk1.5之前ReenterLock的性能有优于synchroized,但是之后jdk在synchroized做了很很多优化,使两者的性能差距相近。

import java.util.concurrent.locks.ReentrantLock;

public class ReenterLock implements Runnable{
	public static ReentrantLock lock=new ReentrantLock();
	public static int i=0;
	@Override
	public void run() {
		for(int j=0;j<10000000;j++){
			lock.lock();
			lock.lock();//锁是可反复进入的
			try{
				i++;
			}finally{
				lock.unlock();
				lock.unlock();//释放的时候也要释放相同的次数,如果释放的次数多了会抛异常
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ReenterLock tl=new ReenterLock();
		Thread t1=new Thread(tl);
		Thread t2=new Thread(tl);
		t1.start();t2.start();
		t1.join();t2.join();
		System.out.println(i);
	}
}
中断响应

对于synchroized如果一个线程正在等待锁,那么结果只有两种情况,要么她获得这把锁继续执行,要么他就保持等待,而是用可重入锁,则提供另外一种可能,线程可以被中断,在等待锁的过程中,程序可以根据需要取消对锁的请求。

import java.util.concurrent.locks.ReentrantLock;

public class IntLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    public IntLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1) {
                lock1.lockInterruptibly();
                Thread.sleep(500);
                lock2.lockInterruptibly();
            } else {
                lock2.lockInterruptibly();
                Thread.sleep(500);
                lock1.lockInterruptibly();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock1.isHeldByCurrentThread()) {
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread()) {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() + "线程退出");
        }

    }

    public static void main(String args[]) throws InterruptedException {
        IntLock r1 = new IntLock(1);
        IntLock r2 = new IntLock(2);
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);
        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        thread2.interrupt();
    }
}
<<<
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.atmb.me.IntLock.run(IntLock.java:24)
	at java.lang.Thread.run(Thread.java:748)
12线程退出
11线程退出

锁申请等待限时

除了等待外部通知以外,要避免死锁还有另外一种方法限时等待,可以使用tryLock进行限时的等待。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TimeLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {//第一个参数为时长,第二个但是为单位,如果获得成功就返回true,否则返回失败
                System.out.println(Thread.currentThread().getName());
                System.out.println("get lock success");
                Thread.sleep(6000);
            } else {
                System.out.println(Thread.currentThread().getName());
                System.out.println("get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static void main(String args[]) {
        TimeLock timeLock = new TimeLock();
        Thread thread1 = new Thread(timeLock);
        Thread thread2 = new Thread(timeLock);

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

tryLock也可以不带参数直接运行,线程会不等待的获取锁,获取成功返回true,获取失败返回false

import java.util.concurrent.locks.ReentrantLock;

public class TryLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    public TryLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        if (lock == 1) {
            while (true) {
                if (lock1.tryLock()) {
                    try {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (lock2.tryLock()) {
                            try {
                                System.out.println(Thread.currentThread().getId() + ":My Job done;");
                                return;
                            } finally {
                                lock2.unlock();
                            }
                        }
                    } finally {
                        lock1.unlock();
                    }
                }
            }
        } else {
            while (true) {
                if (lock2.tryLock()) {
                    try {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (lock1.tryLock()) {
                            try {
                                System.out.println(Thread.currentThread().getId() + ":My Job done;");
                                return;
                            } finally {
                                lock1.unlock();
                            }
                        }
                    } finally {
                        lock2.unlock();
                    }
                }
            }

        }
    }

    public static void main(String args[]) {
        TryLock r1 = new TryLock(1);
        TryLock r2 = new TryLock(2);
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);
        thread1.start();
        thread2.start();
    }

}

使用tryLock后,如果当前线程获取不到想要的锁,就会释放当前持有的锁,然后重新获取所有需要的锁。

公平锁

公平锁的特点是,不会产生饥饿,只要排队肯定最终能得到锁资源,使用syschorized关键字进行锁控制,产生的锁是非公平的,ReenterLock可以对锁的公平性进行设置。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    public ReentrantLock() {
        sync = new NonfairSync();//默认情况下是不公平的
    }
package com.atmb.me;

import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by 13 on 2017/5/5.
 */
public class FairLock implements Runnable {

    public static ReentrantLock fairLock = new ReentrantLock(true);//设置true指定锁是公平的,也可以不设置,分别运行观察公平锁与非公平锁间的区别
    //public static ReentrantLock unfairLock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                fairLock.lock();
                // unfairLock.lock();
                System.out.println(Thread.currentThread().getName() + "获得锁");
            } finally {
                fairLock.unlock();
                // unfairLock.unlock();
            }
        }
    }
    
    public static void main(String args[]) {
        FairLock r1 = new FairLock();
        Thread thread1 = new Thread(r1, "Thread_t1");
        Thread thread2 = new Thread(r1, "Thread_t2");
        Thread thread3 = new Thread(r1, "Thread_t3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
<<<
Thread_t1获得锁
Thread_t2获得锁
Thread_t3获得锁
Thread_t1获得锁
Thread_t2获得锁
Thread_t3获得锁
Thread_t1获得锁
Thread_t2获得锁
Thread_t3获得锁
Thread_t1获得锁

基本上是交替执行的
使用非公平锁,系统的调度会倾向于再次获取已经持有锁,这种分配方法很高效,但是不公平

Thread_t1获得锁
Thread_t1获得锁
Thread_t1获得锁
Thread_t1获得锁
Thread_t1获得锁
Thread_t2获得锁
Thread_t2获得锁
Thread_t2获得锁
Thread_t2获得锁
Thread_t2获得锁

Condition

Condition可以与ReenTrantLock配合使用,new Condition可以生成一个与当前重入锁绑定的Condition实例。
Condition接口中的基本方法如下:

void await() throws InterruptedException;
使当前方法等待,同时释放当前锁,当其他线程使用singnal或者signalAll方法时u,线程会重新获取锁并继续执行,当前线程被中断也能跳出等待。
void awaitUninterruptibly();
不会再等待过程中响应中断
long awaitNanos(long nanosTimeout) throws InterruptedException;
long awaitNanos(long nanosTimeout) throws InterruptedException;
void signal();
用于唤醒一个等待中的线程
void signalAll();
唤醒所有等待中的线程
package com.atmb.me;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReenterLockCondition implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

    @Override
    public void run() {
        try {
            lock.lock();
            condition.await();
            System.out.println("Thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public static void main(String args[]) throws InterruptedException {
        ReenterLockCondition reenterLockCondition = new ReenterLockCondition();
        Thread thread1 = new Thread(reenterLockCondition);
        thread1.start();
        System.out.println("线程正在执行");
        Thread.sleep(2000);
        lock.lock();
        condition.signal();
        lock.unlock();
    }
}
<<<
线程正在执行
Thread is going on

当使用Condition.await()方法时,要求线程持有相关的可重入锁,在Condition.await()调用后这个线程会释放这把锁,在调用Condition.signal()时也要先获取这把锁,唤醒一个线程之后,这个线程会尝试获取锁,因此如果我们将最后一句代码lock.unlock();去掉,那么被唤醒的线程也是无法执行的。Condition在ArrayBlockingQuene中的put和get方法中使用了。

信号量(Semaphore)

信号量是对锁的扩展,之前内部锁与可重入锁都只允许一个线程访问一个资源,而信号量可以指定多个线程,同时访问一个资源。

public Semaphore(int permits)//指定多少个线程可以访问
public Semaphore(int permits, boolean fair)//是否公平
public void acquire() throws InterruptedException
尝试获得一个准入的许可,无法获得的话 就会等待,直到有线程释放了许可,或者当前线程被中断。
public void acquireUninterruptibly()
不响应中断
public boolean tryAcquire()
尝试获取一个许可,成功返回true,失败返回False
public boolean tryAcquire(long timeout, TimeUnit unit)
在到一定时间内尝试获取
public void release()
释放许可
package com.atmb.me;

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

public class SemapDemo implements Runnable {
    final Semaphore semp = new Semaphore(5);

    @Override
    public void run() {
        try {
            semp.acquire();
//            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId() + ":done!");
            semp.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String args[]) {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        final SemapDemo demo = new SemapDemo();
        for (int i = 0; i < 20; i++) {
            executorService.submit(demo);
        }
    }
}
<<<
13:done!
15:done!
14:done!
11:done!
12:done!
18:done!
17:done!
20:done!
16:done!
19:done!
21:done!
22:done!
23:done!
24:done!
25:done!
26:done!
27:done!
28:done!
29:done!
end
30:done!

读写锁(readWriteLock)

读写分离可以有效的帮助减少锁竞争,提升系统的性能。
其中

读与读不互斥,读读之间也不阻塞
读与写之间互斥,读会阻塞写,写也会阻塞读
写与写互斥。

如果一个系统中,读的操作次数远大于写的操作次数,则读写锁就可以发挥出最大的功效,提升性能。

package com.atmb.me;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Created by 13 on 2017/5/5.
 */
public class ReadWriteLockDemo {
    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = reentrantReadWriteLock.readLock();
    private static Lock writeLock = reentrantReadWriteLock.writeLock();
    private int value;

    public Object handleRead(Lock lock) throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);//模拟读操作
            System.out.println("读操作:" + value);
            return value;
        } finally {
            lock.unlock();
        }
    }

    public void handleWrite(Lock lock, int index) throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);//模拟写操作
            System.out.println("写操作:" + value);
            value = index;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String args[]) {
        final ReadWriteLockDemo demo = new ReadWriteLockDemo();

        Runnable readRunnable = new Runnable() {
            @Override
            public void run() {
                //分别使用两种锁来运行,性能差别很直观的就体现出来,使用读写锁后读操作可以并行,节省了大量时间
                try {
                    demo.handleRead(readLock);
                    //demo.handleRead(lock);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };

        Runnable writeRunnable = new Runnable() {
            @Override
            public void run() {
                //分别使用两种锁来运行,性能差别很直观的就体现出来
                try {
                    demo.handleWrite(writeLock, new Random().nextInt(100));
                    //demo.handleWrite(lock, new Random().nextInt(100));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };
        for (int i = 0; i < 18; i++) {
            new Thread(readRunnable).start();
        }
        for (int i = 18; i < 20; i++) {
            new Thread(writeRunnable).start();
        }
    }
}

倒计数器(CountDownLatch)

CountDownLatch这个线程常用于控制线程等待,可以让某一个线程等待直到计数结束,再开始执行。

package com.atmb.me;

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

/**
 * Created by 13 on 2017/5/5.
 */
public class CountDownLatchDemo implements Runnable {
    static final CountDownLatch end = new CountDownLatch(10);
    static final CountDownLatchDemo demo = new CountDownLatchDemo();

    @Override
    public void run() {

        try {
            Thread.sleep(new Random().nextInt(3) * 1000);
            System.out.println(Thread.currentThread().getName()+"check complete");
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String args[]) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.submit(demo);
        }
        end.await();
        System.out.println("first");
        executorService.shutdown();
    }
}
<<<
pool-1-thread-2check complete
pool-1-thread-5check complete
pool-1-thread-3check complete
pool-1-thread-4check complete
pool-1-thread-6check complete
pool-1-thread-10check complete
pool-1-thread-8check complete
pool-1-thread-7check complete
pool-1-thread-1check complete
pool-1-thread-9check complete
first

循环栅栏(Cyclicbarrier)

CyclicbarrierCountDownLatch非常的相似,但是比后者更强大。
首先Cyclicbarrier可以循环使用。

package com.atmb.me;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Created by 13 on 2017/5/4.
 */
public class CyclicBarrierDemo {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclicBarrier;

        public Soldier(CyclicBarrier cyclicBarrier, String soldier) {
            this.soldier = soldier;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                cyclicBarrier.await();
                doWork();
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }

        }

        void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier + ":任务完成");
        }
    }

    public static class BarrierRun implements Runnable {

        boolean flag;
        int N;

        public BarrierRun(boolean flag, int N) {
            this.flag = flag;
            this.N = N;
        }

        @Override
        public void run() {
            if (flag) {
                System.out.println("司令:[士兵" + N + "个,任务完成!");
            } else {
                System.out.println("司令:[士兵" + N + "个,集合完毕!");
                flag = true;
            }
        }
    }


    public static void main(String args[]) {
        final int N = 10;
        Thread[] allSoldier = new Thread[N];
        boolean flag = false;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(N, new BarrierRun(flag, N));
        System.out.println("集合队伍!");
        for (int i = 0; i < N; i++) {
            System.out.println("士兵" + i + "报道!");
            allSoldier[i] = new Thread(new Soldier(cyclicBarrier, "士兵" + i));
            allSoldier[i].start();
        }
    }
}

<<<
集合队伍!
士兵0报道!
士兵1报道!
士兵2报道!
士兵3报道!
士兵4报道!
士兵5报道!
士兵6报道!
士兵7报道!
士兵8报道!
士兵9报道!
司令:[士兵10,集合完毕!
士兵4:任务完成
士兵7:任务完成
士兵6:任务完成
士兵3:任务完成
士兵8:任务完成
士兵5:任务完成
士兵1:任务完成
士兵2:任务完成
士兵0:任务完成
士兵9:任务完成
司令:[士兵10,任务完成!

cyclicBarrier.await();可能会抛出两个异常,一个是InterruptedException,用于响应中断,另一个是BrokenBarrierExceptiion这个异常意味着当前的cyclicBarrier已经皮损,没有办法等待所有的线程到齐了。

线程阻塞工具LockSupport

可以提到resume,suspend

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super.setName(name);
        }

        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
            }
        }
    }


    public static void main(String args[]) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

LockSupport类使用类似信号量的机制,为每一个线程准备了一个许可,如果许可可以使用那么park方法就立即返回,并且将这个许可设置为不可用的状态,其他线程调用park方法就会阻塞,最后unpark方法使得许可变为可用。即使unpark方法发生在park方法之前,也可以是下一次的park方法立即返回。

处于被park方法挂起的线程不会像suspend方法一样处于Runanle的˙状态,而是处于waiting状态。

LockSupport.park还能支持中断影响吗,但是不会抛出InterruptException异常,但是可以使用Thread.interrupted方法获取中断标记。

Gvava和RateLimiter限流

一般的限流算法有两种,漏桶算法和令牌桶算法

漏桶算法:利用一个缓存区,当有请求进入系统是,无论请求的速度如何,都先缓存在缓存区中,然后以固定的流速流出缓存区进行处理。

漏桶算法的特点是无论外部请求速度如何,该算法总是以固定的流速处理数据,漏桶的提及和流出的速率是两个重要的参数。

令牌桶算法是一种反向的漏桶算法,在令牌桶算法,桶中存放的不再是请求而是令牌,处理程序只有拿到令牌后才能对请求进行处理。如果没有令牌那么处理程序要么丢弃,要么等待可用的令牌,该算法在每个单位时间内产生一定量的令牌存入桶中,当令牌没有被消耗掉是,只能累计有限单位时间内的令牌数量。

package com.atmb.me;

import com.google.common.util.concurrent.RateLimiter;

public class RateLimiterDemo {
    static RateLimiter limiter = RateLimiter.create(2);

    public static class Task implements Runnable{

        public void run() {
            System.out.println(System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            limiter.acquire();
            new Thread(new Task()).start();
        }
    }
}
<<<
1598173212011
1598173212511
1598173213010
1598173213513
1598173214012
1598173214511

线程池

在生产环境中,创建线程,运行任务,销毁线程,对cpu内存资源消耗很大。

线程是一种轻量级的工具,但其创建和关闭依然需要花费时间,如果为每一个任务都创建一个线程,则很有可能出现创建和销毁线程所占用的时间大于该线程真实工作消耗的时间。

什么是线程池?

为了避免频繁的创建的销毁线程,我们可以让创建的线程复用,类似数据库连接池,当系统须有使用数据库时,并不是创建一个新的连接,而是从连接池中获得一个可用的连接即可,反之当需要关闭连接时,并不真的把连接关闭,而是把连接还给连接池,这种方式可以节约不少创建和销毁对象的时间。

在这里插入图片描述
ThreadPoolExecutor表示一个线程池,Executors类扮演者线程工厂的角色,通过Executors可以的得到各种线程池。

public static ExecutorService newFixedThreadPool(int nThreads)

public static ExecutorService newSingleThreadExecutor() 

public static ExecutorService newCachedThreadPool()

public static ScheduledExecutorService newSingleThreadScheduledExecutor()

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
newFixedThreadPool
返回一个固定数量的线程池,该线程池中的线程数量始终不变,当有一个新的任务
提交时,线程池中若有空闲的线程,则立即 执行,若没有则新的任务会被暂存在
一个任务队列中,待有线程空闲时处理任务队列中的任务。

newSingleThreadExecutor
该方法只有一个线程的数量的线程池,若多余的任务被提交到线程池,任务会被保存
到一个任务队列中,然后以FIFO的顺序去执行队列中的任务

newCachedThreadPool
该方法返回一个可根据实际情况调整线程数量的线程池,线程池中的数量不确定,
但若有空闲的线程可以复用,则会优先使用可复用的线程,若所有的线程都在工作,
又有新的任务提交,则会创建新的线程处理人物,所有线程在当前任务执行完毕后,
将返回线程池进行复用。
固定大小的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static class MyTask implements Runnable {

        @Override
        public void run() {
            System.out.println(System.currentTimeMillis() + "Thread ID:" + Thread.currentThread().getId());

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String args[]) {
        MyTask myTask = new MyTask();
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            executorService.submit(myTask);
        }
    }
}
计划任务

newScheduledThreadPool(),其返回一个ScheduleExecutorService对象,可以根据时间需要对线程进行调度

public ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit)
在给定的时间进行一次调度
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
对任务进行周期性的调度,以上一个任务开始执行的时间起点,经过period时间调度下一次任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
对任务进行周期性的调度,上一次任务执行的结束时间,在经过Delay进行调度。
package com.atmb.me;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorServiceDemo {
    public static void main(String args[]) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println(System.currentTimeMillis() / 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 0, 2, TimeUnit.SECONDS);
    }
}
<<<
1598183834
1598183836
1598183838
1598183840
1598183842
核心线程池的内部实现
    public ThreadPoolExecutor(int corePoolSize,//线程池中的线程数量
                              int maximumPoolSize,// 制定了线程池中的最大线程数量
                              long keepAliveTime,//当前线程池数量超过corePoolsize时,多余的空闲线程的存活时间,
                              TimeUnit unit,//keepAliveTime的单位
                              BlockingQueue<Runnable> workQueue,// 任务队列,被提交但尚未执行的任务
                              ThreadFactory threadFactory,//线程工厂,用于创建线程,一般用默认的即可
                              RejectedExecutionHandler handler)//拒绝策略
拒绝策略

在这里插入图片描述

  1. 会动抛出异常,阻止系统正常工作
  2. 只要线程池没有关闭,该策略世界在调用者线程中,运行当前被丢弃的任务。但是任务提交线程的性能极有可能会急剧下降。
  3. 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  4. 丢弃无法处理的任务,不予 任何处理。
package com.atmb.me;

import java.util.concurrent.*;

public class RejectThreadPoolDemo {
    public static class MyTask implements Runnable {

        @Override
        public void run() {
            System.out.println(System.currentTimeMillis() + ":Thread ID:" + Thread.currentThread().getId());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String args[]) throws InterruptedException {
        MyTask myTask = new MyTask();

        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(10), Executors.defaultThreadFactory()
                , new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println(r.toString() + " is discard");
            }
        });

        for (int i = 0; i < 100; i++) {
            executorService.submit(myTask);
            Thread.sleep(10);
        }
        executorService.shutdown();
    }
}
<<<
1598185083090:Thread ID:14
1598185083105:Thread ID:15
java.util.concurrent.FutureTask@3f99bd52 is discard
自定义线程创建:ThreadFactory

ThreadFactory是一个接口,只有一个用来创建线程的方法

Thread newThread(Runnable r);
扩展线程池

线程池提供了beforeExecute()afterExecute(),terminated()三个接口来对线程池进行控制。

package com.atmb.me;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExtThreadPool {

    public static class MyTask implements Runnable {
        public String name;

        public MyTask(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println("正在执行:Thread ID:" + Thread.currentThread().getId() + ",Task Name:" + name);

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String args[]) throws InterruptedException {
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>()) {
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println("准备执行:" + ((MyTask) r).name);
            }

            protected void afterExecute(Thread t, Runnable r) {
                System.out.println("执行完成:" + ((MyTask) r).name);
            }

            protected void terminated() {
                System.out.println("线程池退出!");
            }
        };

        for (int i = 0; i < 5; i++) {
            MyTask task = new MyTask("TASK-GEYM-" + i);
            executorService.execute(task);
            Thread.sleep(10);
        }
        executorService.shutdown();
    }
}
<<<
准备执行:TASK-GEYM-0
正在执行:Thread ID:11,Task Name:TASK-GEYM-0
准备执行:TASK-GEYM-1
正在执行:Thread ID:12,Task Name:TASK-GEYM-1
准备执行:TASK-GEYM-2
正在执行:Thread ID:13,Task Name:TASK-GEYM-2
准备执行:TASK-GEYM-3
正在执行:Thread ID:14,Task Name:TASK-GEYM-3
准备执行:TASK-GEYM-4
正在执行:Thread ID:15,Task Name:TASK-GEYM-4
线程池退出!

shutdown()方法不会立即暴力地终止所有的任务,它会等待所有任务执行完成后,在关闭线程池,但不会等待所有线程执行后,再返回。因此,可以简单的理解成shutdown()执行之后,线程池就不能再接受其他新的任务了。

分而治之Fork/join

在linux平台中,方法fork()用来创建子进程,使得系统进程可以多一个执行分支。

线程A完成了自己的任务,会帮助线程B,从线程B的任务队列中拿一个任务过来处理,线程执行自己的任务时总是从任务队列的头部取任务,帮助其他线程时,总是从尾部取任务。

RecursiveAction没有返回值
RecursuveTask有返回值

package com.atmb.me;

import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class CountTask extends RecursiveTask<Long> {
    private static final int THRESHOLD = 10000;

    private long start;
    private long end;

    public CountTask(long start, long end) {
        this.start = start;
        this.end = end;
    }


    @Override
    protected Long compute() {
        long sum = 0;
        boolean canCompute = (end - start) < THRESHOLD;
        if (canCompute) {
            for (long i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            long step = (start + end) / 100;

            ArrayList<CountTask> subTasks = new ArrayList<CountTask>();
            long pos = start;

            for (int i = 0; i < 100; i++) {
                long lastOne = pos + step;
                if (lastOne > end) {
                    lastOne = end;
                }
                CountTask subTask = new CountTask(pos, lastOne);
                pos += step + 1;
                subTasks.add(subTask);
                subTask.fork();
            }

            for (CountTask t : subTasks) {
                sum +=  t.join();//等待结束获取结果
            }
        }


        return sum;
    }


    public static void main(String args[]) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        CountTask task = new CountTask(0, 200000L);
        ForkJoinTask<Long> result = forkJoinPool.submit(task);

        long res = 0;
        try {
            res = result.get();
            System.out.println("sum=" + res);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Guava对线程池的扩展

Daemon线程池

MoreExecutors中还提供了将普通线程池转为Daemon线程池,即便有线程池的存在。那么就可以使用MoreExecutors.getExitingExecytorService()方法。

线程池 submit execute 区别

1、接收的参数不一样
都可以是Runnable
submit 也可以是Callable
2、submit有返回值,而execute没有

返回值是Future
3、submit方便Exception处理

JDK的并发容器

  • ConcurrentHashMap :一个高效的的并发HashMap,线程安全
  • CopyOnWriteArrayList:在读多写少的场合,其性能远远高于Vector.
  • ConcurrentLinkedQuene:高效的并发队列,使用链表实现,线程安全。
  • BlockingQuene:一个接口,jdk通过链表,数组等方式实现了这个接口,表示阻塞队列,非常适合作为数据共享的通道。
  • ConcurrentSkipListMap:跳表的实现,一个Map,内部使用跳表的数据结构进行快速查找。
线程安全的HashMap
怎样得到一个线程安全的HashMap?
  1. 使用Collections.synchronizeMap()方法包装HashMap,其内部发部分方式使用Syschronzied 对方法加锁,因此效率不是很高
  2. 使用ConcurrentHashMap,位于java.util.concurrent包中,适合多线程的场合
线程安全的List

ArrayList不是线程安全的,Vector是线程安全的。LinkedList也不是线程安全的。可以使用Collections.synchronized()包装list来实现线程安全。

ConcurrentLinkedQuene

ConcurrentLinkedQuene几乎是高并发环境中性能最好的队列,内部实现

   private static class Node<E> {
        volatile E item;//目标元素
        volatile Node<E> next;//下一个节点
//对node进行操作时使用了cas
boolean casItem(E cmp, E val) {//设置当前的值(期望值,目标值)
            return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }

        void lazySetNext(Node<E> val) {// 
            UNSAFE.putOrderedObject(this, nextOffset, val);
        }

        boolean casNext(Node<E> cmp, Node<E> val) {//设置next的字段
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

ConcurrentLinkedQuene允许在运行时链表处于不同的状态,以tail为例,一般链表中tail标识尾节点,但ConcurrentLinkedQuene不同,tail的更新也不是实时的,可能会有延迟的情况。

不变模式下的CopyOnWriteArrayList

在写的时候进行一次自我复制,就是并不修改全有的内容,而是对数据进行一次复制,将修改的内容写入副本中,写完之后用修改的副本替换原来的数据。

写的时候使用锁,这个锁仅控制写-写的情况,会对数据进行完成复制,修改完之后替换原有的数组,而且数组使用volatile修饰。使读线程瞬间察觉到修改。

数据共享通道BlockingQuene

BlockingQuene是一个接口,
ArrayBlockingQuene是基于数组实现的,适合做有界队列。
LinkedBlockingQuene是基于链表实现的,适合做无界队列。
服务线程处理完队列中的消息后会被阻塞,直到下一条消息的到来,BlockingQuene会让服务线程等待,然后再新消息进入队列后自动将其唤醒。

ArrayBlockingQuene的内部元素放置在一个对象数组中,向队列中压入元素可以使用offer()或者put()

offer() 如果当前队列已经满了,直接返回false,如果没满正常入队
put() 如果队列已经满了,会一直等待,直到队列中有空闲的位置

弹出元素可以使用poll()或者take()

poll()如果队列为空直接返回null
take()如果队列为空会一直等待

内部有两个锁(ReentrantLock)notFull和NotEmpty,队列满了,压入线程notFull阻塞,然后消费线程消费一个消息之后进行唤醒,反之同理。

跳表

跳表是一种用来快速查找的数据结构。其内部维护多个链表用来加速元素的查找,其查找时间复杂度为(logn)与二分查找类似。
跳表内的所有元素都是有序的,因此在对跳表遍历时,会得到一个有序的结果。其实现类为ConcurrentSkipListMap.
在这里插入图片描述

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值