首页 > 编程语言 >初识Java多线程

初识Java多线程

时间:2024-07-30 09:41:05浏览次数:17  
标签:Runnable Java target 任务 初识 线程 new 多线程 pool

Java中如何创建新线程?

第一种方式:继承Thread类

  1. 写一个子类继承Thread
  2. 重写run方法
  3. 创建该类的对象,代表一个线程
  4. 调用start方法启动线程,该线程会执行run方法

这种方式的优点在于编码方式简单,但是该类已经继承了Thread类,不能继承其他类。

注意:

  1. 启动线程时一定调用start方法,而非run方法(直接调用run方法会被当成是普通方法执行,只有调用start方法才会启动一个新线程)
  2. 不能把主线程的任务放在启动子线程的语句之前(要先启动子线程,否则主线程会先跑完,相当于单线程执行)

第二种方式:实现Runnable接口

  1. 定义一个实现类实现Runnable
  2. 重写run方法
  3. 实例化该实现类任务对象Runnable target = new MyRunnable();
  4. 把任务对象交给一个线程对象处理并调用Thread对象的start方法new Thread(target).start();

这种方式的优点在于任务类只是实现接口,还是可以继续继承其他类、实现其他接口的,扩展性强。

可以使用匿名内部类的写法简化代码的写法。

// 写法一
Runnable target = new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            System.out.println("子线程输出"+ i);
        }
    }
};
new Thread(target).start();

// 写法二(简化一)
new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            System.out.println("子线程输出"+ i);
        }
    }
}).start();

// 写法三(简化二)
new Thread(() -> {
    for (int i = 0; i < 5; ++i) {
        System.out.println("子线程输出"+ i);
    }
}).start();

前述两种方式都存在问题:线程的任务执行完毕后无法直接返回数据,这存在局限性。jdk5.0提供了Callable接口和FutureTask类来解决这个问题。

第三种方式:使用Callable接口和FutureTask类

  1. 定义一个类实现Callable<>接口,重写call方法,定义要封装的任务以及要返回的数据
  2. 创建一个Callable对象,封装成FutureTask<>任务对象
  3. 把任务对象交给Thread线程对象,启动子线程
  4. 要获取结果时,调用任务对象的get方法

FutureTask对象是一个任务对象,实现了Runnable接口,它的作用在于,在线程执行完毕之后,调用实例的get方法可以获取任务执行结果。

这种方式的优点在于扩展性强,而且能够获得子线程执行的结果,不过编码比较复杂。

Java中的Thread类提供了针对线程对象的一些常用api,供使用者调用。

方法签名 描述
public String getName() 当前线程的名字,默认名字是Thread-索引
public void setName(String name) 为线程设置名字
public static Thread currentThread() 获取当前执行的线程对象
public static void sleep(long time) 让当前执行的进程休眠毫秒
public final void join() 当前线程阻塞直到调用join方法的线程执行完毕

线程安全问题

线程安全问题出现的原因是存在同时执行的多个线程同时访问(修改)同一个共享资源。线程同步可以用来解决线程安全问题。线程同步(加锁)的方式有三种。

第一种方式:同步代码块

把访问共享资源的代码上锁,每次只允许一个线程进入,执行完毕后自动解锁。

synchronized (锁) {
    // 访问共享资源的代码
}

建议用共享资源作为锁,比如:如果代码块在实例方法中,建议使用this作为锁;如果代码块在类的静态方法中,建议使用类名.class作为锁

第二种方式:同步方法

把访问共享资源的方法整个上锁,这种方式不需要显式指定锁(实例方法默认使用this,静态方法使用类名.class作为锁),锁的范围是整个方法代码

修饰符 synchronized 返回值类型 方法名(形参列表) {
    // 访问共享资源的代码
}

这种方式与第一种方式相比,同步代码块的方式加锁范围更小更灵活,性能比较好,但是同步方法的代码可读性更好

第三种方式:Lock锁

Lock锁是jdk5开始提供的操作,可以创建出锁对象进行加解锁,更加灵活方便。注意Lock是接口,可以使用实现类比如ReentrantLock来实例化Lock对象

// 类内声明
privat final Lock lck = new ReentrantLock();

// 使用try-catch-finally加解锁
try {
    lck.lock();
    // 业务代码,访问共享资源
} catch (Exception e) {
    // 处理异常
} finally {
    lck.unlock();
}

