多线程(1)

一、多线程

  1. 定义
    • 进程
      • 进程是程序执行的一条路径
      • 一个系统级的应用执行的就是一个进程
    • 线程
      • 线程在进程的内部,一个进程中可以有多个线程
      • 多个线程并发执行可以提高程序的效率,可以同时完成多项工作
      • 多线程节约的是执行程序的等待时间,如果程序排列紧密,没有等待时间,多线程就不能真正的提高效率了
  2. 多线程的应该
    • 迅雷下载多个任务
    • 服务器同时处理多个请求
  3. 多线程并发和并行的区别
    • 并发是指两个任务都请求运行,而处理器只能接受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行
    • 并行就是两个任务同时运行,就是甲任务运行的同时,乙任务也在进行(需要多核CPU)
    • 如果一台电脑我先给甲发个信息,然后立刻给乙发消息,然后再跟甲聊,再跟乙聊,这就叫并发
    • 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一个电脑跟乙聊天,这就叫并行
  4. Java虚拟机运行流程
    • java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类中的main方法
    • JVM启动后至少会创建垃圾回收线程和主线程
  5. 思考
    • 四核CPU和双核四线程的CPU哪个效率更高?(四核CPU)

二、Java中多线程的实现方式一

  1. 步骤

    • 继承Thread方式
    • 定义类继承Thread
    • 重写run方法
    • 把新线程要做的事写在run方法中
    • 创建线程对象
    • 调用start()方法开启新线程,内部会自动执行run()方法
  2. 演示

    public class MyThread extends Thread{
    
         @Override-
         public void run() {
             while(true){
                 System.out.println("我是子线程");
             }
         }
    }
    
    public static void main(String[] args) {
         
         MyThread myThread = new MyThread();
         myThread.start();
    }
    
  3. 原理解析

    • 创建多线程肯定要跟系统平台的底层打交道,我们程序猿根本就不知道如何去做,所以,我们仅仅是提供运行代码,至于如何创建多线程全靠java实现
    • 继承Thread的形式,每个Thread的子类对象只能创建一个线程

三、Java中多线程的实现方式二

  1. 步骤

    • 定义类实现Runnable接口
    • 实现run方法
    • 把新线程要做的事写在run方法中
    • 创建自定义的Runnable的子类对象
    • 创建Thread对象,传入Runnable
    • 调用start()开启新线程,内部会自动调取Runnable中的run()方法
  2. 演示

    public class MyRunnable implements Runnable{
    
     @Override
     public void run() {
         while(true){
             System.out.println("我是子线程");
         }
     }
    }
    
    public static void main(String[] args) {
     
     MyRunnable myRunnable = new MyRunnable();
    
     Thread thread = new Thread(myRunnable);
         Thread thread2 = new Thread(myRunnable);
     thread.start();
         thread2.start();    
    }
    
  3. 原理

    • Thread类中定义了一个Runnable类型的成员变量target用来接收Runnable的子类对象
    • 当调用Thread对象的start()方法的时候,方法内首先会判断target是否为null,如果不为null,就调用Runnable子类对象的run方法
    • 多个Thread对象可以共享一个Runnable子类对象

四、多线程两种方式的区别

  1. 查看源码的区别
    • 继承Thread:由于子类重写了Thread类的run(),当调用start()时,直接找子类的run方法
    • 实现Runnable:构造函数中传入了Runnable的引用,成员变量记住了它,start()调用run()方法时内部判断成员变量Runnable的引用是否为空,不为空,编译时看的是Runnable的run(),运行时执行的是子类的run方法
  2. 继承Thread
    • 好处是:可以直接使用Thread类中的方法,代码简单
    • 弊端是:如果已经有了父类,就不能使用这种方法了
  3. 实现Runnable接口
    • 好处是:即使自己定义的线程类有了父类也没有关系,因为有了父类也可以实现接口,而且接口是可以多实现的,多个线程可以非常方便的使用同一个对象的成员变量
    • 弊端是:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂
  4. 思考
    • 当我们调用start()方法之后,是否就意味着线程立即运行呢?(否)
    • CPU的执行权(时间片):如果当前线程被CPU执行的时候,我们称当前线程获取了CPU的执行权
    • 调用start方法之后,意味着当前线程准备好被执行了

五、匿名内部类实现多线程的两种方式

  1. 继承Thread类

    public static void main(String[] args) {
     
     new Thread(){
         public void run(){
             while(true){
                 System.out.println("我是子线程");
             }
         }
     }.start();
     
    }
    
  2. 实现Runnable接口

    public static void main(String[] args) {
     //创建Thread对象,提供Runnable匿名内部类对象
     new Thread(new Runnable(){
         public void run(){
             while(true){
                 System.out.println("我是子线程");
             }
         }
     }).start();
    }
    

