美文网首页
3/18day13_线程池_死锁_线程的状态_定时器

3/18day13_线程池_死锁_线程的状态_定时器

作者: 蹦蹦跶跶的起床啊 | 来源:发表于2020-03-18 15:50 被阅读0次

复习

1.synchronized关键字
    a.作用: 控制多行代码的原子性
    b.用法:
            同步代码块:
            synchronized(锁对象){
                需要同步的代码(需要保证原子性的代码)   
            }
            同步方法:
            public synchronized void 方法名(){
                需要同步的代码(需要保证原子性的代码)   
            }
            Lock锁:
            Lock lock = new ReentrantLock();
            lock.lock();
                需要同步的代码(需要保证原子性的代码)   
            lock.unlock();
2.各种高并发情况下使用的线程安全的类  
    ArrayList线程不安全-->CopyOnWriteArrayList线程安全
    HashSet线程不安全 --> CopyOnWriteArraySet线程安全
    HashMap线程不安全--> HashTable(全局锁,性能较低),线程安全
                        ConcurrentHashMap(局部锁+CAS,性能较高)线程安全
    
    Semaphore: 控制线程最多的并发数量
    CountDownLatch: 可以运行一个线程等待另外一个线程执行完毕后再继续执行
    CyclicBarrier: 可以让多个线程到达某种条件之后,再执行其他任务(五个人都到了,再开会)
    Exchanger: 用于两个线程之间数据交换  

今日内容

  • 线程池[重点]
  • 死锁
  • 线程的状态[非常重要]
  • 定时器[重点]

线程池方式

线程池的思想

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

线程池的概念

  • 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

线程池的使用

  • 线程池的顶级接口是: java.util.concurrent.Executor

  • 线程池的子接口:java.util.concurrent.ExecutorService

  • 线程池的工具类:java.util.concurrent.Executors建议使用Executors工程类来创建线程池对象。

  • Executors类中有个创建线程池的方法如下:

    • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
  • 获取到了一个线程池ExecutorService 对象,在这里定义了一个使用线程池对象的方法如下(提交任务):

    • public Future<?> submit(Runnable task):(向线程池提交无返回值的任务,返回值可以看做void)获取线程池中的某一个线程对象,并执行
      Future接口:用来记录线程任务执行完毕后产生的结果。 Future接口中的方法为get()
    • public Future<T> submit(Callable<T> task)(向线程池中提交有返回值的任务),返回Future类型,表示返回封装线程执行完毕之后结果的对象

线程池代码实现

  • 无返回值的
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一个教练");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教练来了: " + Thread.currentThread().getName());
        System.out.println("教我游泳,教完后,教练回到了游泳池");
    }
}

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
        // 创建Runnable实例对象
        MyRunnable r = new MyRunnable();
 
        //自己创建线程对象的方式
        // Thread t = new Thread(r);
        // t.start(); ---> 调用MyRunnable中的run()
 
        // 从线程池中获取线程对象,然后调用MyRunnable中的run()
        service.submit(r);
        // 再获取个线程对象,调用MyRunnable中的run()
        service.submit(r);
        service.submit(r);
        // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
        // 将使用完的线程又归还到了线程池中
        // 关闭线程池
        //service.shutdown();
    }
}
  • 使用Callable 有返回值的
public class ThreadPoolDemo2 {
    public static void main(String[] args) throws Exception {
        // 创建线程池对象
      ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
 
        // 创建Runnable实例对象
        Callable<Double> c = new Callable<Double>() {
            @Override
            public Double call() throws Exception {
                return Math.random();
            }
        };
 
        // 从线程池中获取线程对象,然后调用Callable中的call()
        Future<Double> f1 = service.submit(c);
        // Futur 调用get() 获取运算结果
        System.out.println(f1.get());
 
        Future<Double> f2 = service.submit(c);
        System.out.println(f2.get());
 
        Future<Double> f3 = service.submit(c);
        System.out.println(f3.get());
    }
}

死锁

死锁概念

在多线程程序中,使用了多把锁,造成线程之间相互等待.程序不往下走了。
应该尽量避免死锁

产生死锁的条件

1.有多把锁 2.有多个线程 3.有同步代码块synchronized嵌套
synchronized 获取不到锁, 就会一直等待

避免死锁

  • 加锁顺序保持一致
  • 使用ReentrantLock, 加锁方式可以设置时间,如果在时间内没有获得锁,返回false,可以避免死锁

死锁代码演示

public class Demo05 {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
 
        new Thread(mr).start();
        new Thread(mr).start();
    }
}
 
