一文读懂Java并发编程基础知识

什么是并发?

并发是指在某个时间段内,多任务交替处理的能力。比如说,你有一台单核心CPU电脑,注意必须是以单核心来理解下面的例子才是正确的。你可以利用电脑边听音乐,边写PPT,还能随时斗上一盘地主。这些事情表面上看起来就是同一时刻在执行多个任务。在同一应用上又可以做多个事情,比如你玩斗地主,程序在播音乐,在处理各个玩家的出牌,还要处理玩家有聊天界面上相互骂娘的任务。在单核CPU电脑上能“同时”处理多个事情,这个叫并发。假设你是多核心电脑,你在电脑时同时进行多个任务,此时叫并行。

并发与并行相对容易混淆,核心区分就是进程是否同时执行。并发处理是为了提高程序的运行效率。

进程和线程

在计算机操作系统中可以同时运行多个进程,每个进程会有多个线程。比如同时在电脑上打开音乐应用,视频应用,那么这两个应用就是处于两个不同的进程中。进程相互是独立隔离运行的,操作系统负责给进程分配资源,比如内存资源,线程调度等。进程间通讯需要借助如socket、共享内存等方式。线程是操作系统调度的的最小单位,运行一段代码,就是在线程中运行的,线程会顺序执行代码,通过栈来存储运行过程中的数据,程序计数器记录程序已经执行到的行数(这样当线程切换,后面再次获取CPU时间片时得以恢复执行正确的代码行)。因此,进程与线程的关系也很明显,进程包含多个线程。

Java多线程与线程安全

当启动一个进程时,操作系统会分配给进程内存、cpu等资源以运行。Java多线程是指在一个Java应用中同时运行多个线程的能力。当然,如果是多核CPU计算机,那么Java应用在任一时刻,是可以同时运行多个线程的。但同时运行的这些线程却有可能同时访问一些共享内存变量,此时就涉及到线程安全了,因为极有可能不同线程同时将一个变量值读取了,并各自修改,然后相互之间还不知晓数据已改,导致程序执行错误。要保证数据安全则需要额外的机制,如锁、CAS、内存读写屏障等手段来保证线程安全。

如何创建Java线程

方式一,实现Runnable

/**
 * @author kangming.ning
 * @date 2020/10/12 16:14
 */
public class RunnableThread implements Runnable {
    @Override
    public void run() {
        System.out.println("用实现Runnable接口实现线程,ThreadName:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Thread(new RunnableThread()).start();
        //匿名方式如下
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名Runnable接口实现线程,ThreadName:" + Thread.currentThread().getName());
            }
        }).start();
        //lambda 表达式
        new Thread(() -> System.out.println("lambda 表达式Runnable接口实现线程,ThreadName:" + Thread.currentThread().getName())).start();
    }
}

上面是通过Runnable方式来实现run接口,然后传递Runnable对象给Thread以实现线程运行。

方式二,继承Thread

/**
 * @author kangming.ning
 * @date 2020/10/12 16:21
 */
public class ExtendsThread extends Thread {

    @Override
    public void run() {
        System.out.println("用Thread类实现线程,ThreadName:" + Thread.currentThread().getName());
    }

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

可以看到,直接继承Thread,然后重写run方法即可实现线程。

Java创建线程的方式有很多种,但换汤不换药,Java创建线程本质上只有一种,就是继承 Thread ,然后通过Thread的run方法运行程序。常用的实现Runnable方式也是借助Thread的run方法而已。可以简单看一下源码,下面源码摘取自Thread类

 /* What will be run. */
 private Runnable target;
 
 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    
    private native void start0();
 
     @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

很明显,在run方法中,判断如果target不为空,则直接运行target的run方法。所以说,本质上,Java实现线程只有一种方式。但实现Runnable这种方式建议使用,因为直接Java是单继承的,要是继承了Thread类,它就没法继承其它类了,扩展性不好。另外实现Runable的方式和线程池更搭,线程池是为了更高效的利用线程,避免线程频繁创建、销毁线程的开销,所以使用Runnable创建任务,再把任务交给线程池去执行是比较好的(应该没人傻到用继承Thread的方式,再把它扔线程池吧,虽说可以运行,但没必要)。因此,实现线程都建议采用实现Runnable接口的方式。

当前线程

任一时刻,一个CPU核心只能执行一个线程,这个线程就被称为当前线程。线程都是操作系统通过CPU轮询以获取时间片,然后执行,不会让一条线程无限执行。在Java中获取当前线程如下

/**
 * @author kangming.ning
 * @date 2023-02-14 17:20
 * @since 1.0
 **/
public class CurrentThreadDemo {

    public static void main(String[] args) {
        new Thread("custom thread") {
            @Override
            public void run() {
                System.out.println("当前线程:" + Thread.currentThread().getName());
            }
        }.start();

        System.out.println("当前线程:" + Thread.currentThread().getName());
    }
}

打印如下

当前线程:main
当前线程:custom thread

可见,同样是Thread.currentThread().getName(),但获取到的线程名称不同。进入源码发现是一个本地方法,也就是获取当前线程是由虚拟机来实现的。

    /**
     * Returns a reference to the currently executing thread object.
     *
     * @return  the currently executing thread.
     */
    public static native Thread currentThread();

从注释也不难看出,调用这个本地方法会返回一个当前CPU正在执行的线程的对象引用,因线程也是一个对象,不同对象所记录的信息自然是不同的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值