六、多线程中获取名字和设置名字

  1. 获取名字

    • 通过getName()方法获取线程对象的名字
    • 此方法只适用于Thread的形式
    • Runnable的形式必须通过获取线程对象来获取名字
  2. 演示

    public class MyThread extends Thread{
    
     @Override
     public void run() {
         
         System.out.println(this.getName());
     
        }
    }
    
  3. 设置名字

    • 通过构造方法传入String类型的名字

      public static void main(String[] args) {
        
         new Thread("线程一"){
             public void run(){
                 
                 System.out.println(this.getName());
             }
         }.start();
      }
      
    • 通过setName方法可以设置线程对象的名字

      public static void main(String[] args) {
         
         Thread thread1 = new Thread(){
             public void run(){
                 
                 System.out.println(this.getName());
             }
             
         };
         thread1.setName("线程一");
         thread1.start();
      }
      

七、获取当前线程的对象名

  1. 定义

    • 我们可以在多线程运行代码中获取代表当前执行线程的对象,并对其进行操作
  2. 演示

    • 通过获取对象的形式在Runnable运行代码中查看当前线程的名称

      public static void main(String[] args) {
         
         new Thread(new Runnable() {
             
             @Override
             public void run() {
                  //获取线程的名称
                 System.out.println(Thread.currentThread().getName());
                 
             }
         }).start();
      
      }
      
    • 通过获取线程对象的形式设置线程的名称

      public static void main(String[] args) {
         
         new Thread(new Runnable() {
             @Override
             public void run() {
                 Thread.currentThread().setName("线程二");//设置线程的名称
                 System.out.println(Thread.currentThread().getName());
             }
         }).start();
      }
      
    • 通过Thread的构造方法设置线程的名称

      public static void main(String[] args) {
         
         new Thread(new Runnable() {
             
             @Override
             public void run() {
                 
                 System.out.println(Thread.currentThread().getName());
                 
             }
         //通过构造方法设置线程的名称
         },"线程二").start();
      
      }
      

八、休眠线程(sleep)

  1. 定义

    • 在线程的执行代码中调用Thread.sleep()方法,就可以将线程休眠若干毫秒
    • 休眠结束的线程进入可运行状态(就绪状态)而不是直接运行
  2. 演示

    public static void main(String[] args) {
      
     new Thread(new Runnable() {
         @Override
         public void run() {
             
             long time1 = System.currentTimeMillis();
             System.out.println(time1);
             
             try {
                 Thread.sleep(10000);//设置线程等待10000毫秒
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             
             long time2 = System.currentTimeMillis();
             System.out.println(time2);
         }
     //通过构造方法设置线程的名称
     }).start();
    
    }
    
  3. 测试题(龟兔赛跑)

    • 需求: 乌龟和兔子赛跑总赛程100m, 兔子的速度是10m/s, 乌龟的速度是5m/s.乌龟和兔子都是每跑完10米输出一次结果, 当兔子跑到70米的时候休息2s ,编程模拟比赛过程

九、守护线程(setDaemon)

  1. 定义

    • 围绕着其他非守护线程运行,该线程不会单独运行,当其他非守护线程都执行结束后,自动退出
    • 调用Thread的setDaemon()方法设置一个线程为守护线程
  2. 应用场景

    • 使用飞秋聊天时,我们打开了多个聊天窗口,当我们关闭主程序时,其他所有的聊天窗口都会随之关闭
  3. 演示

    public static void main(String[] args) {
     
     Thread t1 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 5; i++) {
                 System.out.println("线程一运行中");
             }
         }
     });
     
     Thread t2 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("守护线程运行..........");
             }
         }
         
     });
     
     t2.setDaemon(true);
     t1.start();
     t2.start();
    
    }
    

十、加入线程(join)

  1. 定义

    • 当前线程暂停,等待指定的线程执行结束后,当前线程再继续
  2. 常用方法

    • join()优先执行指定线程
    • join(毫秒) 优先执行指定线程若干毫秒
  3. 演示

    public static void main(String[] args) {
     
     Thread t1 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("线程一运行中");
                 try {
                     Thread.sleep(10);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
         
     });
     
     Thread t2 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("线程二运行中..........");
                 try {
                     t1.join(); //让线程一先执行
                     t1.join(100);//让线程一先执行100毫秒
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 try {
                     Thread.sleep(10);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
         
     });
     
     //只有调用了加入线程的线程才会停止运行,其他线程不受影响
     Thread t3 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("线程三运行中..........");
                 try {
                     Thread.sleep(10);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
         
     });
     
     t1.start();
     t2.start();
     t3.start();
    
    }
    
  4. 注意事项

    • 只有调用了加入线程(也就是join方法)的线程才会停止运行,其他线程不受影响