class MyRunnable implements Runnable {
    Object objA = new Object();
    Object objB = new Object();
 
    /*
    嵌套1 objA
    嵌套1 objB
    嵌套2 objB
    嵌套1 objA
     */
    @Override
    public void run() {
        synchronized (objA) {
            System.out.println("嵌套1 objA");
            synchronized (objB) {// t2, objA, 拿不到B锁,等待
                System.out.println("嵌套1 objB");
            }
        }
 
        synchronized (objB) {
            System.out.println("嵌套2 objB");
            synchronized (objA) {// t1 , objB, 拿不到A锁,等待
                System.out.println("嵌套2 objA");
            }
        }
    }
}

线程的状态[非常重点]

线程的六种状态

  • New(新建状态)
    线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。只有New状态的线程才能开始调用 start().

  • Runnable(可运行)
    线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。处于新建状态的线程调用了t.start()方法

  • Blocked(锁阻塞)
    当一个线程试图获取一个对象锁(线程运行的过程中遇到了同步方法,同步代码块,Lock锁),而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变Runnable状态。

  • Waiting(无限等待)
    一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

    • 线程进入Waiting(无限等待状态)条件
      • 该线程必须先持有锁对象
      • 调用锁对象的wait方法,进入无线等待
      • 进入无线等待之前,会自动释放锁对象
    • 其他线程如何唤醒Waiting状态的线程条件
      • 其他线程先持有锁对象(就是进入无限等待线程释放的那个锁对象)
      • 调用锁对象的notify()方法,唤醒无限等待的线程
      • 被唤醒的无限等待线程,先进入锁阻塞,直到再次持有锁对象才能进入可运行状态
  • TimedWaiting(计时等待)
    同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。

  • Teminated(被终止)
    因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

等待和唤醒代码实现

Object类的方法
public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.此方法包含自动释放锁功能

public class Demo1_wait {
    public static void main(String[] args) throws InterruptedException {
       // 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
        new Thread(() -> {
            try {
 
                System.out.println("begin wait ....");
                synchronized ("") {
                    "".wait();
                }
                System.out.println("over");
            } catch (Exception e) {
            }
        }).start();
    }

public void notify() :(只能唤醒一个线程) 唤醒当前锁对象上等待状态的线程 此方法必须之前等待的锁对象调用.此方法不包含释放锁对象功能, 需要该线程结束后, 才能释放锁对象给唤醒的线程, 唤醒的线程才能继续执行锁对象内部的代码

public class Demo2_notify {
    public static void main(String[] args) throws InterruptedException {
       // 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
        new Thread(() -> {
            try {
 
                System.out.println("begin wait ....");
                synchronized ("") {
                    "".wait();//进入无限等待之前,会自动释放锁对象
                }
                System.out.println("over");
            } catch (Exception e) {
            }
        }).start();
 
        //步骤2:  加入如下代码后, 3秒后,会执行notify方法, 唤醒wait中线程.
        Thread.sleep(3000);
        new Thread(() -> {
            try {
                synchronized ("") {
                    System.out.println("唤醒");
                    "".notify();
                }
            } catch (Exception e) {
            }
        }).start();
    }
}

等待和唤醒注意事项

  • 只有线程进入了无限等待,其他线程调用锁对象.notify() 才有作用,
  • 锁对象.notify() 只能唤醒一个线程(因为该锁对象而进入无限等待的多个线程,当其他线程执行唤醒该线程方法并且释放该锁随想时, 会随机唤醒无限等待的线程中的一个)
  • 锁对象.nofityAll方法可以唤醒多个线程, 谁抢到锁谁执行

定时器

定时器,可以设置线程在某个时间执行某件事情,或者某个时间开始,每间隔指定的时间反复的做某件事情

定时器的使用

java.util.Timer类:线程调度任务以供将来在后台线程中执行的功能。任务可以安排一次执行,或者定期重复执行。

  • 构造方法
    public Timer();构造一个定时器

  • 成员方法
    public void schedule(TimerTask task, long delay);在指定的延迟之后安排指定的任务执行。TimerTask是个抽象类,实现的是Runnable接口, 需要重写run();delay是指定的时间后.
    public void schedule(TimerTask task, long delay, long period);在指定的延迟之后开始该任务,重新执行固定延迟执行的指定任务。period是再过该时间段为周期
    public void schedule(TimerTask task, Date time) 在指定的时间安排指定的任务执行。
    public void schedule(TimerTask task, Date firstTime,long period);从指定的时间开始,对指定的任务执行重复的 固定延迟执行该任务 。

