典型线程问题综合演示

一、创建线程的三种方式 

1.继承Thread(Thread类其实是实现了Runnable接口的类)

  1. 继承Thread类后需要命名一个名为run的方法;
  2. 再通过start方法启动线程。

java只能单向继承,继承Thread类耦合性强,不利于扩展。

public class MyThreadTh extends Thread {

    public MyThreadTh() {
    }

    //父类的带参构造方法可以直接执行线程名
    public MyThreadTh(String name) {
        super(name);
    }

    @SneakyThrows
    public void run(){
        int i = new Random().nextInt(10);
        Thread.currentThread().sleep(5000); //当前线程休眠1秒
//        try {
//            Thread.currentThread().wait(5000);//wait()只能放在synchronized block中,而sleep可以在任何地方使用
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println(Thread.currentThread().getName() + "---" + i);
    }


}
private static void testThreadTh() {
        //定义一个流,从seed开始,步长为n+2,截取前10个数进行遍历
        Stream.iterate(0, n->n+1).limit(10).forEach(value -> {
            //定义线程,并指定线程名:1)使用Thread的setName方法; 2)调用父类的带参构造方法
//            new MyThreadTh("extends-thread").start();
            MyThreadTh myThreadTh = new MyThreadTh();
            myThreadTh.setName("thread-"+value);
//            myThreadTh.setDaemon(true);//守护线程在主线程运行结束后,也会结束运行,而非守护线程不会结束
            myThreadTh.start();
//            try {
//                //join会让当前创建的线程所在的线程阻塞
//                myThreadTh.join();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
        });
    }

 2.实现Runnable接口

  1. 通过实现Runnable接口,并重写run方法;
  2. 再把Runnable实例传给Thread对象;
  3. Thread对象调用start方法就相当于调用run方法
public class MyThreadRu implements Runnable {
    @Override
    public void run() {
        int i = new Random().nextInt(900) + 100;
        System.out.println(Thread.currentThread().getName() + "---" + i);
    }
}



private static void testThreadRu() {
        Stream.iterate(0, n->n+1).skip(10).limit(10).forEach(value -> {
            new Thread(new MyThreadRu(), "thread-"+value+": ").start();
        });
}

3.实现Callable 接口

  1. 通过实现Callable接口并重写call方法;
  2. 再把Callable实例传给FutureTask对象;
  3. 再把FutureTask对象传给Thread对象;

Callable 与Thread、Runnable最大的不同是Callable能返回一个异步处理的结果Future对象,并且可以抛出异常,而其他两种则不能。

public class MyThreadCall implements Callable {
    @Override
    public Integer call() throws Exception {
        int i = new Random().nextInt(9000) + 1000;
        Thread.currentThread().sleep(5*1000);
        System.out.println(Thread.currentThread().getName() + "---" + i);
        return i;
    }
}



