java的多线程总结

一、Java的多线程

1.1、线程的基本概念

1.1.1、定义:

​ 引入线程:打开计算机中的任务管理器,有很多条目,每一条目对应一个应用程序,这个应用程序我们称之为“进程”,每一个进程都占用CPU资源和内存,在这一个进程中包含多个任务,它们可以“同时”运行,这里的每一个任务称为“线程”

​ 如果将Java的应用程序比做一个进程,那么它包含的多个执行流程就是一个线程

​ 生活中的多线程:你现在正在玩游戏,你可以一边聊天(互喷),你也可以操控游戏,还可以问候队友。玩游戏就是一个进程,你的不同的操作对于游戏本身就是一个单线程,如果你可以同时操作,就是游戏可支持多线程。

​ 进程:是计算机中独立的应用程序,进程是动态,可运行的

​ 程序:是数据描述和操作代码的集合,它是完成某个功能的代码,它是静态的

​ 线程:在进程中运行的单一任务,是进程的子程序

在这里插入图片描述

​ 多线程:一个进程中的多个子任务,在多线程中会出现资源抢占问题,在单核CPU下同一时间点某个进程下只能有一个线程运行。线程与线程之间会互抢资源

CPU资源分配

在这里插入图片描述

​ 电脑可以运行多个应用程序(多程序),在同一时间片,CPU只能执行某一个任务,由于时间片切换非常快,你根本不能察觉会出现“等待”的情况,如果电脑出现“卡死”,你可以认为资源没有获取并正在等待中。

CPU分配资源无规律,每个应用程序会抢占资源,先到先得。

单线程运行流程:程序只有一条运行线路,从开始到结束保持一致

在这里插入图片描述

多线程:可以有多条结束任务,对于那一条先结束无法预知

在这里插入图片描述

多线程的优缺点:

优点:
1、多线程技术使响应速度更快,大多数应用服务器底层都采用多线程;
2、对于占用大量处理时间的任务使用多线程可以提高CPU利用率 ,例如数据备份;
3、多线程提高系统并发操作。
缺点:
1、等候使用共享资源 导致运行速递降低;
2、可能出现 线程死锁;
3、对共有资源的操作不合理导致 出现脏数据;
4、对线程的管理需要额外的开销。

如何创建多线程的程序呢?

方式一:继承Thread类

a、定义一个类继承Thread类,重写run方法

b、创建该类的对象,并调用start方法

public class MyThread extends  Thread {
    @Override
    public void run() {
         for(int  i=0;i<100;i++){
             //获取当前线程名
             System.out.println(this.getName()+"----"+i);
         }
    }
}
 public static void main(String[] args) {
         // 创建线程对象
         MyThread my = new MyThread();
         //开启线程
         my.start();

          for(int i = 0 ;i < 100 ;i ++){
            System.out.println("主线程的 i-----"+i);
        }
        // 结论: 对于多线程之间它们的执行过程会存在资源抢占,谁先获得cpu资源,谁就执行
    }
方式二:实现Runnable接口

a、创建一个类实现一个接口

public class MyThread2 implements Runnable {
    @Override
    public void run() {
        for(int i = 0;i<100;i++){
            //获取当前 线程的线程名
            System.out.println(Thread.currentThread().getName()+"----"+i);
        }
    }
}

b、借助Thread类开启线程