  • 定时器代码演示

public class Test{
    public static void main(String[] args){
       //1.设置一个定时器,2秒后启动,只执行一次
        Timer t = new Timer();
        t.schedule(new TimerTask(){
            @Override
            public void run(){
                for(int i = 10;i >= 0 ; i--){
                    System.out.println("倒数:" + i);
                    try{
                        Thread.sleep(1000);
                    }catch(Exception e){}
                }
                System.out.println("嘭......嘭......");
                //任务执行完毕,终止计时器
                t.cancel();
            }
        },2000);
        
        //2.设置一个定时器,5秒后开始执行,每一秒执行一次
        Timer t2 = new Timer();
        t2.schedule(new TimerTask(){
            @Override
            public void run(){
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                System.out.println(sdf.format(new Date()));
            }
        },5000,1000);
        
        //3.设置一个定时器,在2030年01月01日零时开始执行,每隔24小时执行一次
        Timer t3 = new Timer();
        Calendar c = new GregorianCalendar(2030, 1-1, 1, 0, 0, 0);
        Date d = c.getTime();
        
        t3.schedule(new TimerTask(){
            @Override
            public void run(){
                System.out.println("搜索全盘......");
            }
        },d,1000 * 3600 * 24);
    }
}

今日小结

1.线程池【理解】
    a.怎么创建???
        ExecutorService service = Executors.newFixedThreadPool(int 线程个数);
    b.提交任务
        service.submit(Runnable 任务); //提交无返回值任务
        Future<T> future = service.submit(Callable<T> 任务);//提交有返回值任务
        通过 future.get() 该方法会阻塞,直到线程执行完毕,返回结果
    c.关闭线程池
        service.shutDown();    
           
2.死锁【了解】
     a.多个线程
     b.多把锁
     c.嵌套获取锁
     死锁只能尽量避免
    
3.线程的状态(等待和唤醒机制) 【掌握】   
    a.NEW(新建状态)
    b.RUNNABLE(可运行状态)
    c.TERMINATED(消亡状态)
    d.BLOCKED(锁阻塞状态)
    e.TIMED_WAITING(限时等待状态)
    f.WAITING(无限等待状态)
    
    怎么进入WAITING状态??
        a.当前线程获取锁对象
        b.调用锁对象.wait()方法
        c.进入WAITING之前自动释放锁对象
    其他线程怎么唤醒WAITING的线程??
        a.其他线程持有锁对象
        b.调用锁对象.notify()方法
        c.WAITING的线程就会醒来,先进入BLOCKED状态,直到再次获取到锁对象
    
    需要练习两个相关案例demo05和demo06
    
4.Timer【理解】  
    四个方法
    public void schedule(TimerTask task, long delay); 

    public void schedule(TimerTask task, long delay, long period);

    public void schedule(TimerTask task, Date time);

    public void schedule(TimerTask task, Date firstTime,long period);

相关文章

  • 3/18day13_线程池_死锁_线程的状态_定时器

    复习 今日内容 线程池[重点] 死锁 线程的状态[非常重要] 定时器[重点] 线程池方式 线程池的思想 如果并发的...

  • ThreadPoolExecutor学习笔记

    线程池状态: 高3位表示"线程池状态"低29位表示"线程池中的任务数量" Worker

  • 5. 死锁

    线程死锁 死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序...

  • 使用Thread Pool不当引发的死锁

    简介 多线程锁定同一资源会造成死锁 线程池中的任务使用当前线程池也可能出现死锁 RxJava 或 Reactor ...

  • 定时器线程池(ScheduledThreadPoolExecut

    前言 定时器线程池提供了定时执行任务的能力,即可以延迟执行,可以周期性执行。但定时器线程池也还是线程池,最底层实现...

  • 2022-03-31 线程池的shutdown方法

    生产环境某定时器用了线程池,经常出现问题,定时器执行到线程池的代码处就关闭了,异常如下: 原因是在这里线程池定义为...

  • Java多线程常识

    死锁并发特性线程状态转换基本机制同步与互斥线程池常用工具类(JUC) 死锁 1. 定义 指两个或两个以上的进程在执...

  • 线程池简易实现和线程池源码

    线程池简单实现 源码 ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 ...

  • Java线程池

    线程池 new Thread 弊端 线程池的好处? TreadPoolExecutor 线程池的几种状态 初始...

  • 6. 使用synchronized实现死锁

    死锁定义 死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序...

网友评论

      本文标题:3/18day13_线程池_死锁_线程的状态_定时器

      本文链接:https://www.haomeiwen.com/subject/itzfyhtx.html