private static void testThreadCall() {
        List<FutureTask> list = new ArrayList<>();
        Stream.iterate(20, n->n+1).limit(10).forEach(value -> {
            MyThreadCall myThreadCall = new MyThreadCall();
            FutureTask<Integer> futureTask = new FutureTask<Integer>(myThreadCall);
            Thread thread = new Thread(futureTask);
            thread.start();
            list.add(futureTask);
        });

//        list.stream().forEach(futureTask -> {
//            if (futureTask.isDone()){
//                try {
//                    Integer i = (Integer) futureTask.get();
//                    System.out.println("--------> " + i);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                } catch (ExecutionException e) {
//                    e.printStackTrace();
//                }
//            }
//        });
        list.stream().forEach(futureTask -> {
            try {
                Integer i = (Integer) futureTask.get(10, TimeUnit.SECONDS);
                System.out.println("######## :" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }

        });
    }

 二、多线程实现顺序访问

作用:当前线程等待创建的子线程执行结束,也就是t.join()阻塞了主线程直到t线程执行完毕;

private static void testJoin() {
        Thread thread1 = new Thread(() -> {
            Stream.iterate(0, n -> n + 1).limit(10).forEach(value -> {
                System.out.println("===>" + value);
            });
        });
        Thread thread2 = new Thread(() -> {
            Stream.iterate(11, n -> n + 1).limit(10).forEach(value -> {
                System.out.println("===>" + value);
            });
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            System.out.println("-----------------");
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
1.类Thread中方法join()源码如下:
可以看出它是利用wait方法来实现的,当父线程调用子线程t的时候,父线程的方法获取到了子线程t的对象锁,而子线程t调用自身wait方法进行阻塞,只要当t结束或者到时间后才会退出,接着唤醒主线程继续执行。millis为主线程等待t线程最长执行多久,0为永久直到t线程执行结束

public final void join() throws InterruptedException {
        join(0);
}

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
}

 三、两个线程间的数据交换

Exchanger 就是线程之间的数据交换器,只能用于两个线程之间的数据交换;

可以用于多个线程中的两个线程两两交换数据,如果没有对应的线程交换就会一直阻塞,可设置超时,也可以中断。

private static void testExchange() {
        Exchanger<Integer> exchanger = new Exchanger<>();
        Thread t1 = new Thread(() -> {
            int v1 = 8;
            try {
                Integer exchange = exchanger.exchange(v1);
                System.out.println(Thread.currentThread().getName() + ": " + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            int v2 = 10;
            Integer exchange = null;
            try {
                exchange = exchanger.exchange(v2);
                System.out.println(Thread.currentThread().getName() + ": " + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        t1.start();
        t2.start();
    }

 四、统计多个线程的总耗时

 1)join阻塞主线程,不推荐; 2)CountDownLatch计数器, 推荐

CountDownLatch是倒计时器,是多线程并发控制中非常有用的工具类,它可以控制线程等待,直到倒计时器归0再继续执行。如果通过join的形式,假设统计50个线程就要写50个join等待,这显然是不现实的。

1.new CountDownLatch(10)约定了倒计时器的计数数量,在这里也是线程的数量,每个线程执行完后再对倒计时器-1。countDown()是对计数器-1,这个方法需要放在finally中,一定要保证在每个线程中得到释放,不然子线程如果因为某种原因报错倒计时器永远不会清0,则会导报主线程会一直等待。

2.await()方法是主线程阻塞等待,等倒计时器的计数数量归0后,父线程再继续往下执行。await也可以带时间进去,等待设置的超时时间后不管倒计时器的计数数量是否归0,父线程都会继续往下执行。

private static void testCountDownLatch() {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        long start = System.currentTimeMillis();
        Stream.iterate(0, n->n+1).limit(10).forEach(value -> {
            Thread t = new Thread(()->{
                try {
//                    Thread.currentThread().sleep(3000);
                    System.out.println(Thread.currentThread().getName() + ": " + value);
                }catch (Exception e){
                    e.printStackTrace();
                }finally{
                    countDownLatch.countDown();
                }
            }, "Thread-"+value);
            t.start();
        });
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start) + " ms");
    }

 五、fork/join框架

 将一个任务拆分成多个子任务,最后合并子任务结果:fork/join框架(Java7提供的并行执行任务框架);

Fork/Join框架是Java7提供的并行执行任务框架,思想是将大任务分解成小任务,然后小任务又可以继续分解,然后每个小任务分别计算出结果再合并起来,最后将汇总的结果作为大任务结果。其思想和MapReduce的思想非常类似。对于任务的分割,要求各个子任务之间相互独立,能够并行独立地执行任务,互相之间不影响。

工作窃取算法
ForkJoin采用了工作窃取(work-stealing)算法,若一个工作线程的任务队列为空没有任务执行时,
便从其他工作线程中获取任务主动执行。为了实现工作窃取,在工作线程中维护了双端队列,
窃取任务线程从队尾获取任务,被窃取任务线程从队头获取任务。这种机制充分利用线程进行并行计算,
减少了线程竞争。但是当队列中只存在一个任务了时,两个线程去取反而会造成资源浪费。

这里需要计算结果,所以任务继承的是RecursiveTask类。ForkJoinTask需要实现compute方法,
在这个方法里首先需要判断任务是否小于等于阈值1000,如果是就直接执行任务。否则分割成两个子任务,
每个子任务在调用fork方法时,又会进入compute方法,看看当前子任务是否需要继续分割成孙任务,
如果不需要继续分割,则执行当前子任务并返回结果。使用join方法会阻塞并等待子任务执行完并得到
其结果。

private static void testMyForkJoin() {
        long start = System.currentTimeMillis();
        long max                  = 10 * 100000000;
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> submit = forkJoinPool.submit(new MyForkJoin(0, max));
        try {
            Long aLong = submit.get();
            System.out.println("recursiveTask计算结果为" + aLong + " 耗时:" + (System.currentTimeMillis() - start));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }



public class MyForkJoin extends RecursiveTask<Long> {
    private long start;
    private long end;
    private long THRESHOLD = 1000;

    public MyForkJoin() {
    }

    public MyForkJoin(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long sum = 0;
        if ((end - start) <= THRESHOLD){
            for(; start<=end; start++){
                sum = sum + start;
            }
            return sum;
        }
        //一分为二,分别计算,然后聚合结果进行返回
        long mid = start + (end - start)/2;
        ForkJoinTask<Long> fork = new MyForkJoin(start, mid).fork();
        ForkJoinTask<Long> fork1 = new MyForkJoin(mid + 1, end).fork();
        return fork.join() + fork1.join();
    }
}

 Fork/Join框架主要由子任务、任务调度两部分组成;

  • ForkJoinPool

ForkJoinPool是ForkJoin框架中的任务调度器,和ThreadPoolExecutor一样实现了自己的线程池,提供了三种调度子任务的方法:

  1. execute:异步执行指定任务,无返回结果;

  2. invoke、invokeAll:异步执行指定任务,等待完成才返回结果;

  3. submit:异步执行指定任务,并立即返回一个Future对象;

  • ForkJoinTask

Fork/Join框架中的实际的执行任务类,有以下两种实现,一般继承这两种实现类即可。

  1. RecursiveAction:用于无结果返回的子任务;

  2. RecursiveTask:用于有结果返回的子任务;

并行的时间损耗明显要少于串行的,这就是并行任务的好处。

尽管如此,在使用Fork/Join时也得注意,不要盲目使用。

  1. 如果任务拆解的很深,系统内的线程数量堆积,导致系统性能性能严重下降;

  2. 如果函数的调用栈很深,会导致栈内存溢出;

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值