Java并发——线程介绍

前言:

互联网时代已经发展到了现在。从以前只考虑小流量到现在不得不去考虑高并发的问题。扯到了高并发的问题就要扯到线程的问题。你是否问过自己,你真正了解线程吗?还是你只知道一些其他博客里写的使用方法。下面让我们先从线程的一些基础开始讲解并发这一个知识体系。

一、线程是什么?

首先我们要明白线程是什么,下面是我从百度百科摘过来的概念:

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

这个概念比较晦涩难懂,我们可以这样理解线程表示的就是一条单独的执行流,一个程序可以包括很多子任务,而这些子任务就是一条线程。这个线程有自己的程序执行计数器,也有自己的栈。程序就是一个进程。平常我们所说的多线程不是很多个程序在一起运行,那叫多进程。多线程则是一个程序里面很多个任务在同时执行。(注意:通常多线程在OS底层是通过时间片分配来实现多线程的,实际上每一个时间片只运行了一条线程)

二、怎么创建线程?

1、继承Thread

public class StudyThread extends Thread {     
    @Override 
    public void run() {
        System.out.println("学习多线程");
    }
 }

你可以理解run方法就是类似于单线程中的main函数,如果这个线程执行,那么就会从run方法内的第一条语句开始执行到结束。但是线程现在就开始执行了吗?不是的,要让线程启动必须要先创建出StudyThread这个类的对象,而后开始调用start方法。注意:如果我们没有调用start方法,而是直接调用run方法,那你就可以认为只是调用了一个普通方法。程序并没有达到多线程,还是单线程。如果调用了start方法,程序就有了两条执行流,新的执行run方法,旧的就继续执行main函数。

如果是在单CPU的机器上,同一时刻只能有一个线程执行,而多CPU的机器同一时刻就可以有多个线程同时执行。当所有线程执行完后程序才退出。

2、实现Runnable接口
  像我们之前说的设计模式,很多设计模式都涉及到继承,但是Java中只支持单线程。所以我们通常不会用上面的方式,而是去实现Runnable接口。

public class StudyRunnable implements Runnable {    
    @Override
    public void run() {
        System.out.println("hello");
    }
 }

其余的和继承Thread一样。但是有一点不同的就是

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

我们需要创建一个线程类,并且传Runnable对象进去。说到底我们操作的还是Thread类,不过是将继承去掉罢了。

三、线程有哪些方法和属性?

接下来我们直接看源码来解释

public class Thread implements Runnable {
    //线程初始化入口       
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    //初始化方法
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    
    //真正的初始化方法
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
 g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
}

我们注意,在真正的初始化方法中,有以下这些属性会被初始化:

1、Tread类中重要的属性

  • 1.1、name
      线程的名字默认是Thread-后跟一个编号,可以使用默认,通过getName方法得到,也可以通过setName方法自定义名字。

  • 1.2、group
      所属线程组,一个线程必然有所属线程组。Thread与ThradGroup的关系就像元素与集合的关系。

  • 1.3、daemon
      前面我们说过,启动线程就会启动一条单独的执行流,如果整个线程都结束,那么程序就会结束,但是当整个程序只剩下daemon的时候,程序也会退出。daemon线程一般是其他线程的辅助线程,主要作用是负责垃圾回收。

  • 1.4、priority
      线程中的优先级,从1到10,优先级逐渐升高,默认为5。操作方法是用getPriority,setPriority

  • 1.5、stackSize
      预期堆栈大小,不指定默认为0,0代表忽略这个属性。与平台相关,不建议使用该属性。

2、Tread类中一些重要的方法

  • 2.1、getState()
      得到当前线程的线程状态。Thread.State是枚举类型,有NEW(没有调用start的线程状态)、RUNNABLE(调用start后线程在执行run方法且没有阻塞的状态)、BLOCKED(线程被阻塞)、WAITING(线程被阻塞)、TIMED_WAITING(线程被阻塞)、TERMINATED(线程运行结束后的状态) 5种。

  • 2.2、isAlive()
      线程被启动后,run方法结束前,线程都是活的。

  • 2.3、sleep(),yield(),join()
      上面三个方法都可以让控制线程的执行,sleep是让线程睡眠的方法,yield是告诉操作系统调用该方法的线程不急着执行,可以先让其他线程执行,当然操作系统可能接受yield的建议,也有可能不接受。join方法则是将其他线程中断,让调用该方法的线程先执行完再执行其他线程,如果这个执行的过程中被中断,就会抛出Interrupted-Exception

四、总结

这篇文章只是先简单介绍了一下线程,让我们知道了线程的创建方法,线程初始化的过程,以及线程中一些常用属性和方法。但是这只是Java并发的皮毛。后续会介绍更深入的知识点。


文末彩蛋

针对于上面所涉及到的知识点我总结出了有1到5年开发经验的程序员在面试中涉及到的绝大部分架构面试题及答案做成了文档和架构视频资料免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习,也可以关注我一下以后会有更多干货分享。

资料获取方式: QQ群搜索“708-701-457” 即可免费领取

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42982923/article/details/89193176