代码编织梦想

创建线程有哪几种方式?

创建线程有三种方式,分别是继承Thread类、实现Runnable接口、实现Callable接口。

通过继承Thread类来创建并启动线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法将作为线程执行体。

  2. 创建Thread子类的实例,即创建了线程对象。

  3. 调用线程对象的start()方法来启动该线程。

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

MyThread t = new MyThread();
t.start(); // 启动线程

通过实现Runnable接口来创建并启动线程的步骤如下:

  1. 定义Runnable接口的实现类,并实现该接口的run()方法,该run()方法将作为线程执行体。

  2. 创建Runnable实现类的实例,并将其作为Thread的target来创建Thread对象,Thread对象为线程对象。

  3. 调用线程对象的start()方法来启动该线程。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start(); // 启动线程

通过实现Callable接口来创建并启动线程的步骤如下:

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。然后再创建Callable实现类的实例。

  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。

  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

public class Main {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        new Thread(task).start();
        try {
            Integer result = task.get();
            System.out.println("计算结果为:" + result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

如何实现线程同步?

Java中的线程同步主要有以上四种方式:synchronized 关键字、ReentrantLock 类、CountDownLatch 类、Semaphore 类

1.synchronized 关键字 

/ˈsɪŋkrənaɪzd/   翻译:同步的

synchronized 是Java中最基本的线程同步方式,可以使用 synchronized 关键字来修饰方法或者代码块。当一个线程进入一个 synchronized 代码块或方法时,它会尝试获取锁,如果该锁已经被其他线程持有,则该线程会被阻塞等待锁的释放,直到获取到锁才能执行代码。

2.ReentrantLock 类

翻译:重入锁

ReentrantLock 是Java中的一个锁类,它提供了更丰富的锁控制机制,可以替代 synchronized 关键字。ReentrantLock 可以重复进入、可中断、可设置超时时间、可设置公平性等,使用 ReentrantLock 需要手动获取锁和释放锁。

3.CountDownLatch 类

翻译:倒数锁存器

CountDownLatch 是Java中的一个同步工具类,可以用来控制线程等待。CountDownLatch 维护一个计数器,当计数器的值变为0时,所有等待该计数器的线程将被唤醒。可以通过 CountDownLatch 来实现线程之间的协调和同步。

4.Semaphore 类

翻译:信号

Semaphore 是Java中的一个同步工具类,可以用来控制同时访问某个资源的线程个数。Semaphore 维护一个许可证,可以设置允许同时访问该资源的线程个数,当许可证已经全部分配完毕时,后续的线程将会被阻塞等待,直到有其他线程释放许可证。

介绍一下线程的生命周期

在线程的生命周期中,它要经过新建(New)、就绪(Ready)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。

1.当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值

2.当线程对象调用了start()方法之后,该线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了

3.如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

4.当一个线程开始运行后,它不可能一直处于运行状态,线程在运行过程中需要被中断,目的是使其他线程获得执行的机会;如果线程被中断的话,则进入阻塞状态。

5.线程会以如下三种方式结束,结束后就处于死亡状态:

  • run()或call()方法执行完成,线程正常结束。

  • 线程抛出一个未捕获的Exception或Error。

  • 直接调用该线程的stop()方法来结束该线程

run()和start()有什么区别?

run()方法被称为线程执行体,它的方法体代表了线程需要完成的任务,而start()方法用来启动线程。

调用start()方法启动线程时,系统会把该run()方法当成线程执行体来处理。但如果直接调用线程对象的run()方法,则run()方法立即就会被执行,而且在run()方法返回之前其他线程无法并发执行。也就是说,如果直接调用线程对象的run()方法,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程执行体。

说一说Java多线程之间的通信方式

1.wait()、notify()、notifyAll()

wait()方法可以让当前线程释放对象锁并进入阻塞状态。notify()方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。notifyAll()用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。

2.await()、signal()、signalAll()

await()/signal()/signalAll() 与 wait()/notify()/notifyAll()有着对应关系,功能相似

3.BlockingQueue

程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好地控制线程的通信。线程之间需要通信,最经典的场景就是生产者与消费者模型,而BlockingQueue就是针对该模型提供的解决方案。

如何实现子线程先执行,主线程再执行?

要实现子线程先执行,主线程再执行,可以使用Java中的join()方法。join()方法可以等待线程执行完毕,然后再继续执行当前线程。如果在主线程中调用子线程的join()方法,主线程就会等待子线程执行完毕,然后再执行自己的代码。

下面是一个示例代码,演示了如何使用join()方法实现子线程先执行,主线程再执行的效果:

public class ThreadExample {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("子线程开始执行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程执行完毕");
        });

        t1.start();
        t1.join();

        System.out.println("主线程开始执行");
        System.out.println("主线程执行完毕");
    }
}

说一下乐观锁和悲观锁

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。Java中悲观锁是通过synchronized关键字或Lock接口来实现的。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。

说一说synchronized与Lock的区别

  1. synchronized是Java关键字,在JVM层面实现加锁和解锁;Lock是一个接口,在代码层面实现加锁和解锁。

  2. synchronized可以用在代码块上、方法上;Lock只能写在代码里。

  3. synchronized在代码执行完或出现异常时自动释放锁;Lock不会自动释放锁,需要在finally中显示释放锁。

  4. synchronized会导致线程拿不到锁一直等待;Lock可以设置获取锁失败的超时时间。

  5. synchronized无法得知是否获取锁成功;Lock则可以通过tryLock得知加锁是否成功。

  6. synchronized锁可重入、不可中断、非公平;Lock锁可重入、可中断、可公平/不公平,并可以细分读写锁以提高效率。

