java多线程

线程概述

一个程序的运行算是一个进程,一个进程有多个线程,线程(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,当一个线程获得对象的排它锁,独占资源,其他线程必须等待锁释放才可以,会存在以下问题

  1. 一个线程持有锁会导致其他所有需要此锁的线程挂起
  2. 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  3. 如果一个优先级较高的线程等待一个优先级较低的线程释放锁, 会导致优先级倒置,引起性能问题

实例:不安全的买票

/*多个线程同时操作一个对象
* 买火车票的例子
* 在下面的实例中,我们发现,多个线程操作同一个数据的时候,线程不安全了,会出现紊乱
* 有的拿到了相同的票,有的拿到了负数的票,
* 解决方法(线程同步,并发性问题)
* */
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只能解决同步的问题)有两种解决方式

  1. 缓冲区解决办法(管程法)
  2. 信号灯法


管程法

// 生产者 消费者 缓冲区 产品
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 由调用线程执行该任务,即提交任务的线程,一般是主线程​​​​​​​

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值