Java并发编程 之创建线程的四种方式

概述

1、继承 Thread 类
2、实现 Runnable 接口
3、实现 Callable 接口 + FutureTask(可以拿到返回结果,可以处理异常)
4、线程池

总结:
方式1和方式2:主进程无法获取线程的运行结果。
方式3:主进程可以获取线程的运行结果,但是不利于控制服务器中的线程资源。可能会导致服务器资源耗尽。

继承Thread类创建线程

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello Thread");
    }
}

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

实现Runnable接口创建线程

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello Thread");
    }
}

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

使用Callable和FutureTask创建线程

FutureTask 继承了 Runnable。
FutureTask 传入Callable可以接收返回值。

public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask<>(() -> {
            return "hello";
        });
        new Thread(task).start();
        System.out.println(task.get());
    }
}

使用线程池例如用Executor框架

public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(() -> {
            System.out.println("hello");
        });
    }
}

为什么 Thread 类的构造方法根本没有 Callable,为啥还是可以通过 Callable 创建线程?

FutureTask 实现了 RunnableFutureRunnableFuture 继承了 Runnable。所以 new Thread 可以传入一个FutureTask
然后发现创建 FutureTask 可以传入一个 Callable 对象,得出结论 Thread 可以通过 FutureTask 这个中间桥梁来创建线程。
在这里插入图片描述

进程与线程

什么是进程:
进程就是在运行过程中的程序,就好像手机运行中的微信,QQ,这些就叫做进程。
什么是线程:
线程就是进程的执行单元,就好像一个音乐软件可以听音乐,下载音乐,这些任务都是由线程来完成的。
进程与线程的关系:
一个进程可以拥有多个线程,一个线程必须要有一个父进程
线程之间共享父进程的共享资源,相互之间协同完成进程所要完成的任务
一个线程可以创建和撤销另一个线程,同一个进程的多个线程之间可以并发执行

线程的生命周期

当一个线程开启之后,它会遵循一定的生命周期,它要经过新建,就绪,运行,阻塞和死亡这五种状态。

  • 新建状态
    这个状态的意思就是线程刚刚被创建出来,这时候的线程并没有任何线程的动态特征。
  • 就绪状态
    当线程对象调用 start() 方法后,该线程就处于就绪状态。处于这个状态中的线程并没有开始运行,只是表示这个线程可以运行了。
  • 运行状态
    处于就绪状态的线程获得了 CPU 后,开始执行 run() 方法,这个线程就处于运行状态。
  • 阻塞状态
    当线程被暂停后,这个线程就处于阻塞状态。
  • 死亡状态
    当线程被停止后,这个线程就处于死亡状态。

控制线程

sleep()

线程生命周期的变化

在这里插入图片描述

方法预览

public static native void sleep(long millis)
public static void sleep(long millis, int nanos)

该方法的意思就是让正在运行状态的线程到阻塞状态,而这个时间就是线程处于阻塞状态的时间。millis 是毫秒的意思,nanos 是毫微秒。

线程优先级

方法预览

public final void setPriority(int newPriority)
public final int getPriority()

从方法名就可以知道,以上两个方法分别就是设置和获得优先级的。值得注意的是优先级是在 1~10 范围内,也可以使用以下三个静态变量设置:

  • MAX_PRIORITY:优先级为 10
  • NORM_PRIORITY:优先级为 5
  • MIN_PRIORITY:优先级为 1

yield()

线程生命周期的变化

在这里插入图片描述

方法预览

public static native void yield();

这个方法的意思就是让正在运行的线程回到就绪状态,并不会阻塞线程。可能会发生一种情况就是,该线程调用了 yield() 方法后,线程调度器又会继续调用该线程。
这个方法要注意的是它只会让步给比它优先级高的或者和它优先级相同并处在就绪状态的线程。

join()

后台线程

方法预览

public final void setDaemon(boolean on)
public final boolean isDaemon()

这个方法就是将线程设置为后台线程,后台线程的特点就是当前台线程全部执行结束后,后台线程就会随之结束。此方法设置为 true 时,就是将线程设置为后台线程。
而 isDaemon() 就是返回此线程是否为后台线程。
4.5.2 代码举例

