Java基础--多线程(上)

多线程

  • 什么是线程?

    • 线程是程序执行的一条路径,一个进程中可以包含多个线程。
    • 多线程开发执行可以提高程序的效率,可以同时完成多项工作。
  • 多线程的应用场景

    • 迅雷开启多线程一起下载
    • QQ同时和多个人一起视频
    • 服务器同时处理多个客户端请求

并行和并发

  • 什么是并行?

    并行就是多个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)

  • 什么是并发?

    并发是指多个任务都请求运行,而处理器只能接受一个任务,就把这多个任务安排轮流进行,由于时间间隔非常短,所以看起来感觉是多个任务同时在运行。

    举例说明

    • 快要开学了,小学生们要补作业。由于时间紧迫,所以小学生们就左手写语文作业、右手同时写数学作业,这叫并行。
    • 小学生们都比较爱玩游戏,比如消消乐、刺激战场等。但是只有一台游戏设备,于是玩一下消消乐再赶紧切换到刺激战场打一枪,然后再立刻切换到消消乐消一个冰块,再迅速切换到刺激战场扶队友…循环往复。感觉像是在同时玩两款游戏一样,这叫并发。

Java程序运行原理和JVM的启动是多线程的吗?

  • Java程序运行原理

    • Java命令会启动Java虚拟机,启动JVM等于启动了一个应用程序,也就是启动了一个进程。
    • 该进程会自动启动一个"主线程",然后主线程去调用某个类的main方法。
  • JVM的启动是多线程的吗?

    • JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

多线程实现的三种方式

  • 方式一:继承Thread类创建线程(推荐使用)

      a. 定义类继承Thread
      b. 重写run方法
      c. 把新线程要做的事情写在run方法中
      d. 创建线程对象
      e. 开启新线程,内部会自动执行run方法
    

    代码示例:开启线程的第一种方式

      public class Demo2_Thread {
          public static void main(String[] args){
              MyThread myThread = new MyThread();         //4,创建Thread类的子类对象
              myThread.start();                           //5,执行start方法开启线程
      
              for (int i = 0; i < 1000; i++){
                  System.out.println("bbbbbbbbbbbbbbbbbbb");
              }
          }
      }
      
      class MyThread extends Thread{                      //1,继承Thread类
          @Override
          public void run() {                             //2,重写run方法
              for (int i = 0; i < 1000; i++){             //3,将要执行的代码写在run中
                  System.out.println("啊啊啊啊啊啊啊啊啊啊啊啊");
              }
          }
      }
    

注意:使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。

  • 方式二:实现Runnable接口类创建线程

      a. 定义类实现Runnable接口
      b. 实现run()方法
      c. 把新线程要做的事情写在run方法中
      d. 创建自定义的Runnable子类对象
      e. 创建Thread对象,传入Runnable
      f. 调用start()开启新线程,内部会自动调用Runnable的run()方法
    

    代码示例:开启线程的第二种方式

      public class Demo3_Thread {
          public static void main(String[] args){
              MyRunnable mr = new MyRunnable();         //4,创建Runnable的子类对象
              Thread t = new Thread(mr);                //5,将Runnable的子类对象作为参数传入到Thread的构造方法
              t.start();                                //6,开启线程  
      
              for (int i = 0; i < 2000; i++){
                  System.out.println("yoyoyoyoyoyoyoyoyyooyoy");
              }
          }
      }
      
      class MyRunnable implements Runnable{                  //1,定义一个类实现Runnable接口
          @Override
          public void run() {                             //2,重写run()方法
              for (int i = 0; i < 1000; i++){            //3,将要执行的代码写在run()方法中
                  System.out.println("啊啊啊啊啊啊");
              }
          }
      }
    
  • 方式三:使用Callable和Future创建线程

      a. 创建Callable接口的实现类
      b. 实现call()方法
      c. 把新线程要做的事情写在call()方法中
      d. 创建Callable实现类的实例
      e. 使用FutureTask类来包装Callable对象
      f. 使用FutureTask对象作为Thread对象的target创建并启动新线程
      g. 调用FutureTask对象的get()方法来获得子程序执行结束后的返回值
    

    代码示例:通过实现Callable接口来实现线程类,并启动该线程。

      public class ThirdThread{
      	public static void main(String[] args){
      		//创建Callable对象
      		ThirdThread rt = new ThirdThread();
      		//先使用Lambda表达式创建Callable<Integer>对象
      		//使用FutureTask来包装Callable对象
      		//使用匿名内部类
      		FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
      			for(int i = 0; i < 100; i++){
      				System.out.println(Thread.currentThread().getName() + " 的循环变量i的值: " + i);
      			}
      			//call()方法可以有返回值
      			return i;
      		});
      		for(int i = 0; i < 100; i++){
      			System.out.println(Thread.currentThread().getName() + " 的循环变量i的值: " + i);
      			if(i == 20){
      				//实际上还是以Callable对象来创建并启动线程的
      				new Thread(task,"有返回值的线程").start();
      			}
      		}
      		try{
      			//获取线程返回值
      			System.out.println("子线程的返回值:" + task.get());
      		} catch (Exception e){
      			e.printStackTrace();
      		}
      	}
      }
    

