文章目录
- 基础篇
- 基础概念
- 启动线程
- 安全的停止线程
- 线程常用方法和线程状态
- 线程的优先级
- 守护线程
- 协作通信
- synchronized
- volatile
- ThreadLocal
- 等待和通知 - wait/notify/notifyAll
- join
- yield() 、sleep()、wait()、notify()/notifyAll() 等方法 和锁之间的关系与影响
- 工具类
- Fork/Join
- CountDownLatch
- CyclicBarrier
- Semaphore
- Exchange
- Callable、Future和FutureTask
- 线程安全
- 什么是线程安全
- 如何保证线程安全
- 死锁
- 其他的线程安全问题
- 显示锁和CAS
- 显示锁
- CAS(原子操作)
- 线程池
- 为什么要使用线程池
- 线程池的基本使用
- jdk实现的内置线程池
- 并发容器
- ConcurrentHashMap
- ConcurrentSkipListMap 和 ConcurrentSkipListSet
- ConcurrentLinkedQueue
- CopyOnWriteArrayList 和 CopyOnWriteArraySet
- 阻塞队列
- 什么是阻塞队列
- 常用方法
- 常用阻塞队列
- 阻塞队列的实现原理
- 阻塞队列使用示例
基础篇
基础概念
进程:资源分配的最小单位
线程: cpu调度的最小单位,共享进程中的资源,必须依附进程
并行: 同一时刻运行进行的任务
并发:在单位时间段内处理的任务数(例: 每秒的并发数)
启动线程
启动线程的三种方式 1.extends Thread 2.implements Runnable 3.implements Callable
Callable 允许有返回值
//方式1.extends Thread
public static class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println(Thread.currentThread().getName() + "extends thread");
}
}
//方式2.implements Runnable
public static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "implements Runnable");
}
}
//方式3.implements Callable - 有返回值
public static class MyCalladle implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + "implements Callable");
return "CallResult";
}
}
public static void main(String[] args)
throws InterruptedException, ExecutionException {
MyThread myThread = new MyThread();
myThread.start(); //启动 extends Thread
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();//启动 implements Runnable
MyCalladle myCalladle = new MyCalladle();
FutureTask<String> futureTask = new FutureTask<>(myCalladle);
new Thread(futureTask).start(); //启动 implements Callable
/*futureTask.get() 获取线程返回结果.
该方法为阻塞方法,只有当对应的线程运行结束后才会继续运行*/
System.out.println("GET myCalladle result = " + futureTask.get());
}
安全的停止线程
1.线程结束后,自动停止
2.interrupt() 不会去真正中断线程,只是把一个中断标志符改为true. 使用isInterrupted() 或 static方法interrupted() 来检查中断标志位
3.stop() 过时的,已经不赞成使用,因为该方法会使线程进入休眠状态,但该线程占用的资源不会释放,容易造成死锁和资源浪费的情况
/**
* 使用interrupt()停止线程 示例
*
* 在某些情况下我们需要无限循环的方式处理业务,这种业务处理我们往往把它放在独立的线程中运行.
* 但因为某些原因可能需要停止该线程(业务处理),这时就需要用 interrupt 方法实现,使方法退出无限
* 循环,让线程达到运行结束而自动停止的状态
*/
private static class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
while (!isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "run...");
}
System.out.println("stop run..." + Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread("tianjieyi");
myThread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.interrupt();
}
线程常用方法和线程状态
yield() 是让当前线程让出cpu时间,os依然可以选中当前线程
join() 当一个线程(A)调用另一个线程(B)的join()后,线程A会等待线程B运行结束后获得执行权.阻塞方法
join()方法还有, join(long millis)和join(longmillis,int nanos)两个具备超时特性的,当时间一到不管线程B是否执行结束,线程A都会获得执行权
线程的优先级
Thread的 setPriority() 方法可以设置线程的优先级,,优先级的范围从1~10,默认优先级是5.
但在不同的JVM和操作系统上,线程规则存在差异,线程优先级不一定正确.有些操作系统甚至会忽略线程优先级的设定.所以setPriority()并不可靠.
守护线程
Thread的 setDaemon(true) 方法可以设置线程为守护线程.
守护线程: 是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作,比如垃圾回收线程就是一个很称职的守护者.
当所有非守护线程停止后,Java虚拟机将会退出,这时守护线程也将会退出.但因Java虚拟机退出而造成的守护线程停止情况下,守护线程中的 finally不能得到执行.因此在构建Daemon线程时不能依赖finally来确保执行关闭和资源释放的操作
//示例: 测试因Java虚拟机退出而造成的守护线程停止情况下,守护线程中的 finally不能得到执行
private static class myThread extends Thread {
@Override
public void run() {
try {
while (!isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "run...");
}
} finally {
System.out.println("finally...");
}
}
}
public static void main(String[] args) throws InterruptedException {
myThread myThread = new myThread();
myThread.setDaemon(true);
myThread.start();
SleepTools.ms(1);
// myThread.interrupt(); // 主动停止守护线程 finally 会得到执行
System.out.println("main...");
}
协作通信
synchronized
synchronized 是java提供的内置锁
在并发运行时,使用synchronized修饰的变量,方法,代码块.可以保证被修饰部分在同一时间只能有一个线程可以访问
//synchronized的基本使用
public class SynTest {
private int age = 100000;
public int getAge() {
return age;
}
public synchronized void add() { // 实例方法, 锁的是对象
age++;
}
public synchronized void remove() {
age--;
}
public static class myThread extends Thread {
private SynTest synTest;
public myThread(String name, SynTest synTest) {
super(name);
this.synTest = synTest;
}
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
synTest.add();
}
}
}
public static void main(String[] args) {
SynTest synTest = new SynTest();
myThread myThread = new myThread("endThread", synTest);
myThread.start();
for (int i = 0; i < 100000; i++) {
synTest.remove();
}
System.out.println(Thread.currentThread().getName() + " = " + synTest.getAge());
}
}
/**
* 锁与锁之间的关系
*
* <p>同一个类中同时存在类锁和实例锁,并发运行时两者会不影响可以同时访问运行
*
* <p>同一个对象的不同实例,并发运行时锁是互不影响的可以同时访问运行
* 例: synClassAndInstance 和 synClassAndInstance2是同一个对象的不同实例
* 当两个线程并发访问SynClassAndInstance对象的实例锁1instance1方法时,两个线程是可以同时
* 访问instance1方法的
*
* SynClassAndInstance synClassAndInstance = new SynClassAndInstance();
* Instance1 instance11 = new Instance1(synClassAndInstance);
* new Thread(instance11).start();
*
* SynClassAndInstance synClassAndInstance2 = new SynClassAndInstance();
* Instance1 instance12 = new Instance1(synClassAndInstance2);
* new Thread(instance12).start();
*
*
* <P>实例(对象)锁,锁的是一个实例.当一个实例(对象)锁定时,该对象的任何方法都不会被调用.
* 例:instance1 和 instance2 是同一个对象的两个不同的方法,当两个不同的线程(A B)并发去执行
* instance1和instance2 方法时,只有等线程A执行完instance1方法后释放了锁线程B才会去执行
* instance2方法
*
* SynClassAndInstance synClassAndInstance = new SynClassAndInstance();
* Instance1 instance1 = new Instance1(synClassAndInstance);
* new Thread(instance1).start();
*
* Instance2 instance2 = new Instance2(synClassAndInstance);
* new Thread(instance2).start();
*/
public class SynClassAndInstance {
private static synchronized void synClass() { // 类锁
SleepTools.second(1);
System.out.println("synClass go…………");
SleepTools.second(1);
System.out.println("synClass end…………");
}
private synchronized void instance1() { // 实例(对象)锁1
SleepTools.second(1);
System.out.println("instance1 go…………");
SleepTools.second(1);
System.out.println("instance1 end…………");
}
private synchronized void instance2() { // 实例(对象)锁2
SleepTools.second(1);
System.out.println("instance2 go…………");
SleepTools.second(1);
System.out.println("instance2 end…………");
}
private static class SynClass extends Thread {
@Override
public void run() {
System.out.println("SynClass start…………");
synClass();
}
}
private static class Instance1 implements Runnable {
private SynClassAndInstance synClassAndInstance;
public Instance1(SynClassAndInstance synClassAndInstance) {
this.synClassAndInstance = synClassAndInstance;
}
@Override
public void run() {
System.out.println("Instance1 start…………");
synClassAndInstance.instance1();
}
}
private static class Instance2 implements Runnable {
private SynClassAndInstance synClassAndInstance;
public Instance2(SynClassAndInstance synClassAndInstance) {
this.synClassAndInstance = synClassAndInstance;
}
@Override
public void run() {
System.out.println("Instance2 start…………");
synClassAndInstance.instance2();
}
}
public static void main(String[] args) {
SynClass synClass = new SynClass();
synClass.start();
SynClassAndInstance synClassAndInstance = new SynClassAndInstance();
Instance1 instance1 = new Instance1(synClassAndInstance);
new Thread(instance1).start();
SynClassAndInstance synClassAndInstance2 = new SynClassAndInstance();
Instance1 instance12 = new Instance1(synClassAndInstance2);
new Thread(instance12).start();
Instance2 instance2 = new Instance2(synClassAndInstance);
new Thread(instance2).start();
Instance2 instance22 = new Instance2(synClassAndInstance2);
new Thread(instance22).start();
}
}
volatile
多线程去访问 volatile 修饰的变量时,会去访问共享内存中 变量的值,但不会在修改后写回内存
volatile关键字,最轻量的同步机制
ThreadLocal
使用 ThreadLocal 定义的变量 在多线程中就像线程中本身的变量一样,在各个线程中的使用,是互不影响的
// ThreadLocal的使用示例
public class MyThreadLocal {
/* 声明一个所有线程都共享的 ThreadLocal类型的static变量,并初始化为1 */
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 1;
}
};
private static class MyThread extends Thread {
private MyThreadLocal myThreadLocal;
public MyThread(MyThreadLocal myThreadLocal, String name) {
super(name);
this.myThreadLocal = myThreadLocal;
}
@Override
public void run() {
int a = myThreadLocal.threadLocal.get();
a = a + Integer.valueOf(Thread.currentThread().getName());
myThreadLocal.threadLocal.set(a);
System.out.println("Thread - " + Thread.currentThread().getName() + " threadLocal的值:"+ myThreadLocal.threadLocal.get());
}
}
public static void main(String[] args) {
MyThreadLocal myThreadLocal = new MyThreadLocal();
new MyThread(myThreadLocal, "1").start();
new MyThread(myThreadLocal, "2").start();
new MyThread(myThreadLocal, "3").start();
new MyThread(myThreadLocal, "4").start();
new MyThread(myThreadLocal, "5").start();
}
}
等待和通知 - wait/notify/notifyAll
当一个线程调用wait()方法后,将会进入等待状态,阻塞运行
当调用notify/notifyAll方法后,等待状态下的线程将会得到通知继续运行
notify() 唤醒一个线程,notifyAll()唤醒所有线程.因CPU的调度机制notify()不能指定线程唤醒,所以应尽量使用notifyAll()方法,除非可以确定当前等待的线程只有一个
// wait()/notifyAll() 方法的使用示例
// 快递实体类
public class Express {
public final static String CITY = "ShangHai";
private int km;/* 快递运输里程数 */
private String site;/* 快递到达地点 */
public Express() {
}
public Express(int km, String site) {
this.km = km;
this.site = site;
}
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理 */
public synchronized void changeKm() {
this.km = 101;
notifyAll();
}
/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理 */
public synchronized void changeSite() {
this.site = "BeiJing";
notifyAll();
}
public synchronized void waitKm() {
while (this.km <= 100) {// 公里数小于100不做处理
try {
wait();
System.out.println("Check Km thread[" + Thread.currentThread().getId() + "] is be notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("the Km is " + this.km + ",I will change db");
}
public synchronized void waitSite() {
while (this.site.equals(CITY)) {// 快递到达目的地
try {
wait();
System.out.println("Check Site thread[" + Thread.currentThread().getId() + "] is be notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("the site is " + this.site + ",I will call user");
}
}
//测试类
public class TestWN {
private static Express express = new Express(0, Express.CITY);
/* 检查里程数变化的线程,不满足条件,线程一直等待 */
private static class CheckKm extends Thread {
@Override
public void run() {
express.waitKm();
}
}
/* 检查地点变化的线程,不满足条件,线程一直等待 */
private static class CheckSite extends Thread {
@Override
public void run() {
express.waitSite();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new CheckSite().start();
}
for (int i = 0; i < 3; i++) {
new CheckKm().start();
}
SleepTools.ms(1000);
express.changeKm();// 快递地点变化
}
}
join
join() 当一个线程(A)调用另一个线程(B)的join()后,线程A会等待线程B运行结束后获得执行权,阻塞方法
join()方法还有, join(long millis)和join(longmillis,int nanos)两个具备超时特性的,当时间一到不管线程B是否执行结束,线程A都会获得执行权
//join()方法的使用示例
public class MyJoin {
static class JumpQueue implements Runnable {
private Thread thread;
public JumpQueue(Thread thread) {
this.thread = thread;
}
public void run() {
try {
// 调用传入线程的join方法,必须等这个方法返回后,当前线程才能继续执行
thread.join();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
public static void main(String[] args) throws Exception {
Thread previous = Thread.currentThread();// 现在previous是主线程
for (int i = 0; i < 10; i++) {
// 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
Thread thread = new Thread(new JumpQueue(previous), String.valueOf(i));
System.out.println("Thread:" + thread.getName() + " wait the Thread: " + previous.getName());
thread.start();
previous = thread;
}
SleepTools.second(2);// 让主线程休眠2秒
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
yield() 、sleep()、wait()、notify()/notifyAll() 等方法 和锁之间的关系与影响
yield() 调用前如果持有锁, 调用后不会释放锁
sleep() 调用前如果持有锁, 调用后不会释放锁
wait() 调用前必须持有锁,调用了wait()方法以后,锁会被释放
notify() 调用前必须持有锁, 包含了notify()的方法结束以后,锁才会被释放
//yield() 、sleep()、wait()、notify()/notifyAll()等方法和锁之间的关系与影响 示例
public class Myyswn {
public synchronized void test() {
System.out.println(Thread.currentThread().getName() + " -----------");
// Thread.yield();
// SleepTools.second(1);
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// }
try {
wait();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + " ............");
}
public synchronized void test1() {
notifyAll();
SleepTools.second(2);
System.out.println(Thread.currentThread().getName() + " notifyAll....");
}
private static class MyRunnable implements Runnable {
private Myyswn myYield;
public MyRunnable(Myyswn myYield) {
super();
this.myYield = myYield;
}
@Override
public void run() {
myYield.test();
}
}
public static void main(String[] args) {
Myyswn myYield = new Myyswn();
new Thread(new MyRunnable(myYield)).start();
new Thread(new MyRunnable(myYield)).start();
SleepTools.ms(100);
myYield.test1();
}
}
工具类
Fork/Join
Fork/Join框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务,再将一个个的小任务的运算结果进行join汇总
RecursiveTask 和 RecursiveAction 是继承 ForkJoinTask 的不同实现
其中 RecursiveTask 有返回值,RecursiveAction 没有返回值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z7bulpBX-1577769464974)(C:\Users\ADMINI~1\AppData\Local\Temp\1545789384597.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nHa1qBqC-1577769464975)(C:\Users\ADMINI~1\AppData\Local\Temp\1545789410652.png)]
//RecursiveTask使用示例,获取指定目录下的文件个数
public class SumDirsFiles extends RecursiveTask<Integer> {
private File file;
public SumDirsFiles(File file) {
this.file = file;
}
@Override
protected Integer compute() {
File[] listFiles = file.listFiles();
if (listFiles == null) {
return 0;
}
int count = 0; //文件个数
int dirCount = 0; //目录个数
List<SumDirsFiles> list = new ArrayList<>();
for (File file : listFiles) {
if (file.isDirectory()) {
list.add(new SumDirsFiles(file));
dirCount++;
} else {
count++;
}
}
System.out.println("线程: " + Thread.currentThread().getName() + ",目录 :" + file.getAbsolutePath() + "下包含目录个数:"
+ dirCount + "文件个数:" + count);
if (!list.isEmpty()) {
//将本次目录下的目录全部invokeAll,再次执行SumDirsFiles的运算
Collection<SumDirsFiles> invokeAll = invokeAll(list);
for (SumDirsFiles sumDirsFiles : invokeAll) {
count = count + sumDirsFiles.join();
}
}
return count;
}
public static void main(String[] args) {
SumDirsFiles sumDirsFiles = new SumDirsFiles(new File("G:/old/olddesk/"));
ForkJoinPool forkJoinPool = new ForkJoinPool();
long currentTimeMillis = System.currentTimeMillis();
forkJoinPool.invoke(sumDirsFiles); // invoke同步执行,会阻塞
SleepTools.ms(100);
long currentTimeMillis2 = System.currentTimeMillis();
System.out.println("I am do work" + currentTimeMillis2 + "相差 ……" + (currentTimeMillis2 - currentTimeMillis));
System.out.println("总文件数 = " + sumDirsFiles.join());
long currentTimeMillis3 = System.currentTimeMillis();
System.out.println("end " + currentTimeMillis3 + "相差 …… " + (currentTimeMillis3 - currentTimeMillis));
}
}
// 使用RecursiveAction的示例,查询指定文件夹下所有的txt文件
public class FindDirsFiles extends RecursiveAction {
private File path;
public FindDirsFiles(File path) {
this.path = path;
}
@Override
protected void compute() {
List<FindDirsFiles> subTasks = new ArrayList<>();
File[] files = path.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
subTasks.add(new FindDirsFiles(file));
} else {
if (file.getAbsolutePath().endsWith("txt")) {
System.out.println(Thread.currentThread().getName() + "," + file.getAbsolutePath());
}
}
}
if (!subTasks.isEmpty()) {
invokeAll(subTasks);
}
}
}
public static void main(String[] args) {
FindDirsFiles findDirsFiles = new FindDirsFiles(new File("G:/old/olddesk/"));
ForkJoinPool forkJoinPool = new ForkJoinPool();
long currentTimeMillis = System.currentTimeMillis();
System.out.println("Task is Running......" + currentTimeMillis);
forkJoinPool.execute(findDirsFiles); // execute 异步执行,不会阻塞
SleepTools.ms(100);
long currentTimeMillis2 = System.currentTimeMillis();
System.out.println("I am do work" + currentTimeMillis2 + "相差 …… " + (currentTimeMillis2 - currentTimeMillis));
findDirsFiles.join(); // join 获取执行结果,会阻塞
long currentTimeMillis3 = System.currentTimeMillis();
System.out.println("end " + currentTimeMillis3 + "相差 …… " + (currentTimeMillis3 - currentTimeMillis));
}
}
CountDownLatch
允许一个或多个线程等待其他线程完成各自的操作后继续自己的线程,类似加强版的join
这里的等待其他线程完成各自的操作,并不需要其他线程都执行结束,只需要countDown()方法执行完毕就行
//CountDownLatch的使用示例,共5个子线程,6个闭锁扣除点,扣除完毕后,主线程和业务线程才能继续执行
public class MyCountDownLatch {
static CountDownLatch countDownLatch = new CountDownLatch(6);
// 线程1,运行一次扣除一个闭锁扣除点
private static class initRunnable implements Runnable {
@Override
public void run() {
SleepTools.ms(5);
System.out.println("Thread " + Thread.currentThread().getName() + " init work...... 扣除了一个");
countDownLatch.countDown(); // 减少(扣除)一个 闭锁扣除点,不会阻塞方法的继续运行
SleepTools.ms(5); // 需要5毫秒处理逻辑
System.out.println("Thread " + Thread.currentThread().getName() + " ready other work......");
}
}
// 线程1,运行一次扣除两个闭锁扣除点
private static class initRunnable2 implements Runnable {
@Override
public void run() {
SleepTools.ms(5);
System.out.println("两个扣除点的Thread " + Thread.currentThread().getName() + " - init work1...... 扣除了一个");
countDownLatch.countDown(); // 减少(扣除)一个 闭锁扣除点
SleepTools.ms(5);
System.out.println("两个扣除点的Thread " + Thread.currentThread().getName() + " - init work2......扣除了一个");
countDownLatch.countDown(); // 减少(扣除)一个 闭锁扣除点
SleepTools.ms(5);
System.out.println("两个扣除点的Thread " + Thread.currentThread().getName() + " ready other work......");
}
}
/**
* 业务线程,需要等待其他线程的运行
*/
private static class businessRunnable implements Runnable {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getName() + " handing business.....");
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new initRunnable2()).start();
for (int i = 0; i < 4; i++) {
new Thread(new initRunnable()).start();
}
new Thread(new businessRunnable()).start(); // 启动业务线程
countDownLatch.await(); // 主线程需要等待其他线程的运行完毕再继续运行
System.out.println("Thread " + Thread.currentThread().getName() + " goto other work......");
}
}
CyclicBarrier
CyclicBarrier(int parties)
让一组线程到达一个屏障点(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行.
CyclicBarrier(int parties, Runnable barrierAction)
可以在一组线程到达屏障点时,优先执行barrierAction线程,当barrierAction线程执行完毕后,屏障才会开门,所有被屏障拦截的线程才会继续执行
//使用CyclicBarrier(int parties, Runnable barrierAction)的示例
public class MyCyclicBarrier {
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(6, new CollectThread());
private static ConcurrentMap<String, Long> chm = new ConcurrentHashMap<>();
private static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread " + Thread.currentThread().getId() + " ....init " + System.currentTimeMillis());
long id = Thread.currentThread().getId();
chm.put("" + id, id);
Random r = new Random();
if (r.nextBoolean()) {
SleepTools.second(2); // 模拟业务,执行消耗时间
System.out.println("Thread " + id + " ....do something ");
}
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("Thread " + id + " ....do other something " + System.currentTimeMillis());
}
}
private static class CollectThread implements Runnable {
@Override
public void run() {
StringBuilder result = new StringBuilder();
for (Map.Entry<String, Long> workResult : chm.entrySet()) {
result.append("[" + workResult.getValue() + "] ");
}
SleepTools.second(2); // 模拟业务,执行消耗时间
System.out.println("Result ... " + result.toString());
}
}
public static void main(String[] args) {
for (int i = 0; i < 6; i++) {
new Thread(new MyRunnable()).start();
}
}
}
Semaphore
用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。常用于做流量控制,特别是公共资源有限的应用场景,比如数据库连接池,有界缓存等。
//使用Semaphore实现的一个数据库连接池
public class DBPoolSemaphore {
private final static int POOL_SIZE = 10;
private static LinkedList<Connection> pool = new LinkedList<>();
private final Semaphore useful, useless;// 两个指示器,分别表示池子还有可用连接和已用连接
static {
for (int i = 0; i < POOL_SIZE; i++) {
pool.addLast(SqlConnectImpl.fetchConnection());
}
}
public DBPoolSemaphore() {
this.useful = new Semaphore(10);
this.useless = new Semaphore(0);
}
/**
* 从池中获取一个连接
*
* @return
* @throws InterruptedException
*/
public Connection takeConnect() throws InterruptedException {
useful.acquire(); // useful - 1
Connection connection;
synchronized (pool) {
connection = pool.removeFirst();
}
useless.release(); // useless + 1
return connection;
}
// 归还连接
public void returnConnect(Connection connection) throws InterruptedException {
if (connection == null) {
return;
}
System.out.println("当前有" + useful.getQueueLength() + "个线程等待数据库连接!!" + "可用连接数:" + useful.availablePermits());
useless.acquire(); // useless - 1
synchronized (pool) {
pool.addLast(connection);
}
useful.release(); // useful + 1
}
}
//数据库连接的平庸实现
public class SqlConnectImpl implements Connection {
/* 拿一个数据库连接 */
public static final Connection fetchConnection() {
return new SqlConnectImpl();
}
........
}
//测试,Semaphore
public class AppTest {
private static DBPoolSemaphore dbPoolSemaphore = new DBPoolSemaphore();
private static class myRunnable implements Runnable {
@Override
public void run() {
try {
long start = System.currentTimeMillis();
Random r = new Random();
Connection takeConnect = dbPoolSemaphore.takeConnect();
System.out.println(Thread.currentThread().getName() + " 获取了数据库连接,共耗时["
+ (System.currentTimeMillis() - start) + "ms]");
SleepTools.ms(100 + r.nextInt(100)); // 模拟数据查询耗时
dbPoolSemaphore.returnConnect(takeConnect);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
new Thread(new myRunnable()).start();
}
}
}
Exchange
用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。
建议同时只能有两个线程进行Exchange数据交换,当多余两个线程时不能确定获得的数据是从你想要的线程中获取的
Exchange还提供了一个超时的exchange(V x, long timeout, TimeUnit unit)方法
//使用Exchange的示例
public class MyExchange {
private static Exchanger<String> ex = new Exchanger<>();
private static class myRunnable1 implements Runnable {
@Override
public void run() {
String s = "A";
try {
String exchange = ex.exchange(s);
System.out.println(Thread.currentThread().getName() + " 交换前 : " + s + " 交换后 :" + exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class myRunnable2 implements Runnable {
@Override
public void run() {
String s = "B";
try {
String exchange = ex.exchange(s);
System.out.println(Thread.currentThread().getName() + " 交换前 : " + s + " 交换后 :" + exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(new myRunnable1()).start();
new Thread(new myRunnable2()).start();
}
}
Callable、Future和FutureTask
Future接口用来表示异步计算的结果.可以对具体的任务进行取消,查询是否完成和获得任务结果
boolean cancel(boolean mayInterruptIfRunning); //试图去取消任务返回true取消成功,false取消失败
boolean isCancelled(); //如果任务在完成前取消,返回true
boolean isDone(); //任务是否结束,结束返回true。结束的原因有多种,可能是正常结束,异常,取消
V get() ; //等待任务结束获取结果
V get(long timeout, TimeUnit unit) ; //get() 的超时方法FutureTask为Future 和 Runnable 的实现类.提供了FutureTask(Callable callable) 和FutureTask(Runnable runnable, V result) 的实现
// Callable、Future、FutureTask的使用示例
public class MyFutureTask {
private static class myCalable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
Thread.sleep(1000);
// SleepTools.ms(1000); throws InterruptedException
for (int i = 0; i < 5000; i++) {
sum = sum + i;
}
System.out.println(Thread.currentThread().getName() + " 计算结果= " + sum);
return sum;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
myCalable myCalable = new myCalable();
FutureTask<Integer> futureTask = new FutureTask<>(myCalable);
new Thread(futureTask).start();
Random random = new Random();
if (random.nextBoolean()) {
System.out.println("获取线程结果=" + futureTask.get());
} else {
/*cancel(true)中断线程
cancel()是用interrupt()实现的
所以要注意当线程中抛出InterruptedException异常时,线程不会中断,需要在抛出异常时再次调用中断 操作才可*/
futureTask.cancel(true);
System.out.println("中断了计算线程");
}
}
}
线程安全
什么是线程安全
不管调用者如何调用一个类,这个类都能表现出正常的行为
如何保证线程安全
- 无状态类
没有成员变量只有成员方法的类被称之为无状态类- 让类不可变
有成员变量,但类中的成员变量都是使用final修饰的不可变变量,称该类不可变- volatile
- 加锁
- CAS
死锁
简单的死锁
解决方式:在多个线程中,保证所有线程拿锁的顺序一直
//反例 :简单的死锁
public class NormalDeadLock {
private static Object o1 = new Object(); // 第一个锁
private static Object o2 = new Object(); // 第一个锁
private static class MyRunnable implements Runnable {
@Override
public void run() {
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " I get hold of o1...");
SleepTools.ms(100);
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " I get hold of o2...");
}
}
}
}
private static class MyRunnable1 implements Runnable {
@Override
public void run() {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " I get hold of o2...");
SleepTools.ms(100);
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " I get hold of o1...");
}
}
}
}
public static void main(String[] args) {
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable1()).start();
}
}
//正例:避免简单的死锁
public class NormalDeadLock {
private static Object o1 = new Object(); // 第一个锁
private static Object o2 = new Object(); // 第一个锁
private static class MyRunnable implements Runnable {
@Override
public void run() {
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " I get hold of o1...");
SleepTools.ms(100);
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " I get hold of o2...");
}
}
}
}
private static class MyRunnable1 implements Runnable {
@Override
public void run() {
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " I get hold of o1...");
SleepTools.ms(100);
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " I get hold of o2...");
}
}
}
}
public static void main(String[] args) {
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable1()).start();
}
}
动态的死锁
当锁定对象不是固定的成员变量,而是方法中的动态的参数时。造成的死锁
解决方案:使用哈希(HashCode)算法计算参数的哈希值,以哈希值的大小确定拿锁定顺序
//示例 : 模拟支付公司转账的动作,分别实现线程安全,线程不安全的方法的实现
// TrasnferAccount线程不安全的转账实现
// SafeOperate1 线程安全的转账实现
public class PayCompany {
private static class myRunnable implements Runnable {
private UserAccountDTO from;
private UserAccountDTO to;
private double amount;
private ITransfer iTransfer;
public myRunnable(UserAccountDTO from, UserAccountDTO to, double amount, ITransfer iTransfer) {
this.amount = amount;
this.from = from;
this.to = to;
this.iTransfer = iTransfer;
}
@Override
public void run() {
iTransfer.transfer(from, to, amount);
}
}
public static void main(String[] args) {
UserAccountDTO zhangsan = new UserAccountDTO("zhangsan", 10000);
UserAccountDTO lisi = new UserAccountDTO("lisi", 10000);
// ITransfer iTransfer = new TrasnferAccount(); 线程不安全的
ITransfer iTransfer = new SafeOperate1(); // 线程安全的
myRunnable myRunnable = new myRunnable(zhangsan, lisi, 2000, iTransfer);
myRunnable myRunnable2 = new myRunnable(lisi, zhangsan, 4000, iTransfer);
new Thread(myRunnable).start();
new Thread(myRunnable2).start();
SleepTools.second(2);
System.out.println("zhangsan money = " + zhangsan.getMoney());
System.out.println("lisi money = " + lisi.getMoney());
}
}
//转账接口
public interface ITransfer {
void transfer(UserAccountDTO from, UserAccountDTO to, double amount);
}
//不安全的转账实现
public class TrasnferAccount implements ITransfer {
@Override
public void transfer(UserAccountDTO from, UserAccountDTO to, double amount) {
synchronized (from) {
SleepTools.ms(100);
System.out.println(Thread.currentThread().getName() + " get" + from.getName());
synchronized (to) {
System.out.println(Thread.currentThread().getName() + " get" + to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
//安全的转账实现
public class SafeOperate1 implements ITransfer {
private final static Object look = new Object();
@Override
public void transfer(UserAccountDTO from, UserAccountDTO to, double amount) {
int fromHashCode = System.identityHashCode(from);
int toHashCode = System.identityHashCode(to);
// 先锁小的
if (fromHashCode < toHashCode) {
synchronized (from) {
synchronized (to) {
from.flyMoney(amount);
to.addMoney(amount);
}
}
} else if (toHashCode < fromHashCode) {
synchronized (to) {
synchronized (from) {
from.flyMoney(amount);
to.addMoney(amount);
}
}
} else {
// 两个账户hashCode相等的情况下(概率在千万分之一以下),加一个公共锁保证转账动作不会同时执行
synchronized (look) {
synchronized (from) {
synchronized (to) {
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
//
}
}
}
//用户实体类
public class UserAccountDTO {
private String name;
private double money;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
// 转入资金
public void addMoney(double amount) {
money = money + amount;
}
// 转出资金
public void flyMoney(double amount) {
money = money - amount;
}
public UserAccountDTO(String name, double money) {
super();
this.name = name;
this.money = money;
}
}
其他的线程安全问题
- 活锁
lock.tryLock(); 当一个线程去尝试的拿一个锁时,如果拿不到就是释放该线程所持有的所以锁
A - [1] ~ [2] 线程A拿到了锁1尝试去拿锁2,拿不到释放
B - [2] ~ [1] 线程B拿到了锁2尝试去拿锁1,拿不到释放
这种情况有可能会造成线程A和线程B无限的去拿锁放锁的这个过程。 执行着无限的无效操作就称之为活锁- 线程饥饿
线程优先级高的线程总是占有cpu,线程优先级低的线程总是拿不到cpu。此时线程优先级低的线程就处于线程饥饿状态- 性能
//线程安全-性能问题示例 // 方法处理业务后为成员变量赋值 public class PerformanceTest { private int result; @SuppressWarnings("unused") private synchronized int calculate(int a, int b) { // 当a = 1, b = 1 处理时间短 // 当a = 1000000, b = 1000000 处理时间长 // 即方法的计算(处理业务)时间过长时 导致后续线程等待会造成性能问题 result = a * b; return result; } /** * * 解决方案 * * 将锁的范围缩小,将不会影响线程安全问题的业务处理过程(计算)放在锁外,仅仅将影响线程安全的操 * 作(为成员变量赋值)放在锁内 * * @param a * @param b * @return */ @SuppressWarnings("unused") private int calculate1(int a, int b) { int temp = a * b; synchronized (this) { result = temp; return result; } } }
显示锁和CAS
显示锁
- Lock,Lock的核心用法【lock() ,unlock(), tryLock()】
一般通过实现ReentrantLock获取Lock的实例
//Lock的标准用法
public class MyLock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
lock.lock();
try {
// do business......
} finally {
lock.unlock();
}
}
}
//使用Lock的简单示例
public class LockCase {
private Lock lock = new ReentrantLock();
private int age = 10000;
public void addAge() {
lock.lock();
try {
age++;
} finally {
lock.unlock();
}
}
public void deleteAge() {
lock.lock();
try {
age--;
} finally {
lock.unlock();
}
}
public int getAge() {
return age;
}
private static class AddRunnable implements Runnable {
private LockCase lockCase;
public AddRunnable(LockCase lockCase) {
super();
this.lockCase = lockCase;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
lockCase.addAge();
}
}
}
/**
* 加锁能保证线程安全结果正确,但是会降低性能
*
* 加锁Lock,耗费的时间(7ms, 6ms, 7ms, 9ms, 8ms).
*
* 加锁synchronized,耗费的时间(5ms, 4ms, 5ms, 6ms, 5ms).
*
* 不加锁[线程不安全的],耗费的时间(3ms, 4ms, 2ms, 3ms, 4ms).
*/
public static void main(String[] args) {
long start = System.currentTimeMillis();
LockCase lockCase = new LockCase();
new Thread(new AddRunnable(lockCase)).start();
for (int i = 0; i < 10000; i++) {
lockCase.deleteAge();
}
SleepTools.ms(500);
System.out.println(
"并发运行后的结果:" + lockCase.getAge() + " 运行时间=" + (System.currentTimeMillis() - start - 500) + "ms");
}
}
- ReentrantReadWriteLock读写锁
当读多写少的情况下使用读写锁的性能比用ReentrantLock要高
//读写线程的使用示例
public class BusiApp {
static final int readWriteRatio = 10;// 读写线程的比例
static final int minthreadCount = 3;// 最少线程数
static CountDownLatch latch = new CountDownLatch(1); // 让读写线程同时运行
// 读线程
private static class GetThread implements Runnable {
private GoodsService goodsService;
public GetThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
try {
latch.await();// 让读写线程同时运行
} catch (InterruptedException e) {
}
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {// 操作100次
goodsService.getNum();
}
System.out.println(
Thread.currentThread().getName() + "读取商品数据耗时:" + (System.currentTimeMillis() - start) + "ms");
}
}
// 写线程
private static class SetThread implements Runnable {
private GoodsService goodsService;
public SetThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
try {
latch.await();// 让读写线程同时运行
} catch (InterruptedException e) {
}
long start = System.currentTimeMillis();
Random r = new Random();
for (int i = 0; i < 10; i++) {// 操作10次
SleepTools.ms(50);
goodsService.setNum(r.nextInt(10));
}
System.out.println(Thread.currentThread().getName() + "写商品数据耗时:" + (System.currentTimeMillis() - start)
+ "ms---------");
}
}
/**
* 读写锁: 写(1475ms,1584ms,1643ms) 读(2114ms,2115ms,2115ms,2114ms,2115ms,2114ms)
*
* 内置锁: 写(17812ms,17819ms,17826ms)
* 读(571ms,1881ms,2457ms,3037ms,3620ms,4194ms,...,16809ms,17355ms) //依次递增
*
*/
public static void main(String[] args) throws InterruptedException {
GoodsInfo goodsInfo = new GoodsInfo("Cup", 100000, 10000);
// GoodsService goodsService = new UseSyn(goodsInfo); // 内置锁实现业务处理
GoodsService goodsService = new UseRwLock(goodsInfo); // 读写锁实现业务处理
for (int i = 0; i < minthreadCount; i++) {
Thread setT = new Thread(new SetThread(goodsService));
for (int j = 0; j < readWriteRatio; j++) {
Thread getT = new Thread(new GetThread(goodsService));
getT.start();
}
setT.start();
}
latch.countDown();
}
}
//业务处理接口
public interface GoodsService {
public GoodsInfo getNum();
public void setNum(int number);
}
//内置锁业务处理实现类
public class UseSyn implements GoodsService {
private GoodsInfo goodsInfo;
public UseSyn(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public synchronized GoodsInfo getNum() {
SleepTools.ms(5);
return this.goodsInfo;
}
@Override
public synchronized void setNum(int number) {
SleepTools.ms(5);
goodsInfo.changeNumber(number);
}
}
//读写锁业务处理实现类
public class UseRwLock implements GoodsService {
private GoodsInfo goodsInfo;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock getLock = lock.readLock();// get
private final Lock setLock = lock.writeLock();// set
public UseRwLock(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public GoodsInfo getNum() {
getLock.lock();
try {
SleepTools.ms(5);
return this.goodsInfo;
} finally {
getLock.unlock();
}
}
@Override
public void setNum(int number) {
setLock.lock();
try {
SleepTools.ms(50);
this.goodsInfo.changeNumber(number);
} finally {
setLock.unlock();
}
}
}
//商品实体类
public class GoodsInfo {
@SuppressWarnings("unused")
private final String name;
private double totalMoney;// 总销售额
private int storeNumber;// 库存数
public GoodsInfo(String name, int totalMoney, int storeNumber) {
this.name = name;
this.totalMoney = totalMoney;
this.storeNumber = storeNumber;
}
public double getTotalMoney() {
return totalMoney;
}
public int getStoreNumber() {
return storeNumber;
}
public void changeNumber(int sellNumber) {
this.totalMoney += sellNumber * 25;
this.storeNumber -= sellNumber;
}
}
- Condition
Condition是显示的等待与通知的实现,与Lock配合使用
//使用Lock和Condition实现的快递变化示例
public class ExpressCond {
public final static String CITY = "ShangHai";
private int km;/* 快递运输里程数 */
private String site;/* 快递到达地点 */
private Lock kmLock = new ReentrantLock();
private Lock siteLock = new ReentrantLock();
private Condition kmCondition = kmLock.newCondition();
private Condition siteCondition = siteLock.newCondition();
public ExpressCond() {
}
public ExpressCond(int km, String site) {
this.km = km;
this.site = site;
}
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理 */
public void changeKm() {
kmLock.lock();
try {
this.km = 101;
kmCondition.signal();
} finally {
kmLock.unlock();
}
}
/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理 */
public synchronized void changeSite() {
siteLock.lock();
try {
this.site = "BeiJing";
siteCondition.signal();
} finally {
siteLock.unlock();
}
}
public void waitKm() {
kmLock.lock();
try {
while (this.km <= 100) {// 公里数小于100不做处理
try {
kmCondition.await();
System.out.println("Check Km thread[" + Thread.currentThread().getId() + "] is be signal");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
kmLock.unlock();
}
System.out.println("the Km is " + this.km + ",I will change db");
}
public void waitSite() {
siteLock.lock();
try {
while (this.site.equals(CITY)) {// 快递到达目的地
try {
siteCondition.await();
System.out.println("Check Site thread[" + Thread.currentThread().getId() + "] is be signal");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
siteLock.unlock();
}
System.out.println("the site is " + this.site + ",I will call user");
}
}
//Lock和Condition实现,测试类
public class TestCond {
private static ExpressCond express = new ExpressCond(0, ExpressCond.CITY);
/* 检查里程数变化的线程,不满足条件,线程一直等待 */
private static class CheckKm extends Thread {
@Override
public void run() {
express.waitKm();
}
}
/* 检查地点变化的线程,不满足条件,线程一直等待 */
private static class CheckSite extends Thread {
@Override
public void run() {
express.waitSite();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new CheckSite().start();
}
for (int i = 0; i < 3; i++) {
new CheckKm().start();
}
Thread.sleep(1000);
express.changeKm();// 快递地点变化
}
}
CAS(原子操作)
- CAS原理
比较且交换三个运算符:一个内存地址V,一个期望的旧值A,一个新值B。操作的时候如果在这个地址上存放的值等于这个期望旧值A,则将地址上的值赋值为新值B,否则不做任何操作
- CAS的问题
- ABA问题, A—》C —》A,线程1已经对元素进行了操作但值的结果最终并没有改变,这时线程2会误认为元素没有进行过操作而进行赋值。解决方法:增加版本号 1A—》2C —》3A
- 循环的时间长开销大,自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销
- 往往只能保证一个共享变量的原子操作(因为它是对一个内存地址判断。对对象和多个变量使用锁来保证线程安全
- jdk中相关的原子操作类
- 更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
- 原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater
//使用基本类型原子操作类,示例
public class UseAtomicInt {
static AtomicInteger ai = new AtomicInteger(10);
public static void main(String[] args) {
//getAndIncrement,获取原值并加1
System.out.println(ai.getAndIncrement());
System.out.println(ai.get());
//incrementAndGet 加1并获取新值
System.out.println(ai.incrementAndGet());
System.out.println(ai.get());
}
}
//使用引用类型原子操作类,示例
public class UseAtomicReference {
// 原子引用类型的实例
public static AtomicReference<UserInfo> aur = new AtomicReference<UserInfo>();
public static void main(String[] args) {
UserInfo user = new UserInfo("Mark", 15);// 要修改的实体的实例
aur.set(user);// 用原子引用类型包装
UserInfo updateUser = new UserInfo("Bill", 17);// 要变化成为的新实例
aur.compareAndSet(user, updateUser);
System.out.println(aur.get().getName());
System.out.println(aur.get().getAge());
System.out.println(user.getName());
System.out.println(user.getAge());
}
// 定义一个实体类
static class UserInfo {
private String name;
private int age;
public UserInfo(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
//使用带版本戳的原子操作类,示例
public class UseAtomicStampedReference {
@SuppressWarnings({ "unchecked", "rawtypes" })
static AtomicStampedReference<String> asr = new AtomicStampedReference("mark", 0);
public static void main(String[] args) throws InterruptedException {
final int oldStamp = asr.getStamp();// 拿初始版本0
final String oldReference = asr.getReference();// 初始值
System.out.println(oldReference + "============" + oldStamp);
Runnable target = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":当前变量值:" + oldReference + "-当前版本戳:" + oldStamp
+ "-" + asr.compareAndSet(oldReference, oldReference + "+Java", oldStamp, oldStamp + 1));
}
};
Thread rightStampThread = new Thread(target);
Runnable target2 = new Runnable() {
@Override
public void run() {
String reference = asr.getReference();// 变量的最新值
System.out.println(Thread.currentThread().getName() + ":当前变量值:" + reference + "-当前版本戳:" + asr.getStamp()
+ "-" + asr.compareAndSet(reference, reference + "+C", oldStamp, oldStamp + 1));
}
};
Thread errorStampThread = new Thread(target2);
rightStampThread.start();
rightStampThread.join();
errorStampThread.start();
errorStampThread.join();
System.out.println(asr.getReference() + "============" + asr.getStamp());
}
}
线程池
为什么要使用线程池
- 降低我们的资源消耗
- 提高我们的响应速度
- 提高线程的可管理性
线程池的基本使用
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue, threadFactory, handler)
corePoolSize 线程池的大小
maximumPoolSize 最大线程池数
keepAliveTime 线程空闲时间:当存在大于线程池大小的线程且多于的线程处于空闲状态时,超过了空闲时间会自动销毁
unit 线程空闲时间的单位
workQueue 阻塞队列: 当请求线程大于最大线程池数量时,多余的线程就会进入放入阻塞队列中
threadFactory 线程工厂: 可以为线程自定义名称,缺省情况下默认命名规则 ( pool-数字-thread-数字)
handler 饱和策略: 当阻塞队列也满了的时候,多余的线程请求的处理策略、机制 . 策略一: AbortPolocy 直接抛出异常 缺省默认的 策略二: CallerRunsPolicy 使用调用者所在的线程执行任务 策略三: DiscardOldestPolicy 将阻塞队列中最靠前的任务丢弃 策略四: DiscardPolicy 直接丢弃最新的请求任务
//线程池基本实现示例
public class MyThreadPool {
private static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " I do work...");
SleepTools.ms(5);
}
}
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 2, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(Integer.MAX_VALUE), new ThreadPoolExecutor.AbortPolicy());
// 创建一个固定数量的线程池
// ExecutorService executorService = Executors.newFixedThreadPool(2);
// 无限制的创建线程(Integer.MAX_VALUE)
// ExecutorService executorService = Executors.newCachedThreadPool();
// 创建一个池大小为1,最大线程数为1,阻塞队列为Integer.MAX_VALUE的线程池
// ExecutorService executorService =
// Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executorService.execute(new MyRunnable());
}
executorService.shutdown();
}
}
jdk实现的内置线程池
FixedThreadPool - Executors.newFixedThreadPool(2);
创建一个固定数量的线程池。线程池大小与最大线程数相同,阻塞队列为Integer.MAX_VALUE【LinkedBlockingQueue】
SingleThreadExecutor - Executors.newSingleThreadExecutor();
创建一个池大小为1,最大线程数为1,阻塞队列【LinkedBlockingQueue】为Integer.MAX_VALUE的线程池
CachedThreadPool - Executors.newCachedThreadPool();
创建一个池大小为0,最大线程数为Integer.MAX_VALUE,无阻塞队列【SynchronousQueue】的线程池
WorkStealingPool
利用所有运行的处理器数目来创建一个工作窃取的线程池,使用forkjoin实现
ScheduledThreadPoolExecutor 定时任务线程池
ScheduledThreadPoolExecutor,包含若干个线程的ScheduledThreadPoolExecutor
SingleThreadScheduledExecutor,只包含一个线程的ScheduledThreadPoolExecutor
//ScheduledThreadPoolExecutor使用的简单示例
public class ScheduledCase {
// 定时任务的工作类
public static class ScheduleWorker implements Runnable {
public final static int Normal = 0;// 普通任务类型
public final static int HasException = -1;// 会抛出异常的任务类型
public final static int ProcessException = 1;// 抛出异常但会捕捉的任务类型
public static SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private int taskType;
public ScheduleWorker(int taskType) {
this.taskType = taskType;
}
@Override
public void run() {
if (taskType == HasException) {
System.out.println(formater.format(new Date()) + " Exception be made,will next task run?");
throw new RuntimeException("ExceptionHappen");
} else if (taskType == ProcessException) {
try {
System.out.println("ProcessException ..." + formater.format(new Date()));
throw new RuntimeException("ProcessException");
} catch (RuntimeException e) {
System.out.println("ProcessException catched,,will next task run?");
}
} else {
System.out.println("Normal..." + formater.format(new Date()));
}
}
}
public static void main(String[] args) {
ScheduledThreadPoolExecutor schedule = new ScheduledThreadPoolExecutor(1);
// 延时Runnable任务(仅执行一次), 任务在1000ms后执行
schedule.schedule(new ScheduleWorker(ScheduleWorker.Normal), 1000, TimeUnit.MILLISECONDS);
// 固定时间间隔执行的任务 ,第一次任务在1000ms后执行,第二次任务在第一次任务完成后 + 3000 ms 执 //行
schedule.scheduleWithFixedDelay(new ScheduleWorker(ScheduleWorker.Normal), 1000, 3000, TimeUnit.MILLISECONDS);
// 固定时间间隔执行的任务,第一次任务在1000ms后执行,第二次任务在1000+3000 ms后执行,第三次在
// 1000+3000*2 ms后执行
schedule.scheduleAtFixedRate(new ScheduleWorker(ScheduleWorker.Normal), 1000, 3000, TimeUnit.MILLISECONDS);
// 固定时间间隔执行的任务,开始执行后就触发异常,next周期将不会运行
schedule.scheduleAtFixedRate(new ScheduleWorker(ScheduleWorker.HasException), 1000, 3000,
TimeUnit.MILLISECONDS);
// 固定时间间隔执行的任务,虽然抛出了异常,但被捕捉了,next周期继续运行
schedule.scheduleAtFixedRate(new ScheduleWorker(ScheduleWorker.ProcessException), 1000, 3000,
TimeUnit.MILLISECONDS);
}
}
并发容器
ConcurrentHashMap
并发安全的HashMap,与普通的Map使用方法一样
特有方法putIfAbsent,通过key来判断Map容器中是否已经存在,没有则放入Map,存在则返回已有数据
ConcurrentSkipListMap 和 ConcurrentSkipListSet
ConcurrentSkipListMap线程安全的TreeMap
ConcurrentSkipListSet线程安全的TreeSet
SkipList —》 二叉查找树算法实现
ConcurrentLinkedQueue
ConcurrentLinkedQueue线程安全的LinkedList
常用方法
- add() 将元素插入队列的头部
- offer() 将元素插入队列的尾部
- peek() 检索链表头部元素并获取,不从链表中移除元素
- poll() 检索链表头部元素并获取,从链表中移除元素
CopyOnWriteArrayList 和 CopyOnWriteArraySet
线程安全的ArrayList 和 ArraySet
实现原理CopyOnWrite
写时复制(写时将容器复制一份修改,修改完成后指向换成新的 ),从而实现(不需要加锁的)并发的读
读写分离思想的容器,最适用于“读多写少情况”
缺点
1、内存占用大,2、数据一致性的问题(只能保证数据最终一致,不能做到数据的实时一致)
阻塞队列
什么是阻塞队列
当插入或移除元素条件不满足的时候,让工作线程进入到阻塞状态的队列
常用方法
特殊值: 移除时,队列中有元素返回元素,无元素返回null。插入时成功返回true
方法 | 抛出异常 | 返回特殊值 | 阻塞 | 超时 |
插入 | add | offer | put | offer(e,timeout,unit) |
移除 | remove | poll | take | poll(long timeout, TimeUnit unit) |
检查 | element | peek |
常用阻塞队列
- ArrayBlockingQueue: 数组结构,有界,先进先出
ArrayBlockingQueue的锁只有一把,直接将元素推入队列,初始化时必须指定大小
- LinkedBlockingQueue: 链表结构,有界,先进先出
LinkedBlockingQueue的锁是分离的(向队列中推入元素和拿取元素是不同的锁),元素包装一下node放入队列,初始化时可以不指定大小(缺省情况下队列大小为Integer.MAX_VALUE)
- PriortyBlockingQueue: 支持优先级排序的队列。无界
默认是元素的自然排序。如果想要自定义排序,可以在自己的自定义类中实现compareTo();或者初始化queue的时候,指定构造参数Compartor
- DelayQueue: 延迟队列,用PriortyBlockingQueue实现的,无界
支持延迟获取元素,元素必须要实现一个Delayed。常用于缓存系统,订单到期,延迟支付
- SynchronousQueue: 不存储元素的队列(只是担当元素中间传递的一个角色)
- LinkedTransferQueue: 链表结构,无界
相对于其他的阻塞队列有两个特有的方法tryTransfer和transfer
使用tryTransfer和transfer向队列中推入元素时发现有消费者在等待从队列中拿取元素,将直接传递给消费者而不进入队列
transfer和tryTransfer区别在于,当没有消费者等待时transfer会将元素放入队列直到有消费者拿取了元素才会返回,而tryTransfer将元素放入队列中后会直接返回
- LinkedBlockingDeque: 链表结构的双向队列,有界
提供了比其他队列多的一些方法
addFirst/addLast,offerFisrt/offerLast,peekFirst/peekLast等
当直接使用add,remove,take等方法时,add默认为addLast,remove默认为removeLast,take默认为takeFirst
阻塞队列的实现原理
阻塞队列的实现其实就是线程锁的等待和通知机
阻塞队列使用示例
//使用DelayQueue实现的一个延迟订单的处理
public class Test {
public static void main(String[] args) {
DelayQueue<ItemVo<Order>> delayQueue = new DelayQueue<ItemVo<Order>>();
List<ItemVo<Order>> itemVos = new ArrayList<ItemVo<Order>>();
ItemVo<Order> itemVo = new ItemVo<Order>(5000, new Order("TD123", 1000));
ItemVo<Order> itemVo1 = new ItemVo<Order>(8000, new Order("JD123", 2000));
itemVos.add(itemVo);
itemVos.add(itemVo1);
PutOrder putOrder = new PutOrder(delayQueue, itemVos);//推入(添加)元素
FetchOrder fetchOrder = new FetchOrder(delayQueue);//拿取(移除)元素
new Thread(putOrder).start();
new Thread(fetchOrder).start();
for (int i = 0; i < 20; i++) {
SleepTools.ms(500);
System.out.println(i * 500);
}
}
}
//添加元素到队列的线程
public class PutOrder implements Runnable {
private DelayQueue<ItemVo<Order>> queue;
private List<ItemVo<Order>> itemVos;
public PutOrder(DelayQueue<ItemVo<Order>> queue, List<ItemVo<Order>> itemVos) {
super();
this.queue = queue;
this.itemVos = itemVos;
}
@Override
public void run() {
for (ItemVo<Order> itemVo2 : itemVos) {
queue.offer(itemVo2);
Order data = itemVo2.getData();
System.out.println("订单[" + data.getOrderNo() + "," + data.getOrderMoney() + "]推入延时队列");
}
}
}
//从队列中获取(移除)元素的线程
public class FetchOrder implements Runnable {
private DelayQueue<ItemVo<Order>> queue;
public FetchOrder(DelayQueue<ItemVo<Order>> queue) {
super();
this.queue = queue;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
ItemVo<Order> take = queue.take();
Order data = take.getData();
System.out.println("从延迟队列中取得了 订单=" + data.getOrderNo() + " - " + data.getOrderMoney() + " 执行了业务处理");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//延迟队列元素的封装类
public class ItemVo<T> implements Delayed {
// 到期时间(只有到期后才能从队列中获取),单位毫秒
private long activeTime;
// 业务数据
private T data;
public ItemVo(long activeTime, T data) {
super();
this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime, TimeUnit.MILLISECONDS) + System.nanoTime();
this.data = data;
}
public long getActiveTime() {
return activeTime;
}
public T getData() {
return data;
}
// 根据到期时间排序
@Override
public int compareTo(Delayed o) {
long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
return (d == 0) ? 0 : (d > 0 ? 1 : -1);
}
// 获取到期(激活)的剩余时间
@Override
public long getDelay(TimeUnit unit) {
long d = unit.convert(this.activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
return d;
}
}
//订单实体类
public class Order {
private final String orderNo;
private final double orderMoney;
public Order(String orderNo, double orderMoney) {
super();
this.orderNo = orderNo;
this.orderMoney = orderMoney;
}
public String getOrderNo() {
return orderNo;
}
public double getOrderMoney() {
return orderMoney;
}
}