public static void main(String[] args) {

         // 由于 MyThread2 与线程无关联,需要借助线程类完成启动
        // 创建线程需要执行的任务类
         MyThread2 my = new MyThread2();
        //创建线程对象,并开启线程
        Thread th = new Thread(my,"线程A");
        th.start();

        //再启动一个
         Thread th2 = new Thread(my,"线程B");
         th2.start();

问题:以上两种创建线程的区别?

1、继承方式适用于没有直接父类 ,相对简单 ,是单一继承, 而接口的方式目标类既可以继承类还可以实现其他接口

2、Runnable实现方式适用于 资源共享,线程同步情况。

3、Runnable实现方式并不是线程类,而是实现线程的目标类(Target)

补充: 创建线程并非只有以上两种方式,还可以通过匿名内部的方式创建线程和 线程池的方式。

1.2、线程的生命周期

生命周期定义

线程从创建到销毁的整个过程,称为线程生命周期, 好比人的生命周期就是从出生到去世的整个过程中间会经历的过程包括 出生,长大,变老,离开 都是一个人要经历的。

生命周期的阶段

1、新生状态 : 程序创建该线程(实例化对象)

2、就绪状态(可运行状态) : 当线程对象调用start()方法后 ,可以抢占cpu资源,但不会立马运行run方法

3、运行状态: 当抢占到资源后,立马运行run方法

4、阻塞状态: 在运行过程中,线程遇到阻塞事件(线程休眠,wait ,IO操作,join操作等),变为阻塞状态

5、死亡状态: 线程运行完毕,或异常中断 ,此时CPU资源被释放

在这里插入图片描述

1.3、多线程的常用方法和API

java.lang.Thread类中提供了大量的相关的方法

new Thread();

new Thread(name);

new Thread(Runnable,name);

new Thread(Runnable)

常用方法:

getId():获取线程的唯一标识

getName():获取线程名

getPriority():获取线程的优先级:优先级从1-10,min-priority:1 max-priority:10 norm-priority:5 注意说明优先级高的获取到CPU资源的概率越大,并不是一定会优先执行完成

currentThread():获取当前线程的对象引用

getState():获取线程的状态(这是返回线程状态的枚举,NEW:未启动,RUNABLE:可运行,BLOCK:阻塞状态,WAITING:等待状态,TIMED-WAITIN:等待另一个线程执行完)

interrupt():中断这个线程

islnterrupted():返回boolean测试当前线程是否中断

isAlive():该线程是否处于活动状态

isDaemon():判断该线程是否是守护线程

setDaemon():设置该线程是否是守护线程

join() :合并线程,使它变为单线程

sleep(ms):让当前线程休眠 ,休眠时间到了,自动唤醒

yield():让出cpu资源,使当前线程处理可运行状态(可运行状态也随时可以获取cpu资源)

案例1: 测试线程的基本属性

  System.out.println("当前主线程:"+Thread.currentThread().getName());
        System.out.println("主线程id:"+Thread.currentThread().getId());
        //设置主线程的线程级别
        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
        System.out.println("主线程的线程级别:"+Thread.currentThread().getPriority());

          //  创建线程对象
         MyThread my = new MyThread();
         //设置线程名
        my.setName("线程A");
        //设置优先级
        my.setPriority(10);
         my.start();

        MyThread my1 = new MyThread();
        my1.setName("线程B");
        //设置优先级     线程A  获取到资源的概率 大于线程B (大概率线程A优先执行完)
        my1.setPriority(1);
        //新生态
        System.out.println("线程"+my1.getName()+"状态-----"+my1.getState());
        my1.start();
        //可运行状态(就绪)
        System.out.println("线程"+my1.getName()+"状态-----"+my1.getState());


         for(int i = 0;i<100;i++){
             System.out.println("主线程------"+i);
         }

守护线程

案例2:守护线程

线程类型分为两种,一种是用户线程一种是守护线程,用户线程是执行某一个任务的独立代码,守护线程是用于守护用户线程的线程,它的特点是当用户线程执行完毕后守护现在自动结束,当用户线程没有执行完,守护线程也不会停止

操作系统中有守护进程 ,用于操作系统的运行,只有关机进程自动结束,这里守护线程和守护进程类似。

       //创建线程对象
        DaemonThread daemonThread = new DaemonThread();
        //设置该线程为守护线程   守护的是与它并行的线程类 ,当主线程或其他线程执行完毕,守护线程自动结束
       // daemonThread.setDaemon(true);
        System.out.println("是否是守护线程:"+daemonThread.isDaemon());
        daemonThread.start();

        for(int i=0;i<100;i++){
            System.out.println("主线程i------"+i);
        }

活动的线程总数: Thread.activeCount()

线程中断

案例3:关于终止线程

线程终止就是当线程运行时由于满足特定的条件需要停止运行,此时我们需要考虑如何安全的终止线程这里终止线程提供几个方法:

方法1:打标记中断法

线程运行1000,当程序达到500时,终止程序

public class ThreadEnd extends  Thread {
    @Override
    public void run() {
        boolean  isOver=false;
        for(int i = 0 ;i<1000;i++){
            if(i>=500){
                isOver= true;
                return ;
            }
            System.out.println("线程结果i-----------"+i);
        }
        System.out.println("正常结束");
    }

    public static void main(String[] args) {
          ThreadEnd th = new ThreadEnd();
          th.start();
    }
}
方法2:异常中断法

interrupt():给线程打一个中断标记,不会立马中断

interrupted():检测线程是否中断,并清除中断标记,返回boolean,如果线程打标记了,就返回true

isInterrupted():只是检测线程是否中断,但不清除中断标记,返回boolean

注意用法: interrupted() : 它所处于的位置,对应于它作用的位置 ,通过线程类名调用

​ interrupt() 和 isInterrupted() : 使用线程对象调用。

public class Thread1 extends  Thread {
    @Override
    public void run() {
        int i =0;
          while(true){
              System.out.println("线程--------------"+i);
               //判断当前线程是否有中断标记  ,但是不清除中断标记
              if(this.isInterrupted()){
                  // 通过抛出异常或 break
                  System.out.println("当前线程打中断标记,可以停止了");
                  break;
              }
              i++;
          }
    }
}
public static void main(String[] args) throws InterruptedException {
         Thread1 th = new Thread1();
         th.start();
         // 休眠一会儿
        Thread.sleep(2000);
        //给th打中断标记
        System.out.println("打标记");
        th.interrupt(); //给th打标记

    }

3个方法的用法

      //   Thread.currentThread().interrupt();
     //   System.out.println("判断当前线程是否打标记 (清除标记):"+ Thread.interrupted());
        System.out.println("判断线程是否打标记(不清除标记)"+ Thread.currentThread().isInterrupted());
        System.out.println("判断当前线程是否打标记 (清除标记):"+ Thread.interrupted());  // 静态方法

join用法

案例4: join的用法: 合并当前线程 ,使其变为单线程 ,哪个线程调用join方法,就立即将该线程剩下的部分执行完成,再执行其他线程

public class ThreadJoin extends  Thread {
    @Override
    public void run() {
            ThreadJoin2 th = new ThreadJoin2();
            th.setName("线程C");
            th.start();

           for(int i=0;i<100;i++){
               try {
                   th.join();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName()+"-----"+i);
           }
    }
}
    public static void main(String[] args) throws InterruptedException {
         ThreadJoin threadJoin = new ThreadJoin();
        threadJoin.setName("线程A");
         threadJoin.start();


//        ThreadJoin threadJoin2 = new ThreadJoin();
//        threadJoin2.setName("线程B");
//        threadJoin2.start();


         for(int i=0;i<100 ;i++){
               if(i==50){
                   // 合并线程 (threadJoin线程的所有代码合并到 主线程中,先执行threadJoin线程)
                   threadJoin.join();
               }
//               if(i==70){
//                   threadJoin2.join();
//               }
             System.out.println("main---"+i);
         }
    }

sleep用法

案例5:sleep的用法:用于休眠当前线程,休眠结束后自动唤醒继续执行,如果同时有多个线程执行,线程没有同步的情况下,相互休眠不影响,资源被公用。

  public static void main(String[] args) {

for(int i =0;i<10;i++){
            try {
                //让当前线程休眠200毫秒  200毫秒后自动唤醒线程 继续执行
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
        }
class ThreadSleep implements  Runnable{

    @Override
    public void run() {
          for(int i =0;i<100;i++){
              try {
                  Thread.sleep(1000); // 当前线程休眠时 不影响其他线程执行
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println(Thread.currentThread().getName()+"----"+i);
          }
    }
}
        ThreadSleep obj = new ThreadSleep();
        Thread th1 = new Thread(obj , "线程A");
        Thread th2 = new Thread(obj , "线程B");
        th1.start();
        th2.start();

面试题: sleep()和 wait()的区别?

回答:1、都可以让线程阻塞, sleep()在指定的时间内阻塞后会自动醒来并进入就绪状态 ,可以不加锁 ,也可以加
锁执行 ,在线程加锁时,sleep不会释放锁资源。
wait() : 让当前线程处于等待状态,自己不能唤醒自己,必须由其他线程通过notify或notifyAll唤醒该线程,必须在
加锁代码中,且wait的线程会释放锁资源。

yield用法

案例6:yield的用法:出让CPU,让当前线程变为可运行状态,并也可以继续抢占CPU资源

public static void main(String[] args) {
        ThreadYiled th = new ThreadYiled();
        th.start();
          // yield  让出cpu资源
        for(int i = 0;i<100;i++){
                if(i==50){
                    //主线程让cpu
                    System.out.println("让出cpu");
                    Thread.currentThread().yield();

                }
            System.out.println("主线程----"+i);
        }
    }

1.4、线程同步

线程并发场景:

在实际开发中,很多时候会出现多个线程同时访问同一个内存空间(变量)的场景,当它们同时对数据进行变更时,可能会出现数据不安全问题,例如经典的银行取钱案例

假设有一个账户(Account),余额是1100元,有小明和小明的爸爸同时取钱,小明拿着银行卡去ATM机取钱,小明爸爸拿着存折去柜台取钱,他们都需要取1000元,小明在取钱时系统会判断是否余额大于1000,如果大于,可以取钱,由于取钱需要一个过程,此时正好小明的爸爸也对该账户取1000元,由于小明没有完成取钱的操作,卡里的钱还没有及时更新提交,所以小明的爸爸也可以通过系统判断的验证,余额大于1000,小明的爸爸也可以取钱,所以现在可能会出现他们两个人都取出1000元,导致账户数据不完整,这就是线程并发导致的问题

在这里插入图片描述

使用代码模拟场景

1、现有一个账户(卡号,余额)

2、取钱的任务,由于需要同一个账户,这里的任务中有一个相同的账户

同步的解决办法:

1、将需要操作公共资源的代码增加“同步锁”(也叫互斥锁)

语法:

synchronized(对象锁){
    代码块
}

注意这里的对象锁必须满足两个线程是同一个对象(同一把锁)

public void run() {
        System.out.println("开始取钱了");
        // 增加互斥锁,协同步伐  ,这个锁必须是公有的对象
        synchronized(account) {
            //先判断账户余额是否足够
            if (account.getMoney() >= 1000) {
                System.out.println(Thread.currentThread().getName() + "可以取钱");
                System.out.println(Thread.currentThread().getName() + "正在取钱");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //更新账户余额
                account.setMoney(account.getMoney() - 1000);
                System.out.println(Thread.currentThread().getName() +
                        "取到了钱,卡里余额还剩:" + account.getMoney());
            } else {
                System.out.println("抱歉,卡里余额不足,不能取1000元");
            }
        }

        // 以上代码需要将操作通过资源的代码块 增加同步关键字
    }

2、 同步方法

在方法的返回值前面增加 “synchronized” , 此时的锁代表的是当前this对象

public synchronized void  get(){
        //先判断账户余额是否足够
        if (account.getMoney() >= 1000) {
            System.out.println(Thread.currentThread().getName() + "可以取钱");
            System.out.println(Thread.currentThread().getName() + "正在取钱");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更新账户余额
            account.setMoney(account.getMoney() - 1000);
            System.out.println(Thread.currentThread().getName() +
                    "取到了钱,卡里余额还剩:" + account.getMoney());
        } else {
            System.out.println("抱歉,卡里余额不足,不能取1000元");
        }
    }

同步代码块和同步方法的区别:

1、语法不同,同步代码块更灵活,可以自定义锁对象,而同步方法不可以指定锁对象

1.5、线程死锁

线程死锁的产生

​ 线程同步可以帮助我们解决多个线程操作同一个资源而导致数据不完全的问题,但线程同步也有可能产生隐患,加入一个线程中出现多个锁对象时,可能出现锁使用不当,导致锁与锁之间相互等待对方释放资源,从而形成一种“相互等待”的僵局,这就是线程死锁。例如哲学家吃饭

模拟线程死锁

public class DeadThread implements  Runnable {
    Object obj1 = new Object();
    Object obj2 = new Object();
    @Override
    public void run() {
        // 模拟线程死锁
        // 如果当前线程为线程A  先拿到obj1锁   ,等待obj2锁资源
        // 如果当前线程为线程B  先拿到obj2锁  ,等待obj1锁的资源
        if(Thread.currentThread().getName().equals("线程A")){
             synchronized (obj1){
                 System.out.println(Thread.currentThread().getName()+"拿到了obj1的锁");
                 try {
                     Thread.sleep(500);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println("我在等待obj2锁。。。。。");
                 synchronized (obj2){
                     System.out.println("我已经拿到了obj2锁。。。。");
                 }
             }
        }else{
            //线程B
            synchronized (obj2){
                System.out.println(Thread.currentThread().getName()+"拿到了obj2的锁");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我在等待obj1锁。。。。");
                synchronized (obj1){
                    System.out.println("我已经拿到了obj1锁,我和开心");
                }
            }
        }

    }
}
    public static void main(String[] args) {
        DeadThread dead = new DeadThread();
        Thread th1 = new Thread(dead,"线程A");
        Thread th2 = new Thread(dead,"线程B");
        th1.start();
        th2.start();
    }

之所以会发生死锁,因为对象锁之间没有良好的“沟通”,导致互相获取对方的锁,进入等待中,可以通过线程类的几个方法来解决线程之间通信的问题

1.6、线程通信的几个方法

wait():让当前线程处于等待中,会释放对象锁,但是不会自动唤醒,需要其他线程唤醒

notify():唤醒等待中的一个线程(必须保证同一个锁)

notifyAll():唤醒所有等待中的线程

它们都属于Object的方法,需要相同的对象,使用时通过Object的对象调用

注意:以上方法的调用必须满足两个条件:

​ a、它们必须在同步代码块中执行;

​ b、调用该方法的对象是锁对象。

案例1:模拟3个人,张飞、李逵和刘备,来买电影票,售票员只有一张5元的钱,电影票5元钱一张。

  • 张飞拿20元一张的人民币排在李逵和刘备的前面,李逵和刘备各拿了一张5元的人民币买票。
   public class TicketThread implements  Runnable{
      //公共资源:
      int fiveCount=1;
      int twentyCount =0;
    
      @Override
      public void run() {
          //开始买票  如果是张飞,他是20元面值,其他都是5元
          if(Thread.currentThread().getName().equals("张飞")){
              //20元面值买票
              takeTicket(20);
          }else{
              // 5元面值买票
              takeTicket(5);
          }
  
      }
  
      /**
       * 买票过程   给方法加同步  ,锁对象默认是 方法
       * @param money
       */
      public synchronized void  takeTicket(int money){
          if(money ==20){
              // 验证 当前公共资源 中是否有3张5元
               while(fiveCount<3){
                   //等待
                   System.out.println(Thread.currentThread().getName()+"不能买到票,要继续等待");
                   try {
                       this.wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
               // 程序执行这里,说明 fiveCount >=3
              System.out.println(Thread.currentThread().getName()+"正在买票。。");
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              fiveCount-=3;
              twentyCount++;
              System.out.println(Thread.currentThread().getName()+"已经买到了票。。");
  
          }else{
              System.out.println(Thread.currentThread().getName()+"正在买票。。");
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              fiveCount++;
              System.out.println(Thread.currentThread().getName()+"已经买到了票。。");
              // 唤醒等待的线程
              this.notify();
          }
      }
  
  }
    public static void main(String[] args) {
           TicketThread ticketThread = new TicketThread();
           Thread th1 = new Thread(ticketThread,"张飞");
          Thread th2 = new Thread(ticketThread,"李逵");
          Thread th3 = new Thread(ticketThread,"刘备");
          //开启线程
          th1.start();
          th2.start();
          th3.start();
      }

1.7、线程的生产者和消费者模式

多个线程同时运行时,会产生线程并发可使用同步操作确保数据的安全性,如果需要个县城之间交互,可使用线程等待和唤醒模式,在这里常用的等待唤醒模式中经典的模式为“生产者”和“消费者”模式

生产者和消费者由两类线程组成:若干个生产者线程负责提交用户的请求,若干个消费者线程负责处理生成出来的任务。它们操作一块共享内存区进行数据通信。

在这里插入图片描述

生成/消费的产品(数据): Mobile (手机编号)

生成者线程类: Provider : 无限制的生成手机

消费者线程类:Customer : 无限制的消费手机

​ 共享存储区: Storage ( push 、pop) 存储手机的对象数组

​ 测试类

public class Mobile {
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Mobile(int id) {
        this.id = id;
    }
}

存储区


public class Storage {

    // 定义存储手机的对象数据
    Mobile [] mobiles = new Mobile[10];
    int index=0;  // 个数

    static int n=1000;
    /**
     * 存放手机
     * @param mobile
     */
    public synchronized void push(Mobile mobile){
        //考虑容器上限已满,必须等待
        while(index == mobiles.length){
            System.out.println("容器已满,需等待");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //通知消费者取消费 ,将其他线程全部唤醒
        this.notifyAll();
        mobiles[index]=mobile;
        index++;

    }

    /**
     * 取出手机      1 2 3 4
     *              1 2 3
     *              index--;
     *             mobile[index] =null
     * @return 取出的手机对象
     */
    public synchronized Mobile pop(){
        Mobile m = null;

        // 判断index是否小于0
        while(index<=0){
            //等待
            System.out.println("容器中没有手机,需要等待");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        index--;
        m = mobiles[index];
        //将容器中的这个位置 设置为空
        mobiles[index]=null;
        // 通知生产者去生产
        this.notifyAll();
        return m;

    }

    public synchronized int getSize(){
        return index;
    }

}

生产者:


public class Provider implements  Runnable {
    //共享存储区
    Storage storage =null;
    public Provider(Storage storage){
        this.storage = storage;
    }


    @Override
    public void run() {
        //手机编号
        int n=1000;
        //一直生产
        while(true){
            Mobile m = new Mobile(n);
            storage.push(m);
            System.out.println(Thread.currentThread().getName()+
                    "生产了一部手机,其编号:"+m.getId()+" 其库存:"+storage.getSize());

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            n++;
        }
    }
}

消费者

public class Customer implements  Runnable {

    Storage storage=null;
    public Customer(Storage storage){
        this.storage = storage;
    }

    @Override
    public void run() {
        while(true){
            Mobile mobile = storage.pop();
            System.out.println(Thread.currentThread().getName()+
                    "消费了一部手机,编号》》"+mobile.getId()+" 库存:"+storage.getSize());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试类

public class TestProviderCustomer {
    public static void main(String[] args) {
         //创建公有的存储空间
        Storage storage = new Storage();
        Provider provider1 = new Provider(storage);
        Provider provider2 = new Provider(storage);
        Provider provider3 = new Provider(storage);

        Thread th1 = new Thread(provider1,"张飞");
        Thread th2 = new Thread(provider2,"刘备");
        Thread th3 = new Thread(provider3,"关羽");

        th1.start();
        th2.start();
        th3.start();

        //消费者上场
        Customer customer1 = new Customer(storage);
        Customer customer2 = new Customer(storage);
        Customer customer3 = new Customer(storage);

        Thread th4 = new Thread(customer1,"张飞的老婆");
        Thread th5 = new Thread(customer2,"刘备的老婆");
        Thread th6 = new Thread(customer3,"关羽的老婆");

        th4.start();
        th5.start();
        th6.start();


    }
}

1.8、线程池

1.8.1、定义

用于创建和管理线程的容器就是线程池(Thread Pool),在线程池中的线程执行完任务后不会立马进入销毁状态,而是重置到线程池中变为“空闲线程”。有利于避免频繁创建线程消耗资源,提供线程复用率,有限管理该线程。
在这里插入图片描述

1.8.2、使用线程池的原因

在多线程环境下,对于不断创建和销毁效率非常消耗系统资源,对于多线程之间的切换存在线程安全问题, 这是使用统一的管理类管理一些线程是比较好的解决办法

1.8.3、线程的运行机制

  • 在线程池模式下,任务是提交给线程池,由线程池根据当前空闲线程进行分配任务,如果没有空闲线程,由管理类创建线程或者进入任务等待队列中。
  • 一个线程同时只能执行一个任务,但多个任务可以同时提交给这个线程池。

线程池的常用类 (ExecutedService)

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());

参数1: corePoolSize:核心线程数

参数2:maximumPoolSize :最大线程数

参数3: keepAliveTime : 线程活动时长

参数4: 对于参数3的单位

1、可缓存的线程池 newCacheThreadPool(n);如果线程池中没有空闲线程,则创建新线程并放入线程池中,无上限线程数,如果有空闲线程则直接使用该线程

 public static void main(String[] args) {
        // 创建可缓存线程
        ExecutorService service =Executors.newCachedThreadPool();
        // 创建10个线程
        int n=0;
        for(int i = 0 ;i < 10;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            service.execute(new Runnable() {
                @Override  //你们内部类
                public void run() {
                      //任务
                    System.out.println(Thread.currentThread().getName()+"---"+n);
                    try {

                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
            //由于以上的线程 每次执行完成只需要500毫秒 ,会释放线程对象到线程池中
            //  1000毫秒后创建的线程 就会复用上一次的线程对象


        }

        //关闭线程池
        service.shutdown();
    }
}

2、可重用的固定线程池: newFixedThreadPool(n) ,线程数量固定,如果没有空闲线程,则存放无界队列中等待

 public static void main(String[] args) {
         //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(3);
        // 连续创建10个线程, 由于只有3个线程,所有线程只能等待
        // 2秒后再执行3个
        for(int i =0;i<10;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"正在执行");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        // 结果: 每2秒执行3个线程 
    }

3、 固定长度的可执行定时任务的线程池 newScheduledThreadPool , 类似于定时器线程

   scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
                     public void run() {
                         System.out.println("延迟1秒后每3秒执行一次");
                     }
                }, 1, 3, TimeUnit.SECONDS);

4、单线程的线程池 newSingleThreadExecutor : 线程池中只有一个线程数,所有的任务都通过该线程执行,它可以保证所有的任务是FIFO模式, 满足队列结构

二、计算机网络基础

2.1、计算机网络定义

把分布在不同区域的计算机与专门的外部设备通过通信线路连接成复杂的网络系统, 众多计算机之间可以方便的互相传输信息,数据共享。

2.2、计算机网络主要功能:

​ 资源共享

​ 信息传输与集中处理

​ 均衡负荷与分布处理

​ 综合信息服务

计算机之间需要数据传输,离不开网络通信协议,网络通信协议就是 双方在传输数据时的约定

2.3、网络通信协议定义:

​ 计算机在数据传输时的通用标准 。约定了他们的传输速率,传输代码、代码结构,出错控制等标准。

​ 根据国际约束的协议分为网络通信协议的七层协议,按照实际应用也可分为四层协议

​ 七层从下往上 : 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层

​ 四层协议从下往上: 物理+数据链路层 、网络层(IP层)、传输层、应用层
在这里插入图片描述

由于网络传输本身是比较复杂的过程,Java 对每一层进行封装,对每一层提供对应的API ,使我们在进行网络传输时不需要跟踪底层协议,只需对每一层提供的API掌握,同时数据传输的过程和 文件流一样操作,从而简化该过程。 所以网络传输会使用流、多线程的概念。

三、Java的网络编程

Java的网络编程包 java.net.*

网络层: 掌握网络IP 和 端口号 java.net.InetAddress \Inet4Address\Inet6Address

IP的定义: IP(Internet Protocol) 互联网协议, 在全球互联网范围内每一个IP地址表示一台独立的计算机(广域网内) ,IPv4由四个段组成,每一个段的数都从0-255

在这里插入图片描述

通过一个IP地址可以路由到唯一的计算机。

端口号(PORT): 一台计算机上可运行多个应用程序,通过端口号区分该计算机的指定应用程序, 同一个计算机下,不同应用程序,端口号一定不同。 端口号的范围(0-65535)

常用端口号 :

端口号对应服务端口号对应服务
7Echo服务器80(http)/443(https)浏览器默认端口
21ftp8080Tomcat默认端口
23telnet远程连接3306MySql
25SMTP(邮件服务)110POP3
2425内网通端口/飞秋1521Oracle

每一个通信程序都有一个端口号

IP层的类:

java.net. InetAddress

常用方法

getLocalHost() : 获取本地IP对象

getHostAddress():获取IP地址

getHostName():获取计算机名

getAddress():获取ip地址的数组表现形式

static getByName(参数) :通过ip地址或计算机名 返回一个InetAddress对象

      //确定主机名称的IP地址。
        // 获取本机    LocalHost 表示本机ip
        InetAddress inet = InetAddress.getLocalHost();
        System.out.println("主机地址:"+inet.getHostAddress());
        System.out.println("主机名:"+inet.getHostName());
        System.out.println("主机名IP数组:"+
                Arrays.toString( inet.getAddress()));
        // 也可以通过ip地址获取InetAddress对象
        InetAddress inet2= InetAddress.getByName("DESKTOP-346CK63");
        //获取ip地址
        System.out.println(inet2.getHostAddress());
        InetAddress inet3 = InetAddress.getByName("192.168.7.189");
        //获取计算机名
        System.out.println(inet3.getHostName());

        InetAddress inet4 = InetAddress.getByName("192.168.7.103");
        System.out.println("对方的计算机名:"+ inet4.getHostName());
    
    

URL类:

URL全称 统一资源定位符,用于访问互联网上的资源地址,也称为 网址,

完成的URL地址包括以下部分

协议名://ip地址:端口号/文件路径/文件名

例如: http://47.100.182.246:8080/robot/

协议名: http 、https、ftp协议

URL : url的路由对象

URLConnection : url的连接对象, 可获取输入流

常用方法:

getConnect() : 获取连接对象的内容

    • getConnectTimeout()
  URL url = new URL("https://www.baidu.com/");
        // 获取根据ip + 端口 + 协议  + 文件组成URL
      //  URL url2 = new URL("http","47.100.182.246",8080,"/robot");
           //获取连接  (与该地址的连接)
        URLConnection conn =  url.openConnection();
          // 获取这里的资源,首先需要获取输入流 ,下载远程资源相当于IO流操作
        InputStream is = conn.getInputStream();
        // 包装成一个字符流
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        String str=null;
        while((str =br.readLine()) !=null){
            System.out.println(str);
        }

        br.close();

下载文件 案例

public static void main(String[] args) throws IOException {
        //https://zhuanlan.zhihu.com/p/285529011
        // 1、创建连接
        URL url = new URL("https://zhuanlan.zhihu.com/p/285529011");
        System.out.println(url);
        // 2、打开连接
       InputStream is =  url.openStream();
       // 定义文件输出流
        FileOutputStream fos = new FileOutputStream("d:/zhihu.html");
        byte [] b = new byte[1024];
        int len = 0;
        while( (len =is.read(b)) !=-1){
            fos.write(b,0,len);
        }
        System.out.println("文件下载 成功");
        fos.close();
        is.close();



    }

传输层(TCP/UDP)

​ 传输层用于数据的传输,在数据传输过程中根据数据的可靠性可分为两类,

1、基于TCP的传输

​ TCP特点: 两台计算机之间建立可靠连接,基于Socket的通道一旦建立,则数据可通过字节流的方式传输到另一方, 安全的可靠协议 ,

在这里插入图片描述

2、基于UDP的传输

​ UDP特点: 它面向无连接的协议,不保证数据的可靠性,传播速度比较快

TCP和UDP的区别

TCP协议UDP协议
是否面向连接面向连接面向无连接
可靠性可靠不可靠
应用场合适用于数据量较大的文件数据量较少
速度较慢较快

在Java中如何实现TCP传输

核心类: java.net.ServerSocket : 用于接收数据的套接字

​ java.net.Socket :用于发送数据的套接字

套接字是两台计算机建立通信连接的端点 ,在Java对应Socket类

a、实现数据的单行发送和接收

以“客户端”发送数据给“服务端” : 客户端是发送方, 服务端是接收方

       //定义数据的接收方
        ServerSocket server = new ServerSocket(8888);
        // 监听返回套接字的  对象   线程会阻塞在这里,只要有人发送消息,就获取消息对应的端点Socket
        Socket socket = server.accept();
        System.out.println(socket.getInetAddress().getHostAddress()+"发送了消息");
        // 获取接收对象的输入流
        InputStream is = socket.getInputStream();
        byte [] b = new byte[100];
        // 实际读取的长度
        int len = is.read(b);
        String result = new String(b,0,len);
        System.out.println(
                socket.getInetAddress().getHostAddress()+"发送的数据:"+result);



 public static void main(String[] args) throws IOException {
        // 客户端用于发送数据
        //创建Socket对象   并根据ip地址和端口号8888
        Socket socket = new Socket(InetAddress.getByName("192.168.7.189"),8888);
        System.out.println("请输入你要发送的内容:");
        Scanner sc = new Scanner(System.in);
        String msg = sc.next();
        OutputStream os =  socket.getOutputStream();
        os.write(msg.getBytes());
        System.out.println("发送成功。");
    }

四、UDP通信

​ UDP是面向无连接的通信协议, 它负责将数据打包后传输,数据以数据报文的形式存在DatagramPacket中, 不能确保对方是否一定能收到,它发送的速度较快。

java支持UDP编程的核心类

1、java.net.DatagramSocket类 :用于发送和接收包裹

2、java.net.DatagramPacket类: 存取数据包裹

在这里插入图片描述

4.1、点对点单次发送接收

发送方:

接收方:

public static void main(String[] args) throws IOException {
        // 定义接收方
         // 1 、创建接收数据对象
        DatagramSocket ds = new DatagramSocket(8882);
        byte [] b = new byte[1024];
        // 2、创建用于接收的数据包对象
        DatagramPacket dp = new DatagramPacket(b ,b.length);

        // 3、接收数据报包
        ds.receive(dp);
        // 4、接收后数据存放在b数组中 ,转成字符串   ,以接收的实际长度转成字符串
        String msg = new String(b,0,dp.getLength());
        System.out.println("接收来自【"+dp.getAddress().getHostAddress()
                +"】的数据:"+msg);

        //5、关闭
        ds.close();

    }
}

4.2、点对点 一个来回的接收和发送

 public static void main(String[] args) throws IOException {
         // 1、先发送
        DatagramSocket ds = new DatagramSocket();
        System.out.println("请输入你要发送的数据 ");
        Scanner sc = new Scanner(System.in);
        String info= sc.nextLine();
        // 将数据打包
        DatagramPacket dp = new DatagramPacket(info.getBytes(),
                info.getBytes().length, InetAddress.getLocalHost(),8883);
        // 发送
        ds.send(dp);

        //  2、再接收  定义新的接收数据包
        byte [] b = new byte[1024];
        DatagramPacket dp2 = new DatagramPacket(b,b.length);
        ds.receive(dp2);
        //接收的数据
        System.out.println("接收服务端回复的数据:"+new String(b,0,dp2.getLength()));


    }

接收

 public static void main(String[] args) throws IOException {
         // 1 先接收数据
        DatagramSocket ds = new DatagramSocket(8883);
        //  需要定义接收的数组
        byte [] b = new byte[1024];
        DatagramPacket dp = new DatagramPacket(b,b.length);
        //2 接收
        ds.receive(dp);
        System.out.println("接收到来自客户端的数据:"+new String(b,0,b.length));



        // 2、再回复数据(发送出去)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入你要回复的消息:");
        String info = sc.nextLine();
        DatagramPacket dp2 = new DatagramPacket(info.getBytes(),
                info.getBytes().length,
                dp.getAddress(), // 对方的ip对象
                dp.getPort() // 对方的端口,都在接收的包裹对象上
                );
        ds.send(dp2);

    }

4.3、点对点的多次聊天

实现多次来回聊天

  public static void main(String[] args) throws IOException {
        // 1、先发送
        DatagramSocket ds = new DatagramSocket();
        while(true) {
            System.out.println("请输入你要发送的数据 ");
            Scanner sc = new Scanner(System.in);
            String info = sc.nextLine();
            // 将数据打包
            DatagramPacket dp = new DatagramPacket(info.getBytes(),
                    info.getBytes().length, InetAddress.getLocalHost(), 8884);
            // 发送
            ds.send(dp);

            //  2、再接收  定义新的接收数据包
            byte[] b = new byte[1024];
            DatagramPacket dp2 = new DatagramPacket(b, b.length);
            ds.receive(dp2);
            //接收的数据
            System.out.println("接收服务端回复的数据:" + new String(b, 0, dp2.getLength()));

        }
    }

接收

public static void main(String[] args) throws IOException {
        // 1 先接收数据
        DatagramSocket ds = new DatagramSocket(8883);

        while(true) {
            //  需要定义接收的数组
            byte[] b = new byte[1024];
            DatagramPacket dp = new DatagramPacket(b, b.length);
            //2 接收
            ds.receive(dp);
            System.out.println("接收到来自客户端的数据:" + new String(b, 0, b.length));


            // 2、再回复数据(发送出去)
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你要回复的消息:");
            String info = sc.nextLine();
            DatagramPacket dp2 = new DatagramPacket(info.getBytes(),
                    info.getBytes().length,
                    dp.getAddress(), // 对方的ip对象
                    dp.getPort() // 对方的端口,都在接收的包裹对象上
            );
            ds.send(dp2);
        }
    }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值