【Java八股面试题】并发编程——进程和线程(上)

#  线程创建&运行

Java如何创建线程?

先说结论:

(1)继承Thread,重写run方法

(2)实现Runnable接口,重写run方法

(3)实现Callable接口

(4)使用线程池创建线程

(5)lambda表达式

(6)使用匿名内部类创建 Thread 子类对象

(7)使用匿名内部类,实现Runnable接口

(1)继承Thread,重写run方法

1、定义一个MyThread类,继承Thread接口,重写run()方法,在run()方法中编写业务逻辑。在main方法中创建MyThread对象,调用start()方法启动线程。

2、注意这里运行main方法的时候JVM也默认创建了一个main线程(主线程),和MyThread创建出来的新线程是不同的,相当于现在有了两个线程在并发执行。

代码如下:

public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("业务逻辑...");
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

(2)实现Runnable接口,重写run方法

1、在实现Runnable接口时,需要实现run()方法,该方法中包含线程的执行逻辑。

2、实现Runnable接口的方式相对于继承Thread类的方式更加灵活,因为Java不支持多重继承,但是可以实现多个接口。因此,如果你的类已经继承了其他类,仍然可以通过实现Runnable接口来创建线程。

代码如下:

public class MyRunnable implements Runnable{
    @Override
    public void run(){
        System.out.println("这里写业务逻辑...");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

(3)实现Callable接口

 使用Callable接口可以让线程返回一个结果

代码如下:

public class MyCallable implements Callable<Integer>{
    // 因为Callable泛型指定为<Integer>,所以call的返回值为Integer
    @Override
    public Integer call() throws Exception {
        return 1;
    }

    public static void main(String[] args) throws Exception {
        // 创建一个FutureTask对象,创建MyCallable对象作参数
        FutureTask task = new FutureTask<>(new MyCallable());
        // 新建线程并启动
        Thread thread = new Thread(task);
        thread.start();
        // 输出线程的返回结果
        System.out.println("MyCallable线程的返回结果是:" + task.get());
    }
}

控制台输出:

MyCallable线程的返回结果是:1

(4)使用线程池创建线程

下面的代码创建了一个固 定大小为3的线程池,然后向线程池提交了5个任务,每个任务都是一个匿名的Runnable对象。每个任务的run()方法简单地打印当前线程的名称。

代码如下:

public class MyThreadPool {
    public static void main(String[] args) {
        // 创建一个固定大小为3的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);

        // 提交任务给线程池执行
        for(int i = 0; i < 5; i++){
            pool.submit(new Runnable() {
                public void run(){
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }

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

控制台输出结果:

pool-1-thread-2

pool-1-thread-1

pool-1-thread-3

pool-1-thread-1

pool-1-thread-2

(5)lambda表达式

1、在这个例子中,我们使用了Lambda表达式作为参数传递给Thread类的构造函数。Lambda表达式 () -> { System.out.println("业务逻辑..."); } 定义了一个匿名的Runnable对象,其中包含了线程的执行逻辑。

2、Lambda表达式的箭头 -> 将参数列表和方法体分隔开,参数列表为空,而方法体包含了要执行的业务逻辑。

代码如下:

public class MyLambda {  
    public static void main(String[] args) {
        // lambda表达式
        Thread thread = new Thread(() -> {
            System.out.println("业务逻辑...");
        });
        thread.start();
    }
}

(6)使用匿名内部类创建 Thread 子类对象

直接创建Thre ad子类,同时实例化出一个对象,重写run方法。

代码如下:

public class MyAnonymous {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("业务逻辑...");
            }
        };
        thread.start();
    } 
}

(7)使用匿名内部类,实现Runnable接口

使用匿名内部类,实现Runnable接口作为Thread构造方法的参数。

代码如下:

public class MyAnonymousRunnable {
    // 使用匿名内部类,实例Runnable接口作为构造参数
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("业务逻辑...");
            }
        });
        thread.start();
    }
}

采用Runnable、Callable接口的方式创建多线程的优缺点?

  1. 优点是,线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  2. 缺点是,编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。

采用继承Thread类的方式创建多线程的优缺点?

  1. 优点是:编写简单,如果需要访问当前线程,则无须使用Thread.currentThread()方法,直接使用this即可获得当前线程。
  2. 缺点是:因为线程类已经继承了Thread类,所以不能再继承其他父类。

说说Thread类的常用方法?

  • Thread类常用构造方法:

Thread()

Thread(String name)

Thread(Runnable target)

Thread(Runnable target, String name):name为线程名,target为包含线程体的目标对象

  • Thread类常用静态方法:

currentThread():返回当前正在执行的线程;

interrupted():返回当前执行的线程是否已经被中断;

sleep(long millis):使当前执行的线程睡眠多少毫秒数;

yield():使当前执行的线程自愿暂时放弃对处理器的使用权并允许其他线程执行;

  • Thread类常用实例方法:

getId():返回该线程的id;

getName():返回该线程的名字;

getPriority():返回该线程的优先级;

interrupt():使该线程中断;

isInterrupted():返回该线程是否被中断;

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

isDaemon():返回该线程是否是守护线程;

setDaemon(boolean on):将该线程标记为守护线程或用户线程,如果不标记默认是非守护线程;

setName(String name):设置该线程的名字;

setPriority(int newPriority):改变该线程的优先级;

join():等待该线程终止;

join(long millis):等待该线程终止,至多等待多少毫秒数。

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

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

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

可以直接调用 Thread 类的 run 方法吗?

new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

线程是否可以重复启动,会有什么后果?

只能对处于新建状态的线程调用start()方法,否则将引发IllegalThreadStateException异常。

当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样, 仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

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

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

启动子线程后,立即调用该线程的join()方法,则主线程必须等待子线程执行完成后再执行。

Thread类提供了让一个线程等待另一个线程完成的方法——join()方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。

以下例子中,子线程childThread执行耗时3秒,主线程等待子线程完成再执行。

public class MyJoin {
    public static void main(String[] args) throws Exception {
        // 创建子线程
        Thread childThread = new Thread(new Runnable() {
            @Override
            public void run(){
                // 子线程执行逻辑
                System.out.println("子线程执行开始");
                try {
                    // 模拟子线程执行耗时任务
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程执行完成");
            }
        });
        // 启动子线程
        childThread.start(); 

        // 主线程等待子线程执行完成
        childThread.join();

        // 主线程在子线程执行完成后执行
        System.out.println("主线程执行完成");
    }
}

控制台输出结果:

子线程执行开始

子线程执行完成

主线程执行完成

#  线程并发/并行

并发与并行的区别

  • 并发:两个及两个以上的作业在同一 时间段 内执行。
  • 并行:两个及两个以上的作业在同一 时刻 执行。

最关键的点是:是否是 同时 执行。

如下图所示,在同一时间段内有两个线程,并发是一个CPU交替执行两个线程,并行是两个CPU同时执行两个线程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值