java多线程&Socket

Posted by lily on April 7, 2023
  1. 程序、进程、线程
  2. 线程的创建
    1. 继承Thread类
    2. 实现Runnable接口
    3. 实现Callable接口
    4. 创建线程池
  3. 线程的同步(解决线程不安全问题)
    1. 同步代码块
    2. 同步方法
    3. Lock锁(多个线程一个🔒)
  4. 线程通信
  5. 经典例子:生产者(producer)消费者(customer)问题

线程的创建

继承Thread类

  1. 要实现多线程的类继承于Thread类
  2. 重写 run 方法
  3. 在主进程里创建子类的实例对象
  4. 每个实例对象都是一条独立的线程
  5. 实例对象调用父类Thread类里的start方法
  6. 该线程开始执行run方法里的操作

    实现Runnable接口

  7. 要实现多线程的类A实现Runnable接口
  8. 重新run方法
  9. 在主进程里实例化一个(根据实际需求)实现类A对象
  10. 实例化多线程,该对象a可以作为Runnable target 放入 Thread类的构造器里实例化Thread对象
  11. 此时由实现类A实现的所有多线程的共用原先的run方法
  12. 由Thread实例化对象调用start方法。

    实现Callable接口

  13. 类A实现Callable接口
  14. 类A重新call方法
  15. 在主程序中实例化一个类A对象a
  16. 对象a作为参数放入FutureTask 构造器里,根据需要线程数量实例化多个FutureTask 类,得到多个实例对象abc…
  17. 把abc…分别放入Thread类的构造器中,实例化多线程Thread类的对象。
  18. 若想获得call方法的返回值,
    public class CallTest {
     public static void main(String[] args) {
         //3.创建Callable接口实现类的对象
         Customer c = new Customer();
    //4.将此Callable实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
         FutureTask future = new FutureTask(c);
         FutureTask futureTask = new FutureTask(c);
    //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法
         Thread t1 = new Thread(future);
         t1.setName("一号");
    
         t1.start();
         new Thread(futureTask).start();
    
          try {
             //6.获取Callable中的call方法的返回值
             //get方法的返回值为重写的call方法的返回值。
             Object sum = futureTask.get();
             System.out.println("总和为:"+sum);
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
     }
    }
    //1.创建一个实现Callable的实现类
    class Customer implements Callable {
    //2.实现call方法,将此线程需要执行的操作声明在call方法中   
      @Override
     public Object call() throws Exception {
         return null;
     }
    }
    

    新增方式:Future接口

  19. 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
  20. FutrueTask是Futrue接口的唯一的实现类
  21. FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

    新增方式:实现Callable接口

    与使用Runnable相比, Callable功能更强大些:

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

    创建线程池

    线程池的优势

  1. 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
  2. 提高系统响应速度,无需等待新线程的创建便能立即执行;
  3. 方便线程管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
  4. 更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池。

    Java线程池相关API

  5. ExecutorService:真正的线程池接口。
    1. void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行
    2. **Runnable Future submit(Callable task)**:执行任务,有返回值,一般又来执行Callable
    3. **void shutdown() **:关闭连接池
  6. Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
    1. ** Executors.newCachedThreadPool()**:创建一个可根据需要创建新线程的线程池
    2. Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
    3. Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    4. Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。
  7. 线程池相关设计参数
    • _corePoolSize:核心池的大小 _
    • _maximumPoolSize:最大线程数 _
    • _keepAliveTime:线程没有任务时最多保持多长时间后会终止 _
    • _ …_ ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;

/**

  • Created by lily via on 2022/10/18 17:11 */

public class theradPoolTest { public static void main(String[] args) { // 1. 获取一个固定大小的线程池 ExecutorService service = Executors.newFixedThreadPool(10); // 2. 实例化要多线程处理的对象 NumCounter n1 = new NumCounter(); // 若实例化了两个对象则两个对象分别拥有一套独立的run方法—>NumCounter n2 = new NumCounter(); // 3. 利用线程池里已造好的线程执行run方法 service.execute(n1); service.execute(n1); // 4. 关闭连接池 service.shutdown(); } }

class NumCounter implements Runnable{ // 共享数据num private int num = 100;

@Override
public void run() {
    while (true) { // 每一轮该线程从if判断语句中出来都要再判断一次--->解决线程不安全问题
        synchronized (this) {
            if (num > 0) {
                if (num % 7 == 0) {
                    System.out.println(Thread.currentThread().getName() + "...num=" + num);
                }num--;
            } else break;
        }
    }
} } ``` <a name="EQ3N9"></a> # 线程的同步 <a name="H5PLf"></a> ## 同步代码块
  1. 使用方式:
    1. synchonized(同步监视器/锁){

需要被同步的代码/多个线程共享需要处理的数据
}

  1. 一旦被同步监视器所包裹,{}内的代码会只允许单线程处理,即未进入同步监视器内的线程视为被阻塞,直到同步监视器内的线程执行完毕,外部的线程才会有机会被cpu分配资源进入运行状态。
  2. synchonized(obj) 使用的锁对象可以为任一所有需要进行同步操作线程所共有的对象
    1. 自己实例化一个对象 例如 Object obj = new Object();
    2. 在 implement Runnable 接口时由于只实例化了一个共享数据类对象,所以可以用本对象来充当同步监视器—-> this关键字即代表本类的实例对象
    3. 在 extends Thread类中由于实例化了多个数据类对象,所以可以用数据类 window1.class来当做同步进程对象。类本身也是一个对象,创建唯一的一个类对象 Class clazz = new Windows(); 新建了一个windows类的类对象。
  3. 使用同步监视器的优缺点:
    1. 优点:保证了处理共享数据时的线程安全
    2. 缺点:在进入锁内时视为单线程运行,效率低
  4. 包裹需要被同步的代码时的注意点:
    1. 不能把共享数据和逻辑代码囊括多了或者是囊括少了,会造成无效同步或者是运行逻辑错误的问题。

      同步方法

  5. 其中的同步监视器也和同步代码块的要求相同
  6. 利用synchonized关键字修饰该方法,该方法内的代码块都会变成同步代码块,握住同步监视器的线程,此时该同步方法时只允许一个线程执行,执行完毕后释放同步监视器。

    Socket Program

    服务器程序的工作过程包含以下四个基本的步骤: **
    调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口
    上。用于监听客户端的请求。
    调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信
    套接字对象。
    调用 该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出
    流和输入流,开始网络数据的发送和接收。
    关闭ServerSocket和Socket对象:**客户端访问结束,关闭通信套接字。