注意:Callable接口有泛型限制,Callable接口里的泛型参数类型与call()方法返回值类型相同。而且Callable接口是函数式接口,因此可以使用Lambda表达式创建Callable对象。

  • 创建线程的三种方式对比

    通过集成Thread类或者实现Runnable、Callable接口都可以实现多线程,不过实现Runnabe接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。因此,可以把实现Runnable接口和实现Callable接口看做一种方式。

    • 采用实现Runnable、Callable接口方式的优缺点:

      • 线程类不仅实现了Runable接口和Callable接口,还可以继承其他类。
      • 多个线程可以共享同一个target对象。因此非常适合多个相同线程共同处理同一份资源的情况,从而将CPU、代码、数据分开,较好地体现了面向对象的思想。
      • 劣势是,程序稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
    • 采用继承Thread类的方式的优缺点:

      • 程序简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
      • 劣势是,因为线程类已经继承了Thread类,所以不能再继承其他父类。

多线程的常用方法

  • 获取名字

    通过getName()方法获取线程对象的名字。

      new Thread(){
          public void run(){
              System.out.println(this.getName() + ".....aa");
          }
      }.start();
    
  • 设置名字

    通过构造函数可以传入String类型的线程对象的名字。

      new Thread("凤姐"){								//向构造方法传入参数
          public void run(){
              System.out.println(this.getName() + ".....aa");
          }
      }.start();
    

    通过setName(String s)方法可以设置线程对象的名字。

      new Thread(){
          public void run(){
              this.setName("张三");					//调用setName方法设置名称
              System.out.println(this.getName() + "aa");
          }
      }.start();
    
  • 获取当前线程的对象

    Thread.currentThread(),主线程也可以获取。

      new Thread(new Runnable(){
          public void run(){
              System.out.println( Thread.currentThread().getName() + "......cc");
          }
      }).start();
    
      Thread.currentThread().setName("我是主线程");              //获取主线程的引用并改名字
      System.out.println(Thread.currentThread().getName());     //获取主线程的引用并获取名字
    
  • 休眠线程

    Thread.sleep(毫秒,纳秒),控制当前线程休眠若干毫秒。

      for (int i = 20; i >= 0; i--){
          Thread.sleep(1000);                 		//休眠一秒
          System.out.println("倒计时" + i + "秒");
      }	
    
  • 守护线程/后台线程

    setDaemon(),设置一个线程为守护/后台线程,该线程不会单独执行。当其他非守护线程执行结束后,该线程自动退出。

      //线程1
      Thread t1 = new Thread(){
          public void run(){
              for (int i = 0; i < 2; i++){
                  System.out.println(this.getName() + ".....aaaaaaaaaa");
              }
          }
      };
      //线程2
      Thread t2 = new Thread(){
          public void run(){
              for (int i = 0; i < 50; i++){
                  System.out.println(this.getName() + ".....bb");
              }
          }
      };
      //设置线程2为守护线程
      t2.setDaemon(true);             //当传入true就是意味着设置为守护线程
      t1.start();
      t2.start();
    
  1. 加入线程

    join,当前线程暂停,等待指定的线程执行结束后,当前线程再继续;join(int),等待指定的毫秒之后再继续当前线程。

     final Thread t1 = new Thread(){           //用final修饰是为了可以让匿名内部类调用
         public void run(){
             for (int i = 0; i < 20; i++){
                 System.out.println("aa");
             }
         }
     };
    
     Thread t2 = new Thread(){
         public void run(){
             for (int i = 0; i < 10; i++){
                 if (i == 2){
                     try {
                         //t1.join();          //方法的匿名内部类中只能调用方法中使用final修饰的局部变量
     					t1.join(1);           //t1插队1毫秒
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 System.out.println("bb");
             }
         }
     };
     t1.start();
     t2.start();
    
  • 礼让线程(知道即可)

    yield,让出cpu。

  • 设置线程的优先级(知道即可)

    setPriority(),设置线程优先级,优先级越高,执行的概率越大。

多线程的同步

多线程编程是一件让人欲罢不能的事情,但是总是容易发生一些意外。这是由于系统的线程调度具有一定的随机性,可能这个线程还没执行完又跑去执行另一个线程,一旦这两个线程共享一个资源,那么意外就发生咯。

  • 什么情况下需要同步?

    • 如果多线程并发,有多段代码同时执行时,我们希望某一段代码执行的过程中CPU不要切换到其他的线程工作,这时就需要同步。
    • 如果两段代码是同步的,那么同一时间只能执行一段,在一段代码没执行结束之前,不会执行另外一段代码。
  • 什么是同步代码块?

    • 使用synchronized关键字加上一个锁对象来定义一段代码,这就叫同步代码块。
    • 多个同步代码块如果使用相同的锁对象,那么他们就是同步的。

同步代码块代码示例:

public class Demo1_Synchronized {
    public static void main(String[] args){
        final Printer p1 = new Printer();
        new Thread(){
            public void run(){
                while (true){
                    p1.print1();
                }
            }
        }.start();
        new Thread(){
            public void run(){
                while (true){
                    p1.print2();
                }
            }
        }.start();
    }
}

class Printer{
    Object o = new Object();
    public void print1(){                   //加了锁对象之后,就不会出现某个方法未执行完,就执行其他线程的情况
        synchronized (o){                   //锁对象是任意的
            System.out.print("老");
            System.out.print("师");
            System.out.print("\n\r");
        }
    }

    public void print2(){
        synchronized (o){                    //锁对象是任意的
            System.out.print("教");
            System.out.print("学");
            System.out.print("生");
            System.out.print("\n\r");
        }
    }
}

注意:synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、成员变量等。

多线程的死锁

当两个线程相互等待对方释放同步监视器的时候就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。

死锁代码示例:

    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();
    }

注意:由于Thread类的suspend()方法也容易导致死锁,所以不推荐使用该方法来暂停线程的执行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值