java面试之javaSE——java基础(线程)

本文详细介绍了Java中的多线程,包括线程的三种创建方式:继承Thread类、实现Runnable接口和实现Callable接口,并通过实例分析了它们的优缺点。还探讨了线程的状态以及Thread类的常用方法。
摘要由CSDN通过智能技术生成

一、多线程

1、线程的创建方式

线程的创建方式主要有三种:

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口
1)线程与进程

进程是系统程序运行的基本单位,是程序的一次执行过程,每个进程都有自己的内存空间,它有5个基本状态:初始态、执行态、等待状态、就绪状态、中止状态。线程是CPU调度的基本单位,它与同一个进程的其他线程共享进程的资源空间。
线程是进程的一部分,一个进程可以拥有多个线程,但至少有一个线程。

(1)线程与进程的区别

根本区别 进程是程序运行的基本单位,线程是CPU调度的基本单位。
开销方面 每个进程都有独立的代码和运行空间,进程间切换开销大;同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程间切换开销小。
内存分配 系统为每个进程分配内存空间;线程的内存空间主要来源于进程的分配,与其他线程共用内存空间。
包含关系 线程是进程的一部分。没有线程的进程认为是单线程,一个进程可以拥有多个线程,但是执行过程不是一条线,而是多个线程共同完成。(线程也被称为是轻量级进程

2)线程概念实例

吃饭实例,在吃饭时,可以边吃饭边喝酒,代码实现会发现,只有吃饭代码完成后才会执行喝酒代码,也就是说程序会顺序执行
为了实现边喝酒边吃饭的目的,引入线程概念:

public class Main2 {
    /**
     * @param args
     * @description 这个实例中,总共有三个线程:main线程,吃饭线程,喝酒线程。main线程很快就执行结束,但是吃饭线程和喝酒线程仍在执行,
     * 直到所有线程执行结束,整个程序才会结束,main线程的结束并不代表程序运行结束。
     * 顺序:代码从上往下运行,只有上一行代码执行完毕才会执行下一行代码。
     * 并行:多个操作同时处理,他们是并行的。就像QQ的收发消息,互不影响,可以同时进行。
     * 并发:把一个操作分割成多个部分并且可以无序处理,那么多个部分就可以交叉完成。但是任意时刻点只有一个部分在进行(多个部分在一个处理器(CPU)上)。
     * <p>
     * 通常一台电脑只有一个CPU,多个线程属于并发执行,如果有多个CPU,多线程并发执行就有可能变成并行执行。
     */

    public static void main(String[] args) {
        //一边吃饭一边喝酒
        new EatThread().start();
        new DrinkThread().start();
    }
}

class EatThread extends Thread {
    @Override
    public void run() {
        System.out.println("开始吃饭?...\t" + new Date());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束吃饭?...\t" + new Date());
    }
}

class DrinkThread extends Thread {
    @Override
    public void run() {
        System.out.println("开始喝酒?...\t" + new Date());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束喝酒?...\t" + new Date());

    }
}

如代码所示,创建吃饭和喝酒两个线程,主线程开始,调用吃饭喝酒线程,从而实现边吃饭边喝酒。(线程同步)。接下来介绍几种线程的创建方法。

3)线程的创建
(1)继承Thread类创建线程
package com.xpl.thread;

/**
 * @program: Intellij
 * @description: 启动一个线程
 * 创建线程的方式:
 * 1、继承Thread类+重写run方法
 *
 *
 * @author: Xing Panlin
 * @date: 2020-02-13 22:58
 **/
public class StartThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<20;i++){
            System.out.println("一边听歌");
        }
    }

    public static void main(String[] args){
        //创建子类对象,启动线程
        StartThread st = new StartThread();
        st.start();
        //st.run();//普通方法调用

        for (int i=0;i<20;i++){
            System.out.println("一边Coding");
        }
    }
}

运行结果:
第一组:

一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding

第二组:

一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边Coding
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌
一边听歌

从两组运行结果可以看出,main线程和st线程的运行互不影响。准确的说,他们是交叉运行。
在st线程启动后,相当于从当前代码行开始新开辟一条路径运行st线程;但是st线程启动后代码并不会等待st线程运行结束,而是会继续向下执行代码。也就是说st线程启动后这个程序中有两条路径分别执行两个线程(st线程启动后不一定立即执行,而是看CPU分配的时间片),但是他们不是同时执行,而是根据CPU分配的时间片执行代码,也就是运行结果中看到的交叉运行或者先后运行。

(2)实现Runnable接口创建线程
public class Main {   
    public static void main(String[] args) throws  Exception{
        //测试:实现Runnable接口
        //将Runnable的实现类作为Thread的构造参数传递到Thread类中,然后启动Thread类
        MyRunnable myThread2 = new MyRunnable();
        new Thread(myThread2).start();

        //变体写法:
        //1、匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
            }
        }).start();

        //2、尾部代码块,是对匿名内部类形式的变体!!!
        new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
            }
        }.start();

        //3、Runnable是函数式接口,所以可以使用Lamda表达式
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        };
        new Thread(runnable).start();       
    }
}

