Java多线程

Java多线程

1. 基本概念

程序:是为完成特定任务,用某种语言编写的一组指令集合。即指一段静态代码,静态对象。

进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生,存在和消亡的过程。–生命周期,进程作为资源分配的单位。

线程:进程可以进一步细化为线程,是一个程序内部的一条执行路径,若一个进程同一时间并行执行多个线程,就是支持多线程的。

  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小。
  • 一个进程的多个线程共享相同的内存单元/内存地址空间,他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更加简便,高效。但多个线程操作共享系统资源可能就会带来安全隐患。

2.多线程创建

创建多线程的四种方式:

  • 方式一:继承于Thread类

    1. 创建一个继承Thread类的子类

    2. 重写Thread类的run(),将此线程执行的操作声明在run()方法中

    3. 创建Thread类的子类的对象

    4. 通过子类对象调用start()

           
      class B extends  Thread{   //创建继承Thread类的子类
          @Override
          public void run() {     //重写run方法
            b();
          }
          public void b(){
              for (int i=0;i<100;i++){
                  if(i%2==0){
                      System.out.println(Thread.currentThread().getName() + ":" + i);
                  }
              }
          }
      }
       B b = new B();       //创建子类对象
       b.start();           //对象调用start()  开启线程,执行run()
      

      注意:

      1. 一个子类对象只能开启一次线程调用一次start()方法

                2. start() 作用是开启线程,和执行run()方法,用对象调用run(),不能开启线程
        
  • 方式二:实现Runnable接口

    1. 创建一个实现了Runnable接口的类

    2. 实现类去实现Runnable中的抽象方法:run()

    3. 创建实现类对象

    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

    5. 通过Thread类的对象调用start();

      public class Demo04 {
          public static void main(String[] args) {
              C c = new C();
              Thread thread = new Thread(c);
              thread.start();
          }
      }
      class C implements Runnable{
          @Override
          public void run() {
              for(int i=0;i<100;i++){
                  if(i%2==0){
                      System.out.println(Thread.currentThread().getName()+":" + i);
                  }
             }
          }
      }
      
  • 方式三:实现Callable接口

    ​ 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

    • call()可以拥有返回值
    • call()可以抛出异常,被外面的操作捕获,获取异常信息。
    • call()支持泛型

    创建步骤:

    1. 创建一个Callable的实现类
    2. 实现call方法,将此线程需要执行的操作声明在call()中
    3. 创建Callable接口实现类的对象
    4. 将次Callanle接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
    5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start();
    6. FutureTask对象调用get()获取Callable中call方法的返回值
    public class Demo03 {
        public static void main(String[] args) {
            A a = new A();
            FutureTask futureTask = new FutureTask(a);
            Thread thread = new Thread(futureTask);
            thread.setName("1");
            thread.start();
            try {
                Object o = futureTask.get();        //此返回值为重写Callable方法的返回值
                System.out.println("总和为: "+o);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    class A implements Callable{
        private int sum=0;
        @Override
        public Object call() throws Exception {
            for (int i =1;i<=100;i++) {
                if (i % 2 == 0)
                    {
                        sum += i;
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
            }
            return sum;
        }
    }
    
  • 方式四:使用线程池

    • 背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
    • 思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用类似生活中公共交通工具
    • 好处:提高响应速度(减少了创建新线程的时间),降低资源消耗(不需要每次创建线程),便于线程管理(carePoolSize核心池大小,maximumPoolSize最大线程数,keepAliveTime;线程没有任务时最多保持多长时间)

    创建步骤:

    1. 提供指定线程数量的线程池

    2. 设置线程属性

    3. 执行指定的线程操作,需要提供实现Runnable接口或者Callable接口类型的对象

    4. 关闭连接池

      public class Demo05 {
          public static void main(String[] args) {
              ExecutorService service = Executors.newFixedThreadPool(10);
              ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;  //向下转型为类,接口不能调用非自身方法
      //        service1.setCorePoolSize(15);
              service.execute(new C());   //适合Runnable
              service.submit(new B());   //适合Callable
              service.shutdown();
          }
      }
      class B implements Callable{
          private int sum=0;
          @Override
          public Object call() throws Exception {
              for(int i=0;i<100;i++){
                  if(i%2==0){
                      sum+=i;
                      System.out.println(Thread.currentThread().getName() + ":" + i);
                  }
              }
              return sum;
          }
      }
      class C implements Runnable{
          @Override
          public void run() {
              for(int i=0;i<100;i++){
                  if(i%2!=0){
                      System.out.println(Thread.currentThread().getName() + ":" + i);
                  }
              }
          }
      }
      
    5. 比较方式一和方式二:

      • 开发中优先选择实现Runnable接口的方式
        1. 原因:实现的方式没有类的单继承性的局限性
        2. 原因:实现的方式更适合来处理多个线程有共享数据的情况
    6. 联系:都实现了Runnable接口,两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

    7. 用两种方式实现模拟卖票

      //方式一
      public class Demo05 {
          public static void main(String[] args) {
               D d1 = new D();
               D d2 = new D();
               D d3 = new D();
              d1.setName("一号窗口");
              d2.setName("二号窗口");
              d3.setName("三号窗口");
              d1.start();
              d2.start();
              d3.start();
          }
      }
      class D extends Thread{
          private static int ticket = 100;
          @Override
          public void run() {
              while (true){
                  if(ticket<=0){
                    break;
                  }
                  System.out.println(getName() + "卖票,票号为:" + ticket);
                  ticket--;
              }
          }
      }
      //方式二
      public class Demo06 {
          public static void main(String[] args) {
              E e = new E();
              Thread thread1 = new Thread(e);
              Thread thread2 = new Thread(e);
              Thread thread3 = new Thread(e);
              thread1.setName("一号窗口");
              thread2.setName("二号窗口");
              thread3.setName("三号窗口");
              thread1.start();
              thread2.start();
              thread3.start();
          }
      }
      class E implements Runnable{
          private int ticket = 100;
          @Override
          public void run() {
              while (true){
                  if(ticket<=0){
                      break;
                  }
                  System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
                  ticket--;
              }
          }
      }
      

3. Thread 常用方法

Thread常用方法:

  1. start():启动当前线程,调用当前线程的run();
  2. run(): 将创建的线程要执行的操作声明在此方法中,一般都需要重写此方法
  3. currentThread(): 静态方法,返回执行当前代码的线程
  4. getName(): 获取当前线程的名字
  5. setName(): 设置当前线程的名字
  6. yield(): 释放当前cpu的执行权
  7. join():在线程a中调用b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完之后,线程a才结束阻塞状态
  8. stop():已过时,当执行此方法时,强制结束当前线程
  9. sleep(long millitime):让当前线程睡眠单位毫秒,在时间内线程时阻塞状态
  10. isAlive():判断当前线程是否存活

4. 线程的调度

调度策略:

  • 时间片
  • 抢占式:高级优先级的线程抢占cpu、

Java调度方法:

  • 同优先级线程组成先进先出队列,使用时间篇策略
  • 对高优先级,使用优先调度的抢占式策略

线程的优先级:

  1. NORM_PRIORITY = 5 (默认优先级) MIN_PRIORITY = 1 MAX_PRIORITY = 10

  2. 获取设置优先级

    setPriority(int p);
    getPriority();

  3. 说明:高优先级的线程要抢占低优先级的线程cpu执行权,只是从高概率上讲,高优先级的线程高概率情况下被执行,并不意味只有当高优先级执行完之后,此优先级的线程才执行

5.线程的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGGjvqIi-1610897462431)(C:\Users\www\Desktop\Snipaste_2021-01-17_17-28-35.png)]

6.线程的同步

在Java中,我们通过同步机制,来解决线程安全问题

线程安全问题: 一个线程操作共享数据过程中,尚未操作完成,其他线程参与进来,也操作共享数据

如何解决:一个线程操作共享数据时,在没有完成之前,其他线程不能参与进来,操作此共享数据,即使先进来的线程阻塞了

​ 也不能改变操作顺序。

同步机制实现的两种方式:

  1. 同步代码块:

    synchronized(同步监视器){
        //需要被同步的代码
    }
    

    说明:

    1. 操作共享数据的代码,即为需要被同步的代码。

      2. 共享数据:多个线程共同操作的变量。
         3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁(要求多个线程要共用一把锁)
      

    ​ 补充: 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器

    ​ 继承Thread类创建多线程方式中,我们可以考虑使用类.class充当同步监视器

    //继承Thread类方式创建多线程加锁
    public class Demo02 {
        public static void main(String[] args) {
            D d1 = new D();
            d1.setName("一号窗口");
            D d2 = new D();
            d2.setName("二号窗口");
            D d3 = new D();
            d3.setName("三号窗口");
            d1.start();
            d2.start();
            d3.start();
        }
    }
    class D extends Thread{
        private static int ticket = 100;
        private static Object object = new Object();
        @Override
        public void run() {
            while (true){
                synchronized(object) {
                    if (ticket <= 0) {
                        break;
                    }
                    System.out.println(getName() + "卖票,票号为:" + ticket);
                    ticket--;
                }
            }
        }
    }
    //实现Runnable接口创建多线程方式加锁
    public class Demo01 {
        public static void main(String[] args) {
            E e = new E();
            Thread thread1 = new Thread(e);
            Thread thread2 = new Thread(e);
            Thread thread3 = new Thread(e);
            thread1.setName("一号窗口");
            thread2.setName("二号窗口");
            thread3.setName("三号窗口");
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    class E implements Runnable{
        private int ticket = 100;
        private Object object = new Object();
        @Override
        public void run() {
            while (true){
                synchronized(this) {
                    if (ticket <= 0) {
                        break;
                    } else {
                        System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
                        ticket--;
                    }
                }
            }
        }
    }
    
  2. 同步方法

    如果操作共享数据的代码完整的声明一个方法中,我们不妨将此方法声明同步的

    //继承Thread类方式创建多线程加锁    
    public class Demo02 {
        public static void main(String[] args) {
            D d1 = new D();
            d1.setName("一号窗口");
            D d2 = new D();
            d2.setName("二号窗口");
            D d3 = new D();
            d3.setName("三号窗口");
            d1.start();
            d2.start();
            d3.start();
        }
    }
    class D extends Thread{
        private static int ticket = 100;
        private static Object object = new Object();
        @Override
        public void run() {
            while (true) {
                show();
                if(ticket<=0){
                    break;
                }
            }
        } 
        private static synchronized void show() {           //此方法必须是静态方法,锁是 当前类.class
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
                ticket--;
            }
        }
    }
    //实现Runnable接口创建多线程方式加锁
    public class Demo01 {
        public static void main(String[] args) {
            E e = new E();
            Thread thread1 = new Thread(e);
            Thread thread2 = new Thread(e);
            Thread thread3 = new Thread(e);
            thread1.setName("一号窗口");
            thread2.setName("二号窗口");
            thread3.setName("三号窗口");
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    class E implements Runnable{
        private int ticket = 100;
        private Object object = new Object();
        @Override
        public void run() {
            while (true){
                show();
                if(ticket<=0){
                    break;
                }
            }
        } 
        private synchronized void show(){              //默认锁为 this
            if(ticket>0) {
                System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
                ticket--;
            }
        }
    }
    
    

​ 注意:同步的方式,解决了线程的安全问题–好处

​ 操做同步代码时,只有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低 --缺点

​ 死锁–缺点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值