介绍一下线程池

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互。在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个空闲的线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。

介绍一下线程池的工作流程

线程池都有哪些状态?

线程池一共有五种状态, 分别是:

  1. RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务。

  2. SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。

  3. STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态。

  4. TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。

  5. TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。进入TERMINATED的条件如下:

    • 线程池不是RUNNING状态;

    • 线程池状态不是TIDYING状态或TERMINATED状态;

    • 如果线程池状态是SHUTDOWN并且workerQueue为空;

    • workerCount为0;

    • 设置TIDYING状态成功。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_51118755/article/details/129604189

秒杀多线程第一篇——多线程笔试面试题汇总-爱代码爱编程

第一篇 多线程笔试面试题汇总 一.概念性问答题 第一题:线程的基本概念、线程的基本状态及状态之间的关系? 第二题:线程与进程的区别? 这个题目问到的概率相当大,计算机专业考研中也常常考到。要想全部答出比较难。 第三

面试题总结——Java多线程-爱代码爱编程

面试题总结——Java多线程  1.进程,线程,程序的区别   进程和线程:进程是操作系统中一个程序的执行周期,而线程是一个进程中独立运行的子任务。    体量:相比于进程而言,线程更加轻量级,创建或销毁一个线程比创建或销毁一个进程所花费的开销要小得多;    依附性:线程是依附于进程的,一旦进程结束,该进程内的所有线程都将不复存在;    系统资源:

Android高级面试题汇总——高级开发技术面试题(1)-爱代码爱编程

(一)图片 1、图片库对比         (1)Universal ImageLoader是很早开源的图片缓存,在早期被很多应用使用         优点: 支持下载进度监听可以在 View 滚动中暂停图片加载默认实现多种内存缓存算法支持本地缓存文件名规则定义        缺点:不支持GIF图片加载,缓存机制没有和Http的缓存很好的结合,完全

个人整理的面试题汇总(二)——多线程-爱代码爱编程

线程运行的几种状态 Synchronized和lock synchronized和lock的用法区别 synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。 lock:需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程

Android高级面试题汇总——Java篇(2)-爱代码爱编程

(三) 数据结构 1、并发集合了解哪些 非阻塞集合:这类集合包括添加和移除的方法,如果方法不能立即被执行,则返回null或抛出异常,但是调用这个方法的线程不会被阻塞 ConcurrentLinkedQueue:         基于链接节点的无限制线程安全队列,此队列命令元素FIFO(先进先出)。这个队列在add(),remove(),poll()

Android高级面试题汇总——Android篇(2)-爱代码爱编程

(三)常见的一些原理性问题 1、Handler机制和底层实现         机制:hanlder是android线程间通信的一种实现,以消息队列的方式实现线程间数据的共享,通过Looper不断的轮询消息队列来  获取数据         底层:MessageQueen、Looper以及Looper内部的ThreadLocal,我们在线程中Looper

java面试公开课,Java公开课|2019年高级Java程序员面试题汇总——Redis系列-爱代码爱编程

【摘要】作为一门面向对象编程语言,Java吸收了C++语言的优点,也展现了其强大的一面,我们能在各个地方看到其功能强大和简单易用的两个特征,当然,也吸引了很多程序员的注意力,所以就有人想了解Java的相关内容,今天就来讲讲高级Java程序员面试题的相关内容。 让我们现在就来看看高级Java程序员面试题,今天我们为大家带来的是有关于Redis的相关题目

常见面试题汇总 —— C语言-爱代码爱编程

面试的方向 C 语言编程基础——初级C 语言编程基础——高级数据结构与算法linux 基本操作常用工具 —— gitC 语言编程基础——初级 预处理 1. 什么是预处理、熟悉那些预处理命令简单说一下   在编译之前对源文件进行简单加工的过程,就称为预处理。以#开头的命令为预处理命令。如文件包含命令#include、宏定义命令#define、条件编译

【珍藏版】80道Java多线程并发经典面试题-爱代码爱编程

前言 个人珍藏的80道Java多线程/并发经典面试题,因为篇幅太长,现在先给出1-10的答案解析哈,后面一起完善,并且上传github哈~ github.com/whx123/Java… ❞ 1. synchronized的实现原理以及锁优化? synchronized的实现原理 synchronized作用于「方法」或者「代码块

测试工程师面试试题汇总——来源于各个公司真实面试试题-爱代码爱编程

1.测试的流程大概简述一下 产品经理根据客户的需求,编写出一个产品说明书,根据产品说明书写出需求分析文档,接下来开会对需求分析进行评审。接下来便进行详细的工作分配。开发人员根据需求文档进行编码,同时测试人员根据需求文档编写出测试计划书,测试组成员开会评审测试计划书,测试计划书通过之后,相应的测试人员根据工作安排编写出负责模块下的测试用例(根据需求文档),

【2022最新java面试宝典】—— springboot面试题(44道含答案)-爱代码爱编程

目录 1. 什么是 Spring Boot?2. 为什么要用SpringBoot3. SpringBoot与SpringCloud 区别4. Spring Boot 有哪些优点?5. Spring Boot 的

java面试题系列——javase面试题(集合二)_javase 面试题-爱代码爱编程

1、是否写过Hash底层,了解过什么? 常用HASH函数: 直接取余法: f(x)-=x mod max;maxM一般是不太接近 24的一个质数。 乘法取整法:f(x)=trunc((x/maxX)*maxlongit)mod maxM, 主要用于实数。 平方取中法:f(x)=(x*x div 1000) mod 1000000);平方后取中间的,每位包