多线程(2)

一、线程间的通信

  1. 定义

    • 多个线程并发执行时,默认情况下CPU是随机切换线程的
    • 如果我们希望他们有规律的执行,就可以使用通信
    • 线程之间也不是直接干预某个线程,也是随机的
  2. 常用方法

    • wait() 让当前线程处于等待状态,并释放锁
    • notify() 唤醒某个等待中的线程
    • notifyAll() 唤醒所有等待中的线程
  3. 演示(两个线程之间的通信)

    public static void main(String[] args) {
         new Thread(){
             private String str;
             @Override
             public void run() {
                 synchronized (Class.class) {
                     while (true) {
                         Class.class.notify();
                         System.out.println("线程一");
                         try {
                             Class.class.wait();
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                 }
             }
         }.start();
         
         new Thread(){
             @Override
             public void run() {
                 synchronized (Class.class) {
                     while (true) {
                         Class.class.notify();
                         System.out.println("线程二");
                         try {
                             Class.class.wait();
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                 }
                 
             }
         }.start();
     }
    
  4. 演示(多个线程之间的通信)

    public static void main(String[] args) {
         new Thread(){
             @Override
             public void run() {
                 synchronized (Class.class) {
                     while (true) {
                         Class.class.notifyAll();
                         System.out.println("线程一");
                         try {
                             Class.class.wait();
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                 }
             }
         }.start();
         
         new Thread(){
             @Override
             public void run() {
                 synchronized (Class.class) {
                     while (true) {
                         Class.class.notifyAll();
                         System.out.println("线程二");
                         try {
                             Class.class.wait();
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                 }
                 
             }
         }.start();
         
         new Thread(){
             @Override
             public void run() {
                 synchronized (Class.class) {
                     while (true) {
                         Class.class.notifyAll();
                         System.out.println("线程三");
                         try {
                             Class.class.wait();
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                     }
                 }
             }
         }.start();
     }
    
  5. 注意事项

    • 线程间的所有通信行为都必须在同步代码块中执行
    • 这些行为都是锁调用的
    • 当一个线程陷入等待,线程会释放掉锁,并且该线程无法动弹,即使被唤醒了,也仅仅表示有了获取锁的机会,只有当真正获取到锁的时候才能继续运行
    • wait方法还有重载的方法,可以传入毫秒值,表示多少毫秒之后当前线程自动唤醒
    • 一个锁只能唤醒被自己锁住的线程
    • 无法在当前同步代码块内操作别的锁
  6. 测试题

    • 需求:小明同学上大学,父亲每次给1000块钱,小明每次花100元,当钱花完了,就打电话给父亲,通知他去银行存钱,编程模拟
    • 思考,为什么wait和notify方法定义在Object中?

二、ThreadLocal

  1. 定义

    • ThreadLocal是线程的本地变量,是一个存储变量的容器,存入到这个容器中的变量可以在线程的任何位置取出
    • ThreadLocal中的变量是使用线程分离的,别的线程无法使用,保证了变量的安全性
  2. 演示

    static ThreadLocal<String> local = new ThreadLocal<>();
    
     public static void main(String[] args) {
         new Thread() {
             @Override
             public void run() {
                 // 在线程的任意位置设置变量
                 local.set("你");
                 method();
             }
         }.start();
    
         new Thread() {
             @Override
             public void run() {
                 local.set("好");
                 method();
             }
         }.start();
         ;
    
     }
    
     public static void method() {
            //可以在当前线程的任意位置获取变量
         System.out.println(local.get());
     }
    

三、互斥锁

  1. 定义

    • 使用ReenTrantLock类代替synchronized关键字,提供了锁定和解锁的方法
    • 提供了更多的操作锁的方法
  2. 常用方法

    • lock() 锁定当前线程
    • unlock() 解锁
    • newCondition() 获取可以操作线程等待和唤醒Condition对象
    • await() 让当前线程陷入等待
    • signal() 唤醒某个被锁定的线程
  3. 演示

    public static void main(String[] args) {
         //创建可以锁定代码的对象
         ReentrantLock lock = new ReentrantLock();
         
         //获取可以操作线程等待唤醒的Condition对象
         Condition condition = lock.newCondition();
         
         new Thread(){
             @Override
             public void run() {
                 lock.lock();
                 for (int i = 0; i < 10; i++) {
                     //唤醒等待的线程
                     condition.signal();
                     System.out.println("线程一");
                     try {
                         //让当前线程等待
                         condition.await();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 
                 
                 lock.unlock();
             }
         }.start();
         
         new Thread(){
             @Override
             public void run() {
                 lock.lock();
                 for (int i = 0; i < 10; i++) {
                     //唤醒等待的线程
                     condition.signal();
                     System.out.println("线程二");
                     try {
                         //让当前线程等待
                         condition.await();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 lock.unlock();
             }
             
         }.start();;
         
     }
    
  4. 注意事项

    • 互斥锁提供了更多的操作线程的方法供我们使用
    • Condition对象允许我们在任意锁内唤醒任意线程
      • 前提是使用同一把锁(同一个ReentrantLock对象)

四、线程的七种状态

  1. 图例

    线程的状态.jpg
  1. 状态分析

    • 初始状态:线程对象创建完成
    • 就绪状态:线程可以被执行
    • 运行状态:线程正在运行中
    • 堵塞状态:线程被休眠
    • 等待队列:线程陷入无限的等待中
    • 锁池状态:线程被唤醒,但是没有获取到锁
    • 死亡状态:线程执行完毕,被关闭
  2. 注意事项

    • 大部分书籍里都讲线程的状态分为5类:初始,就绪,运行,堵塞,死亡
    • 但是我们更愿意将堵塞状态细分出来,因为堵塞,等待队列,锁池完全是不同的性质

五、线程组(了解)

  1. 定义

    • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分组管理,Java允许程序直接对线程组进行控制
    • 提供了一些操作整体的方法,比如设置组中线程的权限,销毁所有线程等
  2. 演示

    public static void main(String[] args) {
         Thread t1 = new Thread();
         Thread t2 = new Thread();
         
         //获取线程的组
         System.out.println(t1.getThreadGroup().getName());
         System.out.println(t2.getThreadGroup().getName());
         
         //创建新的线程组
         ThreadGroup group = new ThreadGroup("新的线程组");
         
         //创建线程的时候确定组,给线程起个新名字
         Thread t3 = new Thread(group , "张三");
         Thread t4 = new Thread(group , "李四");
         
         System.out.println(t3.getThreadGroup().getName());
         System.out.println(t4.getThreadGroup().getName());
         
     }
    

六、线程池(了解)

  1. 定义

    • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中药创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用
    • 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  2. 演示

    public static void main(String[] args) {
         Runnable runnable = new Runnable() {
    
             @Override
             public void run() {
                 System.out.println(Thread.currentThread().getName());
             }
         };
         //创建线程池
         ExecutorService es = Executors.newCachedThreadPool();
         //调用线程池中的线程执行逻辑代码
         es.submit(runnable);
         es.submit(runnable);
         es.shutdown();
     }
    

七、Runtime类

  1. 定义

    • 这是一个单例类,可以运行系统命令
  2. 演示

    public static void main(String[] args) throws IOException {
     Runtime runtime = Runtime.getRuntime();
     
     runtime.exec("shutdown -s -t 300");//300秒后关机
     runtime.exec("shutdown -a"); //取消关机
     
    }
    

八、Timer类

  1. 定义

    • 定时器,可以在指定时间执行任务,可以重复执行
    • 其实就是在指定时间去调用某个方法
    • 任务类必须继承TimerTask类,并且重写run()方法
  2. 常用方法

    • schedule(TimerTask task, Date time) 在指定的时间安排指定的任务执行
    • schedule(TimerTask task, Date time, long period) 在指定的时间开始执行任务,并按照固定的时间长度重复执行
    • schedule(TimerTask task, long period) 在指定的延迟之后安排指定的任务执行
    • schedule(TimerTask task, long delay, long period) 在指定的延迟之后开始执行任务,并按照固定时间长度重复执行
  3. 演示

    public static void main(String[] args) {
         TimerTask task = new TimerTask() {
             
             @Override
             public void run() {
                 System.out.println("赶紧起床了");
             }
         };
         
         Timer timer = new Timer();
         timer.schedule(task, 5000);
     }
    

总结

  1. 线程的通信
    • 让多个线程之间可以相互影响 多个线程公用的空间
    • 我们可以控制线程的等待和唤醒 wait() notify() notifyAll()
    • 这几个方法都必须用在锁的范围内,只对同步代码块有效
    • 当有多个线程处于等待状态时,notify只能随机唤醒一个
    • 锁,只能唤醒被自己锁住的线程
  2. ThreadLocal
    • 线程本地变量
    • 可以存储一个值,线程隔离的,当前线程存放的值,其他线程无法获取
    • 底层原理:一个以线程唯一标识作为key的map集合
  3. 互斥锁
    • synchronized的升级版,提供了更多丰富的方法
    • 目前给我们的直观体现:一个锁可以有多个Condition用来等待唤醒不同的线程
    • 比synchronized功能丰富,但相对的,编码复杂度也有所提升
  4. 线程的状态
    • 新建状态:线程的出生,创建Thread对象
    • 就绪状态:线程可以被执行,但是还没有获取到CPU的执行权
    • 运行状态:线程正在运行中
    • 休眠状态:线程处于停止状态,可以自动醒来,不会释放锁
    • 等待队列:线程陷入永久的等待(也可以中等待一段时间),释放锁,需要外力才能唤醒
    • 锁池状态:等待中的线程被唤醒就会进入锁池状态,这时具备获取锁的资格,在获取锁之前,不能运行
    • 消亡状态:线程运行完毕,结束程序
  5. 线程池
    • 在初始化的时候先创建一批线程存储起来,等使用的时候直接从容器中获取
    • 优点:使用时节约了创建线程的时间(线程的创建时间远远大于一般的对象)
    • 线程池有多种,每种线程池都有其特点
  6. 线程组
    • 多个线程进行分组管理,便于操作
  7. Runtime和Timer
    • Runtime:帮助我们运行操作系统的底层命令
    • Timer:计时器和定时器

作业

  1. 第一题

    • 需求: 创建3个线程, 让着三个线程按顺序执行(比如: 线程一, 线程二, 线程三.......)
  2. 第二题

    • 子线程循环 10 次,接着主线程循环 100 次,接着又回到子线程循环 10 次,接着再回到主线程又循环 100次,如此循环50次,试写出代码
  3. 扩展题

    • 需求: 有四个线程 1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推.........现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式:

      A:1 2 3 4 1 2....

      B:2 3 4 1 2 3....

      C:3 4 1 2 3 4....

      D:4 1 2 3 4 1....

  4. 扩展题

    • 启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到打印到75.
  5. 扩展题

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

推荐阅读更多精彩内容

  • 线程之间的通信:---打印机 分析:两个线程:输入线程和输出线程两个任务:输入任务和输出任务一个数据:要被两个线程...
    半年很快阅读 161评论 0 0
  • 上一篇我们讲了一下多线程的概念。这一篇,我们用一些比较简单的例子,来看看多线程在代码层次上的实现过程。我们现在和之...
    蘑菇Ai布丁阅读 175评论 0 0
  • 在java多线程中,停止线程的方法有两种:Thread.stop()和Thread.interruped() st...
    Carver_c5f0阅读 233评论 0 0
  • Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的synchronized,而...
    脆皮鸡大虾阅读 533评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,523评论 16 22