线程间通信和线程池

线程通信,是指多个线程共同操作共享资源时,不同线程之间通过某种方式互相通信,以相互协调

线程池,是一种可以复用线程的技术。为什么需要使用线程池以及复用线程?web应用中,用户每发起一个请求,后台就需要创建一个新线程来处理。创建新线程的开销很大,同时请求过多时会产生大量线程,会严重影响系统性能。

线程池分为两个部分:工作线程WorkThread(也叫核心线程)和任务队列WorkQueue。线程池可以指定创建一定数量的工作线程,用于处理任务队列中等待处理的任务,一个线程在处理完当前任务之后,可以接着处理队列中正在等待处理的任务。为了避免系统资源耗尽,可以限制核心线程的数量和任务队列的大小。注意,任务队列中的对象一定要实现Runnable和Callable接口,才能称为任务对象。

创建线程池第一种方式:使用ExecutorService接口的一个实现类ThreadPoolExecutor创建线程池对象

public ThreadPoolExecutor(int corePoolSize,
                         int maximumPoolSize,
                         long keepAliveTime,
                         TimeUnit unit,
                         BlockingQueue<Runnable> workQueue,
                         ThreadFactory threadFactory,
                         RejectedExecutionHandler handler)
参数 描述
corePoolSize 核心线程的数量
maximumPoolSize 最大线程数量,两个参数之差表示可以创建的临时线程的数量
keepAliceTime 临时线程的存活时间
unit 临时线程存活时间单位(秒分时天)
workQueue 线程池的任务队列
threadFactory 线程池的线程工厂,用来创建线程
handler 任务拒绝策略(线程均忙且任务队列已满的情况下如何处理新任务)
ExecutorService pool =
    new ThreadPoolExecutor(3, 5, 8,
                          TimeUnit.SECONDS,
                          new ArrayBlockingQueue<>(4),
                          Executors.defaultThreadFactory(),
                          new ThreadPoolExecutor.AbortPolicy());

临时线程创建时机:新任务提交时,核心线程都忙,任务队列已满,且此仍可以创建临时线程。拒绝新任务的时机:核心线程和临时线程都忙,且任务队列已满,新任务再提交时。

线程池处理Runnable对象/任务:

// 先定义类实现Runnable接口,重写run方法

Runnable target = new MyRunnable();
pool.execute(target);  // 线程池自动创建新线程,自动处理任务,自动执行
pool.execute(target);
pool.execute(target);  // 核心线程用满
pool.execute(target);  // 在任务队列中等待,复用核心线程
pool.execute(target);
pool.execute(target);
pool.execute(target);  // 任务队列满了
pool.execute(target);  // 开始使用临时线程
pool.execute(target);  // 临时线程用完
pool.execute(target);  // 开始拒绝新任务,这里由于AbortPolicy会抛出异常

pool.shutdown();  // 等待任务执行完毕后关闭线程池
// pool.shutdownNow();  // 直接关闭

线程池处理Callable对象/任务:

// 先定义类实现Callable<>接口,重写call方法

Future<String> f1 = pool.submit(new MyCallable(100));  // execute执行Runnable任务,submit执行Callable任务
Future<String> f2 = pool.submit(new MyCallable(200));

// 调用get方法获取执行结果
String res1 = f1.get();
String res2 = f2.get();

第二种方式:使用线程池的工具类Executors调用静态方法返回不同特点的线程池对象

方法 描述
newFixedThreadPool(int nThreads) 创建固定线程数量的线程池,如果某个线程因为异常而结束,线程池会补充一个新线程替代
newSingleThreadExecutor() 创建只有一个线程的线程池,如果线程因异常而结束,线程池会补充一个新线程
newCachedThreadPool() 线程数量随任务增加而增加,如果线程任务执行完毕后空闲了60s会被回收
newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以在给定延迟后运行任务,或定期执行任务

这些方法实际上都是在调用ThreadPoolExecutor。

核心线程的数量应该设置为多少?无定论,建议计算密集型任务,设置为CPU核数+1;IO密集型任务,设置为CPU核数乘以二。

注意,阿里的编程规范要求,不能使用Executors的静态方法创建线程池,而需要使用ThreadPoolExecutor手动指定各项参数。这种方式明确线程池的规则和设定,避免系统资源的耗尽。

