线程概述
一个程序的运行算是一个进程,一个进程有多个线程,线程(Thread)是进程的基本单元
实现多线程的三种方式
- 继承Thread类 (不推荐,单继承局限性)
- 实现Runnable接口 (推荐)
- 实现Callable接口 (需要返回值类型)
Thread是java.lang包下的,Thread这个类本身也实现了Runnable接口
继承Thread类
//创建线程并不一定立刻执行,全看cpu调度
public class TestThread1 extends Thread{
@Override
public void run() {
/*run方法线程体*/
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码"+i);
}
}
public static void main(String[] args) {
/*创建对象,调用执行*/
new TestThread1().start();
/*主方法,main线程 主线程 直接运行*/
for (int i = 0; i < 20; i++) {
System.out.println("我在学习"+i);
}
new TestThread1().start();
}
}
实现Runnable接口
public class TestThread1 implements Runnable{
@Override
public void run() {
/*run方法线程体*/
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码"+i);
}
}
public static void main(String[] args) {
/*创建runnable接口的实现类对象*/
TestThread1 testThread1 = new TestThread1();
/*创建一个线程对象,通过线程对象来开启我们的线程,这是一个代理*/
new Thread(testThread1).start();
/*主方法,main线程 主线程 直接运行*/
for (int i = 0; i < 20; i++) {
System.out.println("我在学习"+i);
}
}
}
实现Callable接口
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/*这里的泛型是自己加上去的*/
public class TestCallable implements Callable<int> {
@Override
public int call() throws Exception {
for (int i = 0; i < 20; i++) {
System.out.println("我在学习");
}
return 1;
}
public static void main(String[] args) {
/*创建执行服务*/
ExecutorService ser=Executor.newFixedThreadPool(3);
/*提交执行*/
Future<int> res=ser.submit(tl);
/*获取结果*/
int i =res.get();
/*关闭服务*/
ser.shutdown();
}
}
Future、FutureTask、CompletableFuture
Future、FutureTask在java1.5版本提出,用于实现异步计算,在JDK1.8提出CompletableFuture,是对Future的拓展
Future
Future是一个接口,为了实现异步计算抽象的接口
- get方法,用于获得异步调用返回值
- isCancelled方法,用于检查执行是否被取消
- isDone方法,用于判断是否执行是否完成
- cancel方法,用于取消执行
FutureTask
FutureTask是一个实现类,但是实现了RunnableFuture接口,RunnableFuture又实现了Runable和Future接口,所以Thread才可以传入FutureTask类型启动线程
构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
// V result 代表指定接口返回值
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
因为继承于Runnable所以可以直接交给线程池执行,线程池提交任务,返回结果
FutureTask<String> hello = new FutureTask<>(() -> {
System.out.println("hello");
}, "test");
threadPoolExecutor.execute(hello);
assertEquals("test",hello.get());
FutureTask小结
FutureTask能很好的应用于有返回的异步调用,但有些情况处理起来很不方便
- 无法手动完成:当调用远程服务时,如果发现远程服务出现问题,你需要将最近一次正常结果返回;这时使用Future就无法满足该需求。
- 无法添加回调方法:当调用远程服务结束后需要调用其它方法时,如果使用Future,则需要不断循环调用isDone方法判断是否完成;然后调用get获得结果,接着调用其它方法。
- 无法将多任务合并获得结果:当需要并行调用多个远程服务时,在获得返回结果时需要不断循环调用各future的isDone方法。
- 没有异常处理:Future API没有提供异常处理方法。
CompletableFuture
实现了Future接口和CompletionStage接口。其中CompletionStage中包含多种处理方法用于异步计算、异常处理和计算结合等等
CompletionStage
接口定义了任务编排的方法,执行某一阶段,可以向下执行后续阶段。异步执行的,默认线程池是ForkJoinPool.commonPool()
completableFuture可调用complete方法手动完成,否则get方法将阻塞直到任务完成
CompletableFuture<String> completableFuture1 = new CompletableFuture();
new Thread(() -> {
completableFuture1.complete("ok");
}).start();
assertEquals(completableFuture1.get(), "ok");
常用方法
thenApply():把前面任务的执行结果,交给后面的Function
thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回
thenCombine():合并任务,有返回值
thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理,无返回值
runAfterBoth():两个任务都执行完成后,执行下一步操作(Runnable类型任务)
applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)
allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture
whenComplete:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作
exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成
异步操作
CompletetableFuture提供了四个静态方法实现异步操作
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
runAsync()
以Runnable
函数式接口类型为参数,没有返回结果supplyAsync()
以Supplier
函数式接口类型为参数,返回结果类型为U;Supplier接口的get()
是有返回值的(会阻塞)- 使用没有指定
Executor
的方法时,内部使用ForkJoinPool.commonPool()
作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行
Runnable runnable = () -> System.out.println("无返回结果异步任务");
CompletableFuture.runAsync(runnable);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("有返回值的异步任务");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello World";
});
String result = future.get();
获取结果
join()和get()方法都是用来获取CompletableFuture异步之后的返回值。join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)
结果处理
当CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
Action
的类型是BiConsumer<? super T,? super Throwable>
,它可以处理正常的计算结果,或者异常情况- 方法不以
Async
结尾,意味着Action使用相同的线程执行,而Async
可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)- 这几个方法都会返回
CompletableFuture
,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
if (new Random().nextInt(10) % 2 == 0) {
int i = 12 / 0;
}
System.out.println("执行结束!");
return "test";
});
// 任务完成或异常方法完成时执行该方法
// 如果出现了异常,任务结果为null
future.whenComplete(new BiConsumer<String, Throwable>() {
@Override
public void accept(String t, Throwable action) {
System.out.println(t+" 执行完成!");
}
});
// 出现异常时先执行该方法
future.exceptionally(new Function<Throwable, String>() {
@Override
public String apply(Throwable t) {
System.out.println("执行失败:" + t.getMessage());
return "异常xxxx";
}
});
future.get();
输出结果
执行失败:java.lang.ArithmeticException: / by zero
null 执行完成!
其他方法使用场景
将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果
thenApply
thenApply
接收一个函数作为参数,使用该函数处理上一个CompletableFuture
调用的结果,并返回一个具有处理结果的Future
对象
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
示例:结果为 300
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int result = 100;
System.out.println("第一次运算:" + result);
return result;
}).thenApply(number -> {
int result = number * 3;
System.out.println("第二次运算:" + result);
return result;
});
thenCompose
thenCompose
的参数为一个返回CompletableFuture
实例的函数,该函数的参数是先前计算步骤的结果。
常用方法:
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
CompletableFuture<Integer> future = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(30);
System.out.println("第一次运算:" + number);
return number;
}
})
.thenCompose(new Function<Integer, CompletionStage<Integer>>() {
@Override
public CompletionStage<Integer> apply(Integer param) {
return CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = param * 2;
System.out.println("第二次运算:" + number);
return number;
}
});
}
});
thenApply 和 thenCompose的区别:
thenApply转换的是泛型中的类型,返回的是同一个CompletableFuture;
thenCompose将内部的CompletableFuture调用展开来并使用上一个CompletableFutre调用的结果在下一步的CompletableFuture调用中进行运算,是生成一个新的CompletableFuture。
结果消费
thenAccept()
:对单个结果进行消费thenAcceptBoth()
:对两个结果进行消费thenRun()
:不关心结果,只对结果执行Action
thenAccept
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
CompletableFuture<Void> future = CompletableFuture
.supplyAsync(() -> {
int number = new Random().nextInt(10);
System.out.println("第一次运算:" + number);
return number;
}).thenAccept(number ->
System.out.println("第二次运算:" + number * 5));
thenAcceptBoth
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
CompletableFuture<Integer> futrue1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(3) + 1;
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务1结果:" + number);
return number;
}
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(3) + 1;
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务2结果:" + number);
return number;
}
});
futrue1.thenAcceptBoth(future2, new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer x, Integer y) {
System.out.println("最终结果:" + (x + y));
}
});
thenRun
public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
int number = new Random().nextInt(10);
System.out.println("第一阶段:" + number);
return number;
}).thenRun(() ->
System.out.println("thenRun 执行"));
结果组合
thenCombine
合并两个线程任务的结果,并进一步处理
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor);
CompletableFuture<Integer> future1 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(10);
System.out.println("任务1结果:" + number);
return number;
}
});
CompletableFuture<Integer> future2 = CompletableFuture
.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
int number = new Random().nextInt(10);
System.out.println("任务2结果:" + number);
return number;
}
});
CompletableFuture<Integer> result = future1
.thenCombine(future2, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer x, Integer y) {
return x + y;
}
});
System.out.println("组合后结果:" + result.get());
allOf
allOf方法用来实现多 CompletableFuture 的同时返回
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("future1完成!");
return "future1完成!";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("future2完成!");
return "future2完成!";
});
CompletableFuture<Void> combindFuture = CompletableFuture.allOf(future1, future2);
try {
combindFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
具体completetableFuture异步运算请参考:传送门
函数式接口写法
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口,如果是一个函数式接口,就能够使用Lamda表达式来创建该接口的对象
Runnable接口里边只有一个方法run(),是函数式接口,所以可以使用lambda简化多线程创建
new Thread(()->System.out.println("学习多线程")).start();
线程状态
线程的五大状态(新生,就绪,调度运行,阻塞,死亡)
线程对象一旦创建就进入到了新生状态 Thread th=new Thread();
当调用Start()方法时,线程立即进入了就绪状态,但不意味着立刻执行
cpu进行调度,进入运行状态,进入运行状态,线程才会真正执行线程体的代码块
如果调用了sleep,wait或者同步锁定时,线程会进入阻塞状态,阻塞状态解除后,重新进入就绪状态,等待cpu调度执行
线程中断或者结束,就进入死亡状态 ,一旦进入到死亡状态,就不能再次启动
setPriority(int newPriority) //更改线程的优先级
static void sleep(long millis) //在指定的毫秒数之内让线程休眠
void join()//等待该线程终止
static void yield() //线程礼让,暂停当前正在执行的线程对象,并执行其他线程对象
void interrupt() //中断线程, 不用这个方式
boolean isAlive() //判断线程是否处于活动状态
停止线程
(不推荐使用JDK提供的stop() destroy()方法 已经废弃)推荐让线程自己停下来,建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。
public class TestThread1 implements Runnable{
private boolean flag=true;
/*当flag为false的时候,就没有线程体啦*/
@Override
public void run() {
while(flag){
int i=0;
System.out.println("run.....Thread"+i++);
}
}
/*设置一个公开方法停止线程,装换标志位*/
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestThread1 testThread1 = new TestThread1();
new Thread(testThread1).start();
for (int i = 0; i < 100; i++) {
if(i==90){
/*调用stop方法,切换标志位,停止线程*/
testThread1.stop();
System.out.println("该线程停止了");
}
}
}
}
sleep
需要抛出异常interruptException模拟延时,结束之后进入就绪状态,每个对象都有一把锁,sleep不会释放锁
//模拟倒计时
int num=10;
while(true){
Thread.sleep(1000)
System.out.println(num--);
if(num<=0){
break;
}
}
//打印当前系统时间
Date time=new Date(System.currentTimeMillis());
while(true){
Thread.sleep(1000)
System.out.println(new SimpleDateFormat(HH:mm:ss).format(time));
time=new Date(System.currentTimeMillis());
}
yield
线程礼让,会暂停本线程的执行,礼让其他线程
join
合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞,,就是一个插队线程,我先来的意思
public class TestThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程vip来了");
}
}
public static void main(String[] args) throws InterruptedException {
TestThread1 testThread1 = new TestThread1();
new Thread(testThread1).start();
/*主线程*/
for (int i = 0; i < 100; i++) {
if(i==80){
new Thread(testThread1).join();//在这里进行插队
}
System.out.println("main");
}
}
}
线程的优先级(Priority)
线程调度器会根据优先级决定调用那个线程来执行,线程的优先级使用数字来表示,范围从1到10
通过getPriority().setPriority()来获取和改变优先级
守护线程(daemon)
线程分为用户线程(main())和守护线程,虚拟机必须确保用户线程执行完毕,虚拟机不用等待守护线程执行完毕,如后台记录操作日志,监控内存,垃圾回收gc等
//默认的线程全是用户线程,但是我们可以设置为守护线程
Thread thread=new Thread(god);
thread.setDaemon(true);
线程同步(锁机制)
并发(多个线程操作同一个资源(取钱,买票))解决办法(最天然的排队,一个个来)
多线程在处理问题时,多个线程访问一个对象时,并且线程还想修改这个对象,这个时候我们需要线程同步,线程同步实际上是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程在使用
在访问的时候加上锁机制Synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待锁释放才可以,会存在以下问题
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级较高的线程等待一个优先级较低的线程释放锁, 会导致优先级倒置,引起性能问题
实例:不安全的买票
/*多个线程同时操作一个对象 * 买火车票的例子 * 在下面的实例中,我们发现,多个线程操作同一个数据的时候,线程不安全了,会出现紊乱 * 有的拿到了相同的票,有的拿到了负数的票, * 解决方法(线程同步,并发性问题) * */
public class TestThread1 implements Runnable{
//火车票票数
private int ticketNums=10;
@Override
public void run() {
while(true){
if(ticketNums<=0){
break;
}
/*延时*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args) {
TestThread1 testThread1 = new TestThread1();
new Thread(testThread1,"小明").start();
new Thread(testThread1,"小红").start();
new Thread(testThread1,"小强").start();
}
}
线程不安全集合
/*线程不安全的集合*/
public class Unfaselist {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
//会把两个线程同时添加到一个里边形成覆盖,所以线程不安全
}).start();
}
System.out.println(list.size());
/*结果为765*/
}
}
安全类型集合
java.util.concurrent(juc并发包是安全的)juc里的(lock volatile是保证线程安全的)
public class Unfaselist {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(300);
System.out.println(list.size());
/*运行结果是安全的,java.util.concurrent(juc并发包是安全的)*/
}
}
死锁(互相等待对方释放资源,都停止执行的情况)
某一个同步块同时拥有“两个以上对象的锁的时候”,就可能会发生死锁的问题
在这个例子之中,同步块有两个对象锁,灰姑娘先获得口红的锁,然后白雪公主爱获得镜子的锁,灰姑娘睡了一秒后想拿镜子的锁,被占用,白雪公主睡了一秒后,想拿口红的锁,被占用
/*互相抱着对方所需的资源,互相僵持*/
public class Unfaselist {
public static void main(String[] args) {
Makeup g1=new Makeup(0,"灰姑娘");
Makeup g2=new Makeup(1,"白雪公主");
g1.start();
g2.start();
}
}
class KouHong{}
class Mirror{}
class Makeup extends Thread{
/*需要的资源只有一份,用static来保证只有一份*/
static KouHong kouHong=new KouHong();
static Mirror mirror=new Mirror();
int choose;//选择
String girlName;//使用化妆品的人
Makeup(int choose,String girlName){
this.choose=choose;
this.girlName=girlName;
}
@Override
public void run() {
/*化妆,互相持有对方的锁*/
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if(choose==0){
synchronized (kouHong){//获得口红的锁
System.out.println("获得口红的锁");
Thread.sleep(1000);
synchronized (mirror){//一秒钟后想获得镜子
System.out.println("获得镜子的锁");
}
}
}else{
synchronized (mirror){//获得镜子的锁
System.out.println("获得镜子的锁");
Thread.sleep(1000);
synchronized (kouHong){//一秒钟后想获得口红
System.out.println("获得口红的锁");
}
}
}
}
}
产生死锁的必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时
- 不剥夺条件: 进程已经得到资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
Lock锁
从JDk5.0开启,java提供了更强大的线程同步机制--通过显示定义同步锁对象来实现同步,同步锁使用Lock对象充当
Lock接口是juc并发包下的,ReentrantLock类实现了Lock接口,它与syncnized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
Lock锁只能锁定代码块,Synchronized可以同步方法 Lock锁是显示锁,要手动开启和关闭 ,使用Lock锁,jvm会花费更少的时间来调度线程,性能更好
public void run() {
while(true){
try {
Lock.lock();/*加锁*/
if(ticket<0){
.....
}
}catch(Exception e){
e.printStackTrace();
}finally {
Lock.unlock();//解锁
}
}
}
线程协作
(生产者消费者问题,管程法,信号灯法 线程池)
生产者生产出产品,放入数据缓存区,消费者从数据缓存区去拿,对于生产者而言,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要通知消费者消费,对于消费者,在消费之后,要通知生产者已经消费结束,需要生产新的产品
在生产者与消费者的问题之中,仅有synchronized是不够的,synchronized可阻止并发更新同一个共享资源,实现了同步,synchronized不能用来实现不同线程之间的消费传递(通信),java提供了几个解决线程之间的通信问题。
wait() //表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout)//指定等待的毫秒数
notify() //唤醒一个处于等待状态的线程
notifyAll()//唤醒一个对象上所有调用wait()方法的线程,优先级比别的线程优先调度
对于生产者消费者的线程通信协作的问题(sync只能解决同步的问题)有两种解决方式
- 缓冲区解决办法(管程法)
- 信号灯法
管程法
// 生产者 消费者 缓冲区 产品
public class TestPC {
public static void main(String[] args) {
Container container=new Container();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
Container container;
public Productor(Container container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"只鸡");
container.push(new Chicken(i));
}
}
}
//消费者
class Consumer extends Thread{
Container container;
public Consumer( Container container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了--->"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class Container{
//需要一个容器大小
Chicken []chickens=new Chicken[10];
int count=0;
//生产者放入
public synchronized void push(Chicken chicken){
//如果容器满了,就需要等待消费者进行消费
if(count==chickens.length){
//通知消费者消费,生产等待
}
//如果没有满的话,就要丢入产品
chickens [count]=chicken;
count++;
}
//消费者消费
public synchronized Chicken pop(){
//判断是否能消费
if(count==0){
//等待生产消费者等待
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
return chicken;
}
}
信号灯法
// 信号灯法 通过标志位来解决
public class TestPC {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Wacther(tv).start();
}
}
//生产者--->演员
class Player extends Thread {
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("java");
} else {
this.tv.play("记录世界记录你");
}
}
}
}
//消费者--->观众
class Wacther extends Thread {
TV tv;
public Wacther(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品--->节目
class TV {
//演员表演,观众等待
//观众观看,演员等待
String voice;//表演的节目
Boolean flag = true;//标志位
//表演(表演完了通知观众去看 )
public synchronized void play(String voice) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了"+voice);
//通知观众观看
this.notifyAll();//通知唤醒
this.voice = voice;
this.flag = !this.flag;
}
//观看(看完了,通知演员去演)
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
}
线程池
在线程的使用过程之中,我们经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前穿件好多个线程,放入线程池之中,使用时直接获取,使用完放回池子中,可以避免频繁的创建和销毁,实现重复利用,与连接池的概念相似(队列(先进先出))
使用线程池的好处
- 提高响应的速度(减少了线程创建的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次使用都要去创建)
- 便于线程管理
线程池的7个参数,再生产中尽量不要使用Excutors创建线程,会有oom的风险,可以传入7个参数自定义线程池,有些情况也适用Excutors创建,根据业务需求,性能要求权衡即可
- newFixedThreadPool与newSingleThreadExecutor直接使用的LinkedBlockingQueue ,并且没有声明大小,因此是一种无界阻塞队列。当不停地往线程池中提交任务时,会在队列中堆积无数的任务,可能会造成OOM
- newCachedThreadPool的最大线程数为Integer.MAX_VALUE,如果突然涌入大量的任务,将会瞬间创建大量的线程,也可能会造成OOM
- corePoolSize 核心线程数,或者说常驻线程数,线程池中最少线程数
- maximumPoolSize 最大线程数
- keepAliveTime 空闲线程的存活时间,线程池中当前线程数大于corePoolSize时,那些空闲时间达到keepAliveTime的空闲线程,它们将会被销毁掉
- TimeUnit keepAliveTime的时间单位
- workQueue 任务队列,存放未被执行的任务
- threadFactory 创建线程的工厂
- handler 拒绝策略,当前线程数≥最大线程数且任务队列满的时候,对后续任务的拒绝方式
常用的线程池创建,Executors,这些本质上都是在Executors类中实例化一个ThreadPoolExecutor对象
newFixedThreadPool
创建一个固定大小的线程池,即核心线程数等于最大线程数,每个线程的存活时间和线程池的寿命一致,线程池满负荷运作时,多余的任务会加入到无界的阻塞队列中,newFixedThreadPool可以很好的控制线程的并发量
newCachedThreadPool
创建一个可以无限扩大的线程池,当任务来临时,有空闲线程就去执行,否则立即创建一个线程。当线程的空闲时间超过1分钟时,销毁该线程。适用于执行任务较少且需要快速执行的场景,即短期异步任务。
newSingleThreadExecutor
创建一个大小为1的线程池,用于顺序执行任务
newScheduledThreadPool
创建一个初始大小为corePoolSize的线程池,线程池的存活时间没有限制,newScheduledThreadPool中的schedule方法用于延时执行任务,scheduleAtFixedRate用于周期性地执行任务。
线程池拒绝策略
如果当工作队列已满,且线程数目达到maximumPoolSize后,依然有任务到来,那么此时线程池就会采取拒绝策略,ThreadPoolExecutor中提供了4种拒绝策略
AbortPolicy, 默认,抛出RejectedExecutionException异常
DiscardPolicy 丢弃后续提交的任务,但不抛出异常
DiscardOldestPolicy 此拒绝策略会丢弃队列头部的任务,然后将后续提交的任务加入队列中
CallerRunsPolicy 由调用线程执行该任务,即提交任务的线程,一般是主线程