十一、礼让线程(了解)(yield)

  1. 定义

    • 仅仅让出当前线程的一次执行权
    • 基本看不到效果
  2. 演示

    public static void main(String[] args) {
     
     Thread t1 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("线程一运行中");
             }
         }
         
     });
     
     Thread t2 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("线程二运行中..........");
                 //让出当前线程的执行权
                 Thread.yield();
                 
             }
         }
     });
    
     t1.start();
     t2.start();
    }
    

十二、设置线程的优先级(setPriority)

  1. 定义

    • 每个线程都有优先级,默认都是5,范围是1-10,1表示优先级最低
    • 优先级高的线程在争夺CPU的执行权上有一定的优势,但是表示绝对的
  2. 演示

    public static void main(String[] args) {
     
     Thread t1 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("线程一运行中");
                 
             }
         }
         
     });
     
     Thread t2 = new Thread(new Runnable() {
         
         @Override
         public void run() {
             
             for (int i = 0; i < 50; i++) {
                 System.out.println("线程二运行中..........");
                 
             }
         }
     });
     
     t1.setPriority(1);
     t2.setPriority(10); //线程二的优先级高
     t1.start();
     t2.start();
    }
    

十三、多线程安全问题

  1. 定义

    • 多个线程操作同一个数据时,因为线程执行的随机性,就有可能出现线程安全问题
    • 使用同步可以解决这个问题,把操作数据的代码进行同步,同一时间只能有一个线程操作数据,这样就可以保证数据的安全性
  2. 线程安全问题演示

    static int num =10;
    public static void main(String[] args) {
     
     Thread t1 = new Thread(){
         public void run(){
             while(true){
                 
                 if (num>0) {
                     System.out.println(getName()+":"+num--);
                 }else{
                     break;
                 }
             }
         }
     };
     Thread t2 = new Thread(){
         public void run(){
             while(true){
                 
                 if (num>0) {
                     System.out.println(getName()+":"+num--);
                 }else{
                     break;
                 }
                 
             }
         }
     };
     t1.start();
     t2.start();
    }
    
  3. 解决安全问题

    • 使用synchronized关键字锁定部分代码

      static int num = 10;
      public static void main(String[] args) {
         
         Thread t1 = new Thread(){
             public void run(){
                 while(true){
                     //加上锁
                     synchronized (Class.class) {
                         if (num>0) {
                             System.out.println(getName()+":"+num--);
                         }else{
                             break;
                         }
                     }
      
                 }
             }
         };
         Thread t2 = new Thread(){
             public void run(){
                 while(true){
                     //加上统一把锁
                     synchronized (Class.class) {
                         if (num>0) {
                             System.out.println(getName()+":"+num--);
                         }else{
                             break;
                         }
                     }
                 }
             }
         };
         t1.start();
         t2.start();
      }
      
    • 注意事项

      • 使用同一把锁的代码才能实现同步
      • 没有获取到锁的线程即使得到了CPU的执行权,也不能执行
      • 尽量减少锁的范围,避免效率低下
      • 锁可以加在任意类的代码中或方法上
    • 测试题

      • 火车站总共有100张票, 四个窗口同时卖票, 当票卖光了之后,提示"没票了...",编程模拟场景

十四、死锁

  1. 定义

    • 使用同步的多个线程同时持有对方运行时所需要的资源
    • 多线程同步时,多个同步代码块嵌套,很容易就会出现死锁
    • 锁嵌套的越多,越容易造成死锁的情况
  2. 演示

    • 线程一需要先获取左边的筷子然后获取右边的筷子才能吃饭
    • 线程二需要先获取右边的筷子然后获取左边的筷子才能吃饭
    • 当线程一持有左边的筷子,线程二持有右边的筷子时,相互等待对方释放锁,但又同时持有对方释放锁的条件,互不相让,就会造成无限等待
    private static String s1 = "筷子左";
    private static String s2 = "筷子右";
    
    public static void main(String[] args) {
     new Thread() {
         public void run() {
             while(true) {
                 synchronized(s1) {
                     System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                     synchronized(s2) {
                         System.out.println(getName() + "...拿到" + s2 + "开吃");
                     }
                 }
             }
         }
     }.start();
     
     new Thread() {
         public void run() {
             while(true) {
                 synchronized(s2) {
                     System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                     synchronized(s1) {
                         System.out.println(getName() + "...拿到" + s1 + "开吃");
                     }
                 }
             }
         }
     }.start();
    }
    

总结

  1. 多线程
    • 节省等待时间
    • 平均运行时间
  2. 创建多线程的两种方式
    • 继承Thread
    • 实现Runnable
  3. 常用方法
    • Thread.currentThread()
    • Thread.sleep(毫秒)
    • thread.setDeamon()
    • thread.join()
    • Thread.yield()
    • thread.setPriority(1-10)
  4. 多线程的安全问题
    • 多个线程同时争抢资源,一定会出现问题
    • 同步锁 synchronized
  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

推荐阅读更多精彩内容