标签:Runnable,Java,target,任务,初识,线程,new,多线程,pool
From: https://www.cnblogs.com/louistang0524/p/18331576

相关文章

  • 黑马Java零基础视频教程精华部分_9_面向对象进阶(1)
    系列文章目录文章目录系列文章目录一、static(表示静态)是Java中的一个修饰符,可以修饰成员方法,成员变量1、静态变量2、静态变量底层原理3、static静态方法4、工具类、测试类、Javabean类5、static注意事项从代码层面从内存层面6、重新认识main方法一、static(表......
  • [Java基础]String 为什么是不可变的?
    关于这个问题,网上有人说,是因为String类被写成final或者String中的成员变量value数组被写成final,但其实并不是,下面做一个实验publicfinalclassMyString{publicfinalchar[]value={'z'};}首先我们定义了一个类Mystring,并且类和成员变量都被设置成finalpublicclas......
  • Java编写:给 20 块钱买可乐,每瓶可乐 3 块钱,喝完之后退瓶子可以换回 1 块钱, 问最多可以
    代码如下:注意while和count一般连用,如果是i只会循环执行一次!importjava.util.Scanner;publicclasstest16{publicstaticvoidmain(String[]args){//给20块钱买可乐,每瓶可乐3块钱,喝完之后退瓶子可以换回1块钱,问最多可以喝到多少瓶可乐//......
  • JavaEE 初阶(11)——多线程9之“阻塞队列”
    目录一.什么是“阻塞队列”二.生产者消费者模型2.1概念2.2 组件 2.3实际应用2.4优点 a.实现“解耦合” b.流量控制——“削峰填谷”2.5代价a. 更多的机器b.通信时间延长三.阻塞队列的实现 3.1简述  3.2ArrayBlockingQueue的使用3.3实现MyA......
  • 编写java程序,自动监控程度,dump内存文件
    步骤1:编写Java程序首先,编写一个Java程序,当内存使用达到11GB时生成heapdump文件,并以日期命名。将以下代码保存为MemoryMonitor.java文件:importcom.sun.management.HotSpotDiagnosticMXBean;importjavax.management.MBeanServer;importjava.lang.managemen......
  • 2024年华为OD机试真题-找出作弊的人-(C++/Java/python)-OD统一考试(C卷D卷)
    2024华为OD机试真题目录-(B卷C卷D卷)-【C++JavaPython】  题目描述公司组织了一次考试,现在考试结果出来了,想看一下有没人存在作弊行为,但是员工太多了,需要先对员工进行一次过滤,再进一步确定是否存在作弊行为。过滤的规则为:找到分差最小的员工ID对(p1,p2)列表,......
  • 【Java】韩顺平Java学习笔记 第19章 IO流
    文章目录文件概述常用的文件操作创建文件获取文件信息目录的操作和文件删除流的分类各抽象类常用子类对象FileInputStreamFileOutputStreamFileReaderFileWriter节点流和处理流概念BufferedReaderBufferedWriterBufferedInputStream&BufferedOutputStream对象流:Obje......
  • html中javascript点击事件后显示或隐藏某些元素时需要点击两次才生效的原因分析和优化
    html中javascript点击事件后显示或隐藏某些元素时需要点击两次才生效的原因分析和优化。原来的代码如下:<divclass="cardcardcol-sm-6col-md-4col-xl-2col-lg-2justify-content-centerbg-secondary-subtle"id="tools-trigger"><ahref="javascript:vo......
  • java基础知识汇总(一)
    PART1:Java基础知识概述与Java的下载安装1)Java语言概述:①Java的发展史:詹姆斯·高斯林(JamesGosling)1977年获得了加拿大卡尔加里大学计算机科学学士学位,1983年获得了美国卡内基梅隆大学计算机科学博士学位,毕业后到IBM工作,设计IBM第一代工作站NeWS系统,但不受重视。后来转至S......
  • Java 关键字、标识符、注释、常量
    关键字:被Java语言赋予特殊含义的单词,一般是使用小写字母构成。如何区分关键字?idea对关键字具有高亮的效果。但goto和const作为保留字存在。标识符:给类、接口、方法、变量等起名字时使用的字符序列起名字时的规则:1、英文大小写字母2、数字字符3、$和_起名字时的规范:1......