Java-进阶:多线程2

目录

一、Lock 接口

二、线程间的通信

三、线程池

四、定时器 Timer

五、多线程和异常

一、Lock 接口

1. 线程锁

  • 是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁
//锁的使用
Lock l = new ReentrantLock(); //创建一个锁对象
l.lock();
try {
    // access the resource protected by this lock
} finally {
    l.unlock();
}
  • LockSynchronize更为灵活的功能:
  • boolean tryLock()仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。

2. 同步弊端

  • 影响效率
  • 如果出现了嵌套锁,容易产生死锁

3. 死锁

  • 死锁:死锁是指两个以上的线程在执行过程中,因为争夺资源而产生的一种相互等待的现象

二、线程间的通信

1. wait()

  • 导致当前线程处于等待状态
  • 只有在其他线程线程中调用了notify()方法或 notifyAll()来唤醒,因为 wait()方法阻塞了线程
  • 在调用 wait方法之前,当前线程必须拥有此对象监视器(锁对象),换句话说,必须在 锁对象 上调用 wait方法(此方法只应该由作为此对象监视器的所有者的线程 来调用)
  • 一旦在锁对象上调用了 wait方法,紧接着:
    • 当前线程放弃 cpu 执行权,并等待
    • 放弃持有的 锁对象

wait()sleep()比较
相同点:使当前线程放弃cpu执行权,处于阻塞状态
不同点

  1. 线程因为 sleep() 方法 处于阻塞状态的时候,不会放弃所持有的锁对象;线程因为wait() 处于阻塞状态的时候,会放弃锁对象
  2. 使用条件sleep() 没有任何特殊条件; 使用 wait() 则必须持有锁,在锁对象上调用**wait()**方法
  3. 唤醒条件sleep() 的唤醒条件是休眠时间结束;wait() 被唤醒,只能是在其它线程中调用了同一个锁对象的 notify() 或者 **notifyAll()**方法

2. nofity()

  • 唤醒在此对象(调用wait的同一个锁对象) 监视器上等待的单个线程;如果所有线程都在此对象上等待,则会选择唤醒其中一个线程
  • 直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程
  • 被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争

3. notifyAll()

  • 唤醒在此对象监视器上等待的所有线程

三、线程池

1. 概述

  • 我们创建一个线程,只能使用一次
  • 线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源
  • 线程池的原理:线程池里每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一次被使用

为什么要使用线程池?

  1. 在 java 中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。
  2. 除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。
  3. 为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
  4. 线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

2. 创建线程池

  • 通常,线程池都是通过 线程池工厂 创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法Executors:线程池创建工厂类
  • public static ExecutorService newCachedThreadPool()
    • 创建一个可根据需要创建新线程 的线程池,但是在以前构造的线程可用时将重用它们 (可变)
    • 对于执行很多短期异步(短但是频繁) 任务的程序而言,这些线程池通常可提高程序性能
    • 如果现有线程没有可用的,则创建一个新线程并添加到池中
    • 终止并从缓存中移除那些已有 60 秒钟未被使用的线程(折中)
  • public static ExecutorService newFixedThreadPool(int nThreads)
    • 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
    • 如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待
  • public static ExecutorService newSingleThreadExecutor()
    • 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程
    • 可保证顺序地执行各个任务
  • 以上方法返回了一个ExecutorService,该对象表示一个线程池,它可以执行 Runable对象代表的线程。

3. 提交任务

  • Runnable 接口
  • Callable 接口类似于 Runnable,用来指定线程的任务。其中的 call() 方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常
  • ExecutorService:线程池类
  • Future **submit**(Callable task):获取线程池中的某一个线程对象,并执行线程中的call()方法
  • Future接口:用来记录线程任务执行完毕后产生的结果
    • get()获取 Future对象中封装的数据结果
  • void shutdown():启动一次顺序关闭,执行以前提交的任务,但 不接受新任务
  • shutdownNow()方法: 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

4. 使用线程池中线程对象的步骤:

  • 创建线程池对象
  • 创建 Runnable 接口/Callable接口 子类对象
  • 提交 Runnable 接口/Callable接口 子类对象
  • 关闭线程池
public class ThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个newCachedThreadPool
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //操作线程池
        //向线程池提交任务
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello, thread pool");
            }
        });

        //Callable接口的使用
        Future<Integer> future = executorService.submit(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                TimeUnit.SECONDS.sleep(3);
                int sum = 0;
                for (int i = 0; i < 10; i++) {
                    sum += i;
                }
                return sum;
            }
        });
        System.out.println(future);
        System.out.println(future.get());
    }
}