public class DaemonDemo extends Thread {

    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            System.out.println(getName() + " "+ isDaemon() + " " + i);
        }
    }
    
    public static void main(String[] args) {
        DaemonDemo daemonDemo = new DaemonDemo();
        daemonDemo.setDaemon(true);
        daemonDemo.start();
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

打印结果:

main 0
main 1
main 2
main 3
main 4
Thread-0 true 0
main 5
main 6
main 7
main 8
main 9
Thread-0 true 1
Thread-0 true 2
Thread-0 true 3
Thread-0 true 4
Thread-0 true 5
Thread-0 true 6
Thread-0 true 7
Thread-0 true 8
Thread-0 true 9
Thread-0 true 10
Thread-0 true 11

从打印结果可以看到 main 执行完后,Thread-0 没有执行完毕就结束了。

几个重要概念

同步和异步

同步和异步的都是形容一次方法的调用。它们的概念如下:

  • 同步:调用者必须要等到调用的方法返回后才会继续后续的行为。
  • 异步:调用者调用后,不必等调用方法返回就可以继续后续的行为。

下面两个图就可以清晰表明同步和异步的区别:
同步:

异步:

并发和并行

并发和并行是形容多个任务时的状态,它们的概念如下:

  • 并发:多个任务交替运行。
  • 并行:多个任务同时运行。

其实这两个概念的的区别就是一个是交替,另一个是同时。其实如果只有一个 CPU 的话,系统是不可能并行执行任务,只能并发,因为 CPU 每次只能执行一条指令。所以如果要实现并行,就需要多个 CPU。为了加深这两个概念的理解,可以看下面两个图:
并发:

并行:

原子性

原子就是指化学反应当中不可分割的微粒。所以原子性概念如下:
原子性:在 Java 中就是指一些不可分割的操作。
比如刚刚介绍的内存操作全部都属于原子性操作。以下再举个例子帮助大家理解:

x = 1;
y = x;

以上两句代码哪个是原子性操作哪个不是?
x = 1 是,因为线程中是直接将数值 1 写入到工作内存中。
y = x 不是,因为这里包含了两个操作:
读取了 x 的值(因为 x 是变量)
将 x 的值写入到工作内存中

线程安全

线程安全就是指某个方法在多线程环境被调用的时候,能够正确处理多个线程之间的共享变量,使程序功能能够正确执行。

一个线程不安全的例子

public class ThreadSafeDemo {
    
    public int count = 0;
    
    public void increaseCount() {
        count++;
    }
    
    static class TaskThread extends Thread {
        ThreadSafeDemo threadSafeDemo;
        public TaskThread(ThreadSafeDemo threadSafeDemo) {
            this.threadSafeDemo = threadSafeDemo;
        }
        
        @Override
        public void run() {
            for(int i = 0; i < 1000; i++) {
                threadSafeDemo.increaseCount();
            }
        }
        
    }
    
    public static void main(String[] args) throws InterruptedException {
        int num = 10;
        ThreadSafeDemo threadsafe = new ThreadSafeDemo();
        Thread[] threads = new Thread[num];
        
        for(int i = 0; i < num; i++) {
            threads[i] = new TaskThread(threadsafe);
            threads[i].start();
        }
        
        for(int i = 0; i < num; i++) {
            // 当线程数组的所有线程的任务执行完之后,主线程才会继续执行
            threads[i].join();
        }
        System.out.println(threadsafe.count);
    }
}

首先分析下我们期望这个类的行为是什么?

  • 从代码就可以看出,这里希望这个类的 count 变量可以从 0 变成 10000。
  • 所以这里启动了 10 个线程,每个线程都会循环调用 1000 次 increaseCount(),这个方法就是调用 count++。
  • 启动所有线程之后会循环调用每个线程的 join() 方法,目的就是为了阻塞主线程直到所有线程都执行完成之后才继续调用主线程的打印方法。

运行代码可能会出现如下结果:

9431

这个结果和我们预期代码输出的结果并不一致,这就代表这个类并不具有线程安全性了。

什么时候才会讨论线程安全性?

要讨论线程安全性,必须满足两个前提条件:

  • 在多线程的环境下
  • 多个线程中对共享变量进行修改

如果多个线程根本没有共享变量或者没有对这个共享变量进行修改,这时候这个类肯定是线程安全的,所以根本没必要讨论这个类是否线程安全。

参考:
多线程详解(2)——不得不知的几个概念
多线程详解(1)——线程基本概念
什么是线程安全性?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值