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

复习

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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355