1、callable接口
public class CallableTest {
public static void main(String[] args) throws Exception {
/**
* callable可以有返回值,可以抛异常,需实现call方法
* 1、怎么启动callable;通过FutureTask,因为FutureTask继承了Runnable
* 2、通过futureTask.get()获取返回的值,会有堵塞
* 3、结果会被缓存,效率高
*/
FutureTask<String> futureTask = new FutureTask(new CallThread());
new Thread(futureTask, "a").start();
new Thread(futureTask, "b").start();//callable只被执行了一次
String res = futureTask.get();
System.out.println(res);
}
}
class CallThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("....");
return "aaa";
}
}
2、减法计数器 latch门闩
//减法计数器,个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
public class CountDownLatchTest {
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "get out");
countDownLatch.countDown();//数量-1
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零后再向下执行
System.out.println("close door");
}
}
3、加法计数器 barrier 屏障
//加法计数器,一个同步辅助类,它允许一组线程互相等待
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("在启动 barrier 时执行的命令;如果不执行任何操作,则该参数为 null");
});
for (int i = 0; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "get out");
try {
cyclicBarrier.await(); //在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
System.out.println("等所有人都出来了一起回家");
System.out.println(cyclicBarrier.getParties());//返回要求启动此 barrier 的参与者数目
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
}
}, String.valueOf(i)).start();
}
}
}
4、semaphore 信号 作用:多个共享资源互斥的使用,并发限流,控制最大线程数
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
semaphore.acquire(); //获得,如果已满,就等待
System.out.println(Thread.currentThread().getName() + "停车");
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName() + "离开");
} catch (InterruptedException e) {
} finally {
semaphore.release(); //释放,会将当前型号量释放+1,然后唤醒等待的线程
}
}, String.valueOf(i)).start();
}
}
}
5、独占锁和共享锁
5.1、独占锁也叫排他锁,是指该锁一次只能被一个线程锁持有,JDK中的synchronized和 JUC中Lock的实现类(ReentrantLock、writeLock)就是互斥锁
5.2、共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。readLock
ReentrantReadWriteLock源码:https://blog.csdn.net/yjn1995/article/details/98979393
public class RenentrantReadWriteLockEx {
/**
* Thread-0开始读取
* Thread-1开始读取
* Thread-0读取ok
* Thread-1读取ok
* Thread-3开始写入
* Thread-3写入ok
* Thread-2开始写入
* Thread-2写入ok
*/
public static void main(String[] args) throws Exception{
MyCacheLock myCacheLock = new MyCacheLock();
new Thread(() -> myCacheLock.read()).start();
new Thread(()->myCacheLock.read()).start();
new Thread(() -> myCacheLock.write()).start();
new Thread(()->myCacheLock.write()).start();
}
}
class MyCacheLock {
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void read() {
//如果另一个线程没有保持写入锁,则获取读取锁并立即返回;
// 未取得锁的线程处于WAITING状态
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始读取");
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "读取ok");
} catch (Exception e) {
} finally {
readWriteLock.readLock().unlock();
}
}
public void write() {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入");
TimeUnit.SECONDS.sleep(5); //sleep时线程处于TIMED_WAITING状态
System.out.println(Thread.currentThread().getName() + "写入ok");
} catch (Exception e) {
} finally {
readWriteLock.writeLock().unlock();
}
}
}
6、BlockingQueue 堵塞队列
FIFO 写入:如果队列满了,就必须堵塞等待; 读取:如果对列空的,必须堵塞等待生产
方式 | 抛异常 | 有返回值,不抛异常 | 等待 一直堵塞 | 等待,超时退出 |
---|---|---|---|---|
添加 | add | offer() | put | offer有参 |
移除 | remove | poll() | take | pool有参 |
判断队列首部 | element() | peek() | - | - |
//抛异常
BlockingQueue queue = new ArrayBlockingQueue(3);
//将指定的元素插入到此队列的尾部,在成功时返回 true,
// 如果此队列已满,则抛出 IllegalStateException
//可添加重复的值
System.out.println(queue.add(1));
System.out.println(queue.add(1));
queue.add(3);
//从此队列中移除指定元素的单个实例(如果存在)
//如果此队列包含指定的元素(或者此队列由于调用而发生更改),则返回 true
//如果有重复的值,只移除一个
System.out.println(queue.remove(1));//返回false
System.out.println(queue.remove());//返回移除的值
//element() 获取但不移除队首的头
//有返回值,不抛异常
BlockingQueue queue = new ArrayBlockingQueue(2);
System.out.println(queue.offer(1));
System.out.println(queue.offer(2));//返回true
System.out.println(queue.offer(3));//返回false
System.out.println(queue.peek());//获取但不移除队首的头,如果空,返回null
System.out.println(queue.poll());
System.out.println(queue.poll());//返回移除的值
System.out.println(queue.poll());//返回null
System.out.println(queue.peek());//返回null
7、同步队列 SyncchronousQueue
一种堵塞队列,其中每个插入操作必须等待另一个线程的移除操作。
没有容量 void put一个元素,必须先take,否则不能继续put
8、SimpleDateFormat线程不安全 使用ThreadLocal
private static final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
sdf.parse("2019-06-12");
} catch (ParseException e) {
}
}).start();
}
原因:在SimpleDateFormat转换日期是通过Calendar对象来操作的,中途有个cal.clear(),calendar被清空了,如果此时线程A将Calendar清空,还没设置新值,线程B也进入parse方法使用了Calendar,就会产生线程安全问题。
9、线程池
- 获取cpu的核数:Runtime.getRuntime().availableProcessors()
9.1、线程池的好处:
降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,
使用线程池可以进行统一的分配,调优和监控。
//获取cpu的核数
System.out.println(Runtime.getRuntime().availableProcessors());
//创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
ExecutorService executor = Executors.newFixedThreadPool(1);
/**
* corePoolSize:如果运行的线程数小于corePoolSize,则创建新的线程处理请求
* 如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程
* ThreadFactory:Executors.defaultThreadFactory()
* keepAliveTime:如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止
* 排队三种通用策略:
* 直接提交(SynchronousQueue,它将任务直接提交给线程而不保持它们),
* 有界队列,无界队列
* 创建线程超出 maximumPoolSize,在这种情况下,任务将被拒绝
* AbortPolicy:抛出 RejectedExecutionException
* CallerRunsPolicy:在调用execute方法的线程中运行被拒绝的任务
* DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序
* DiscardPolicy:不能执行的任务将被删除,但是不抛异常
*/
ThreadPoolExecutor executor1 = new ThreadPoolExecutor(2, 4, 10, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
System.out.println(executor1.getPoolSize());//返回池中的当前线程数;0
executor1.submit(() -> {
System.out.println("1" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
}
});
TimeUnit.SECONDS.sleep(5);
//返回主动执行任务的近似线程数,不包括线程执行结束的,因为上面的线程已完成,所以这里0
System.out.println(executor1.getActiveCount());//0
System.out.println(executor1.getPoolSize());//1
executor1.submit(() -> {
System.out.println("2" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("2-over" + Thread.currentThread().getName());
} catch (Exception e) {
}
});
System.out.println(executor1.getPoolSize());//2
executor1.submit(() -> {
System.out.println("3" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
}
});
//第一个线程执行完了,使用了核心线程池中的线程,所以还是2
System.out.println(executor1.getPoolSize());//2
//按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务
//如果后面继续executor1.submit(,会抛异常
executor1.shutdown();
//尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。
executor1.shutdownNow();
10、ForkJoin
11、CopyOnWriteArrayList
写时复制的思想来通过延时更新的策略来实现数据的最终一致性,并且能够保证读线程间不阻塞。
牺牲数据实时性满足数据的最终一致性即可。
通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。对CopyOnWrite容器进行并发的读的时候,不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,延时更新的策略是通过在写的时候针对的是不同的数据容器来实现的,放弃数据实时性达到数据的最终一致性。
//程序会一直运行,因为size没有加volatile
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
new Thread(() -> {
while (list.size() == 0) {
}
System.out.println(".....");
}).start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
new Thread(() -> {
list.add(1);
}).start();
}
12、synchronized和reentrantlock的区别
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断lockInterruptibly()
(4)synchronized非公平锁,ReentrantLock既可以公平锁,也可以非公平锁
(5)synchronized遇到异常退出,ReentrantLock需在finally里面退出