/**
 * 实现java.lang.Runnable接口,重写run方法,然后使用Thread类来进行包装
 * Runnable接口:
 *
 * @FunctionalInterface public interface Runnable {
 * public abstract void run();
 * }
 */
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
    }
}
(3)实现Callable接口创建线程
public class Main {   
    public static void main(String[] args) throws  Exception{
        //测试:实现Callable接口
        //将Callable包装成FutureTask,FutureTask也是一种Runnable
        MyCallable callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask).start();

        //get方法会阻塞调用的线程
        Integer sum = futureTask.get();
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId()+" = "+sum);
 
    }
}

/**
 * 实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask,然后包装成Thread类
 * Callable:有返回值的线程,能取消线程,也可以判断线程是否执行完毕
 * Callable也是一种函数式接口:
 * @FunctionalInterface
 * public interface Callable<V> {
 *     V call() throws Exception;
 * }
 *
 * FutureTask类
 * public class FutureTask<V> implements RunnableFuture<V> {
 * 	    // 构造函数
 * 	    public FutureTask(Callable<V> callable);
 * 	    // 取消线程
 * 	    public boolean cancel(boolean mayInterruptIfRunning);
 * 	    // 判断线程
 * 	    public boolean isDone();
 * 	    // 获取线程执行结果
 * 	    public V get() throws InterruptedException, ExecutionException;
 * }
 *
 * RunnableFuture接口:
 * public interface RunnableFuture<V> extends Runnable, Future<V> {
 *     void run();
 * }
 *
 */
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\tstarting");

        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            sum += i;
        }

        Thread.sleep(5000);

        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\tover……");
        return sum;
    }
}
4)三种创建方法的比较
  • 1、Thread:继承方式,不推荐使用,因为继承了Thread类,就不可以继承其他类了
  • 2、实现Runnable接口:比单继承的Thread更灵活
  • 3、实现Callable接口:Thread类和Runnable接口实际上都是重写了run()方法,并且没有返回值。但是Callable接口重写call()方法,具有返回值,
    并且可以通过FutureTask类判断线程是否执行完毕,或者可以取消线程。
  • 4、当线程不需要返回值的时候可以使用实现Runnable接口的方法,如果需要返回值就是用实现Callable接口的方式。Thread类一般用于启动线程
  • 5、Thread类是实现Runnable接口,Callable封装成FutureTask,FutureTask实现RunnableFuture接口,RunnableFuture接口继承Runnable、Future接口
    !!!注意
    • 接口可以继承多个接口,但是不能实现接口
    • 一个类可以实现多个接口,继承一个类
    • 一个抽象类可以实现一个接口,继承一个类

2、线程的状态

创建(new)状态:执行new Thread();创建完成后就需要为线程分配内存
就绪(runnable)状态:调用了start()方法, 等待CPU进行调度
运行(running)状态:执行run()方法
阻塞(blocked)状态:暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞),可能将资源交给其它线程使用
死亡(terminated)状态:线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

在这里插入图片描述

3、Thread类常用方法

Thread类方法列表

public class Thread implements Runnable {
    // 线程名字
    private volatile String name;
    // 线程优先级(1~10)
    private int priority;
    // 守护线程
    private boolean daemon = false;
    // 线程id
    private long tid;
    // 线程组
    private ThreadGroup group;
    
    // 预定义3个优先级
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
    
    
    // 构造函数
    public Thread();
    public Thread(String name);
    public Thread(Runnable target);
    public Thread(Runnable target, String name);
    // 线程组
    public Thread(ThreadGroup group, Runnable target);
    
    
    // 返回当前正在执行线程对象的引用
    public static native Thread currentThread();
    
    // 启动一个新线程
    public synchronized void start();
    // 线程的方法体,和启动线程没关系
    public void run();
    
    // 让线程睡眠一会,由活跃状态改为挂起状态(阻塞状态)
    public static native void sleep(long millis) throws InterruptedException;
    public static void sleep(long millis, int nanos) throws InterruptedException;
    
    // 打断线程 中断线程 用于停止线程
    // 调用该方法时并不需要获取Thread实例的锁。无论何时,任何线程都可以调用其它线程的interruptf方法
    public void interrupt();
    public boolean isInterrupted()
    
    // 线程是否处于活动状态
    public final native boolean isAlive();
    
    // 交出CPU的使用权,从运行状态改为挂起状态
    public static native void yield();
    
    public final void join() throws InterruptedException
    public final synchronized void join(long millis)
    public final synchronized void join(long millis, int nanos) throws InterruptedException
    
    
    // 设置线程优先级
    public final void setPriority(int newPriority);
    // 设置是否守护线程
    public final void setDaemon(boolean on);
    // 线程id
    public long getId() { return this.tid; }
        
    // 线程状态
    public enum State {
        // new 创建
        NEW,

        // runnable 就绪
        RUNNABLE,

        // blocked 阻塞
        BLOCKED,

        // waiting 等待
        WAITING,

        // timed_waiting
        TIMED_WAITING,

        // terminated 结束
        TERMINATED;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值