多线程
文章目录
创建线程的方式
1.继承Thread类
public class test extends Thread {
@Override
public void run(){
System.out.println("Thread")
}
public static void main(String[] args){
Test test = new Test();
test.start();
}
}
2.实现Runnable接口
public class test implments Runnable{
@Override
public void run(){
System.out.println("Thread")
}
public static void main(String[] args){
Test test = new Test();
Thread thread = new Thread(test);
thread.start();
}
}
3.实现callable接口
public class CallableFutureTaskDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int t = 1;
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + (t++));
}
return t;
}
public static void main(String[] args) {
Callable<Integer> cftd1 = new CallableFutureTaskDemo();
Callable<Integer> cftd2 = new CallableFutureTaskDemo();
FutureTask<Integer> ft1 = new FutureTask<>(cftd1);
FutureTask<Integer> ft2 = new FutureTask<>(cftd2);
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
t1.setName("Thread1");
t2.setName("Thread2");
t1.start();
t2.start();
try {
System.out.println(ft1.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
线程的状态
- 创建状态:新建了一个线程对象。
- 就绪状态:线程创建了对象后,其他线程调用了该对象的start方法。该线程变得可运行,除了CPU的使用权其他运行时所需要的资源都已经全部获得。
- 运行状态:就绪状态的线程得到CPU的资源,执行程序代码。
- 阻塞状态:由于某些原因放弃CPU使用权,暂时停止运行,知道线程进入就绪状态,才有机会转到运行状态。
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池,直至执行notify()或者notifyAll()方法才能唤醒该线程。
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
- 运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时, 或者I/O处理完毕时,线程重新转入就绪状态
- 死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
sleep() 和 yield()和join()
- sleep(时间):每一个对象都会有一个锁,sleep不会释放锁,可以模拟网络延时、倒计时。
- yield():礼让线程让当前执行的线程暂停,但不阻塞,将线程从运行在状态转为就绪状态,礼让不一定会成功。
- join():合并线程,待此线程执行完毕,在执行其他的线程其他线程阻塞。
线程同步
并发:同一个对象被多个线程操作。
synchronized同步方法: 同步方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行就独占该锁。直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁继续执行。
synchronized同步代码块:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。
死锁:指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
产生死锁的必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
- 不剥削条件:进程以获得的资源,在未使用之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
synchronized 和lock的区别
- synchronized 是java 内置的关键字,在jvm 层面Lock是一个类
- synchronized 是无法获取锁的状态,Lock可以判断是否获取到锁的状态。
- synchronized 可以自动释放锁,Lock需要在finally中手动释放锁,否则容易造成线程死锁。
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1
阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以
不用一直等待就结束了。 - synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
读写锁(ReadWriteLock)
独占锁(写锁):指该锁一次只能被一个线程锁持有。ReentrantLock和 Synchronized 都是独占锁。
共享锁(读锁):该锁可被多个线程所持有。
public class ReadWriteLock {
//读-读 不能共存 读-写可以共存 写-写 不可以共存
public static void main(String[] args) {
Mycache mycache = new Mycache();
for (int i = 1; i <= 5; i++) {
int t = i;
new Thread(()->{
mycache.put(t+"",t+"");
}).start();
}
for (int i = 1; i <= 5; i++) {
int t = i;
new Thread(()->{
mycache.get(t+"");
}).start();
}
}
}
/*
* 自定义缓存
* */
class Mycache{
private volatile Map<String,Object> map = new HashMap<>();
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//写
public void put(String key,String value){
try {
readWriteLock.writeLock().lock();//加写锁
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//存
public void get(String key){
try {
readWriteLock.readLock().lock();//加读锁
System.out.println(Thread.currentThread().getName()+"读"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
阻塞队列
阻塞队列是一个队列,在数据结构中起的作用如下图:
当队列是空的,从队列中获取元素的操作将会被阻塞。
当队列是满的,从队列中添加元素的操作将会被阻塞。
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。
试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完
全清空,使队列变得空闲起来并后续新增。
ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:由链表结构组成的有界阻塞队列。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
LinkedTransferQueue:由链表组成的无界阻塞队列。
LinkedBlockingDeque:由链表组成的双向阻塞队列。
处理方式 | 抛出异常 | 返回特殊值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add(e) | offer(e) | put(e)一直等待 | offer(e,超时时间,超时单位)超时结束TimeUnit.Seconds |
移除 | remove() | poll() | take() | poll(time,unit) |
检查队首元素 | element() | peek() | 不可用 | 不可用 |
SynchronousQueue(同步队列)
**SynchronousQueue:**与其他的 BlockingQueue 不同,SynchronousQueue是一个不存储元素的 BlockingQueue 。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
public class SynchronousQueueDemo {
public static void main(String[] args) {
//put 一个值必须take取出来,否则不能put进去值
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+" put2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+" put3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
}
}
线程池
线程池的三大方法
Executors.newFixedThreadPool(int):执行长期任务性能好,创建一个线程池,一池有N个固定的线程,有固定线程数的线程
Executors.newSingleThreadExecutor():只有一个线程
**Executors.newCachedThreadPool();**执行很多短期异步任务,线程池根据需要创建新线程,需要多少创建多
public class Demo {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
ThreadPoolExecutor 七大参数
**corePoolSize:**核心线程数
**maximumPoolSize:**最大线程数
**keepAliveTime:**空闲的线程保留的时间
TimeUnit:空闲线程的保留时间单位
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
**BlockingQueue:**阻塞队列
**ThreadFactory:**线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler :拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
使用ThreadPoolExecutor创建线程池
public class Demo01 {
public static void main(String[] args) {
//最大线程如何去设置?
//CPU密集型 Runtime.getRuntime().availableProcessors() 获取CPU的在最大线程数
//IO密集型 比业务中的大型线程多就可以了
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
5,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 0; i < 100; i++) {
poolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
poolExecutor.shutdown();
}
}
}
四大函数式接口
**Function()**函数型接口,有一个输入,有一个输出
public static void main(String[] args) {
// 函数式接口,可以改为 lambda 表达式
//Function<String,Integer> function = new Function<String, Integer>() {
// @Override
// public Integer apply(String s) {
// return 1024;
// }
//};
// 简写
Function<String,Integer> function = s->{return s.length();};
System.out.println(function.apply("abc"));
}
**Predicate()**断定型接口,有一个输入参数,返回只有布尔值。
public static void main(String[] args) {
//Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return false;
// }
//};
// 简写
Predicate<String> predicate = s -> {return s.isEmpty();};
System.out.println(predicate.test("abc"));
}
Comsumer消费型接口,有一个输入参数,没有返回值
public static void main(String[] args) {
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String s) {
//
// }
// };
// 简写
Consumer<String> consumer = s -> { System.out.println(s);};
consumer.accept("abc");
}
Supplier供给型接口,没有输入参数,只有返回参数
public static void main(String[] args) {
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return null;
// }
// };
Supplier<String> supplier = ()->{return "abc";};
System.out.println(supplier.get());
}
ForkJoin 合并分支
ForkJoin:讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果
工作窃取
一个工作线程下会维护一个包含多个子任务的双端队列。而对于每个工作线程来说,会从头部到尾部依次执行任务。这时,总会有一些线程执行的速度较快,很快就把所有任务消耗完了。
package com.example.department.config;
import java.io.FileOutputStream;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.function.LongBinaryOperator;
import java.util.stream.LongStream;
public class ForkJoin extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp = 10000L;
public ForkJoin(Long start, Long end) {
this.start = start;
this.end = end;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
/* long start = System.currentTimeMillis();
Long sum = 0L;
for (Long i = 1L; i < 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println
(sum +"时间"+(end-start));*/
//forkJoin 执行
/* long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();//实现ForkJoin就必须要有ForkJoinPool
ForkJoinTask<Long> forkJoin = new ForkJoin(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoin);
Long aLong = submit.get();//提交任务
long end = System.currentTimeMillis();
System.out.println(aLong +"时间"+(end-start));*/
//Stream 并行流
System.out.println(LongStream.rangeClosed(0L,
10_0000_0000L).parallel().reduce(0,Long::sum));
}
@Override
protected Long compute() {
if(end-start>temp){
//执行分之合并计算
/*
*
* */
Long mid = (start+end)/2;
ForkJoin forkJoin1 = new ForkJoin(start,mid);
forkJoin1.fork();//拆分任务,把任务压入线程队列
ForkJoin forkJoin2 = new ForkJoin(mid,end);
forkJoin2.fork();
return forkJoin1.join() + forkJoin2.join();
}else{
Long sum = 0L;
for (Long i = start; i < end; i++) {
sum += i;
}
System.out.println(sum);
return sum;
}
}
}
异步回调(Future)
为了让程序更加高效,让CPU最大效率的工作,我们会采用异步编程。首先想到的是开启一个新的线程
去做某项工作。再进一步,为了让新线程可以返回一个值,告诉主线程事情做完了,于是乎Future粉墨
登场。然而Future提供的方式是主线程主动问询新线程,要是有个回调函数就爽了。所以,为了满足
Future的某些遗憾,强大的CompletableFuture随着Java8一起来了。
public class Future {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值
/*CompletableFuture<Void> completableFuture =
CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "没有返回, update mysql ok");
});
System.out.println(11111);
completableFuture.get();*/
//有返回值的异步回调
CompletableFuture<Integer> completableFuture1 =
CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "有返回, update mysql ok");
return 11;
});
completableFuture1.whenComplete((t,u)->{
//成功回调
System.out.println(t);
System.out.println(u);//错误信息
}).exceptionally((e)->{
//失败回调
System.out.println(e.getMessage());
return 233; //错误返回的信息
});
}
}
JMM
volitile 是 Java 虚拟机提供的轻量级的同步机制,三大特性:保证可见性、不保证原子性、禁止指令重排。
JMM的同步约定:
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁。
JMM的八种操作:
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态.
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定.
- read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便
随后的load动作使用. - load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机
遇到一个需要使用到变量的值,就会使用到这个指令 - assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的
变量副本中 - store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存
中,以便后续的write使用 - write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须
write - 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量
实施use、store操作之前,必须经过assign和load操作 - 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解
锁 - 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,
必须重新load或assign操作初始化变量的值 - 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
Volatile 用来保证数据的同步,也就是可见性
public class Test11 {
// volatile 不加volatile没有可见性
// 不加 volatile 就会死循环,这里给大家将主要是为了面试,可以避免指令重排
private volatile static int num = 0;
public static void main(String[] args) {
new Thread(()->{
while(num==0){
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
Volatile不保证原子性
不可分割,完整性,也就是某个线程正在做某个具体的业务的时候,中间不可以被加塞或者被分割,需
要整体完整,要么同时成功,要么同时失败。
public class Test13 {
// volatile 不能保证原子性
//使用原子操作类,来保证原子性
private volatile static AtomicInteger num =new AtomicInteger() ;
public static void add(){
//a++ 多线程情况下不安全
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
//Thread.activeCount 线程的数量
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
单例模式
- 饿汉式
public class Hungry {
private Hungry() {
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance() {
return hungry;
}
}
- 懒汉式
public class LazyMan {
private LazyMan() {
System.out.println(Thread.currentThread().getName()+"Start");
}
private static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
// 测试并发环境,发现单例失效
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();}).start();
}
}
}
改进版
public class Lazy {
//双重检测锁模式 懒汉式单例
public Lazy(){
}
private volatile static Lazy lazy; //保证不会出现指令重排
public static Lazy getInstance(){
if(lazy==null){
synchronized (Lazy.class){
if(lazy==null){
lazy = new Lazy(); //不是原子操作,可能会出现指令重排
/*
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.对象指向空间
* */
}
}
}
return lazy;
}
}
- 静态内部类
public class Test {
private Holder() {
}
public static Test getInstance() {
return InnerClass.test;
}
private static class InnerClass {
private static final Test test = new Test();
}
}
CAS
CAS:比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作
内存中的值一致为止。
CAS 应用
CAS 有3个操作数,内存值V,旧的预期值A,要修改的更新值B。且仅当预期值A 和 内存值 V 相同时,
将内存值 V 修改为B,否则什么都不做。
CAS 的缺点
1、循环时间长开销很大。可以看到源码中存在 一个 do…while 操作,如果CAS失败就会一直进行尝试。
2、只能保证一个共享变量的原子操作。
- 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。但是:
- 对多个共享变量操作时,循环CAS就无法保证操作的原子性,这时候就可以用锁来保证原子性。
- 会出现ABA问题
public class CAS {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//如果期望值达到了就更新,否则不更新
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
atomicInteger.getAndIncrement();
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
}
}
ABA问题用原子引用 AtomicReference解决
public class CAS {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
new Thread(
()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("a===>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1,2,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("a2===>"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3===>"+atomicStampedReference.getStamp());
},"a").start();
new Thread(
()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("b===>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
System.out.println("b==>"+atomicStampedReference.getStamp());
},"b").start();
}
}