四、定时器 Timer

1. 概述

  • 调度定时任务,帮助我们在稍后的时刻执行定时任务。一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务 执行一次,或者 定期重复执行

2. TimerTask 定时任务

  • 定时任务TimerTask对象表示定时任务
  • Timer调度定时任务,定时任务的内容由TimerTaskrun方法 运行决定
  • 在 timer 中所有的定时任务都是运行在同一个线程中
  1. cancel 取消定时任务
  • 如果利用TimerTaskcancel要取消定时任务,在定时任务已经开始运行时, 调用cancel方法,是没有效果
    利用Timercancel方法取消定时任务,其实是终止Timer本身
public class TimerDemo {
    public static void main(String[] args) {
        //定义一个定时器
        // 在timer中所有的定时任务都是运行在同一个线程中
        Timer timer = new Timer(); //默认不是守护线程

        //在定时器上调度,定时任务
        timer.schedule(new MyTimerTask(), 3000);
        timer.schedule(new MyTimerTask(), 1000);

        //调度器重复执行定时任务
        timer.schedule(new MyTimerTask(timer), 0, 2000);
    }
}

class MyTimerTask extends TimerTask {
    Timer timer;
    public MyTimerTask(Timer timer) {
        this.timer = timer;
    }

    @Override
    public void run() {
        //在输出之前,取消定时任务
        //cancel();
        //利用Timer的cancel方法取消定时任务,其实timer的cance方法,是终止Timer本身
        timer.cancel();
        System.out.println("hello timer");
    }
}

五、多线程和异常

  • 在 Thread 类中,不可以抛出编译型异常,但是可以抛出运行时异常
  • 当 运行时异常抛出线程(溢出线程),能不能捕获?

java语言,并不能直接用try-catch代码块捕获,溢出线程的异常,这是java语言实现上的一个遗憾,但是,并不是说,溢出线程的异常开发者就没办法捕获了。—> Thread.UncaughtExceptionHandler

  • static setDefaultUncaughtExceptionHandler,处理所有线程中未捕获的异常(溢出线程的异常)
  • setUncaughtExceptionHandler,处理某一个线程中的未捕获的异常(溢出线程的异常)
public class supplement01 {
    public static void main(String[] args) {
        //我们可以通过设置UncaughtExceptionHandler对象,让该对象捕获溢出线程的异常
        // callback 回调
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                // 这个方法,会在日志模块 -》 将这些异常信息,通过流保存到文件中
                //处理溢出线程的异常
                System.out.println("DefaultUncaughtExceptionHandler");
            }
        });

        //在主线程中捕获异常
        CatchThreadException1 catchThreadException = new CatchThreadException1();
        catchThreadException.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("Thread t" + e.getMessage());
            }
        });

        try{
            //试图在主线程捕获子线程抛出的异常
            catchThreadException.start();
        }catch (Exception e) {
            System.out.println("我捕获到了异常");
        }
    }
}

class CatchThreadException extends Thread {
    @Override
    public void run() {
        //将异常抛出run方法之外,就等价于将该抛出了线程
       throw new RuntimeException("测试抛出线程的异常");
    }
}

class CatchThreadException1 extends Thread {
    @Override
    public void run() {
        //将异常抛出run方法之外,就等价于将该抛出了线程
        throw new RuntimeException("测试抛出线程的异常");
    }
}
# 总结

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。

面试题多多少少对于你接下来所要做的事肯定有点帮助,但我更希望你能透过面试题去总结自己的不足,以提高自己核心技术竞争力。每一次面试经历都是对你技术的扫盲,面试后的复盘总结效果是极好的!如果你需要这份完整版的**面试真题笔记**,只需你多多**支持**我这篇文章。

**[资料领取方式:戳这里免费下载](https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB)**

员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。

面试题多多少少对于你接下来所要做的事肯定有点帮助,但我更希望你能透过面试题去总结自己的不足,以提高自己核心技术竞争力。每一次面试经历都是对你技术的扫盲,面试后的复盘总结效果是极好的!如果你需要这份完整版的**面试真题笔记**,只需你多多**支持**我这篇文章。

**[资料领取方式:戳这里免费下载](https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB)**

![](https://img-blog.csdnimg.cn/img_convert/88f9e118b966487139ca61fa52469025.png)

你可能感兴趣的:(程序员,面试,java,经验分享)