java基础 多线程概念

Posted xzj_2013

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java基础 多线程概念相关的知识,希望对你有一定的参考价值。

多线程中的基础概念

CPU核心数和线程数的关系

 提及目前广泛的计算机设备,无论是电脑还是手机,都离不开一个设备CPU;
 CPU是一个需要考虑的核心因素,因为它决定了电脑或者手机的性能等级。CPU从早期的单核,发展到现在的双核,
 多核。CPU除了核心数之外,还有线程数之说;
 
 简单地说,CPU的核心数是指物理上,也就是硬件上存在着几个核心;
 而线程数是一种逻辑的概念,简单地说,就是模拟出的CPU核心数
 正常来讲 核心数和线程数 是一个1:1的关系  
 举例来说,如果你的电脑的是4核的CPU,那么表示你的电脑可以同时运行4个线程;
 
 但随着技术的发展,Intel提供了一种超线程技术,同个这种技术可以把一个物理CPU模拟成2个逻辑的CPU单元,
 可以让我们的设备在4核的条件下,同时运行8个线程;

时间片轮转机制(RR调度)

按照CPU核心数和线程数的关系,理论上我们应该只能同时运行4个线程,但是在开发项目中,我们可以想开启几个线程就开启多少个线程,那是怎么做到呢?
这是因为操作系统提供了一种CPU时间片轮转机制
时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间
比如我们只有一个CPU,但是我们有数百个线程要运行,我们感觉好像这些线程在同时运行一样,实质上我们是将CPU的运行时间分片,每个分片分给不同的线程,而每个分片的时间极短,几乎让我们感知不出;

 百度百科对CPU时间片轮转机制原理解释如下:
 如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结来,
 则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾
 时间片轮转调度中唯一有趣的一点是时间片的长度。从一个进程切换到另一个进程是需要定时间的,包括保存和
 装入寄存器值及内存映像,更新各种表格和队列等。假如进程切( processwitch),有时称为上下文切换( context 
 switch),需要5ms,再假设时间片设为20ms,则在做完20ms有用的工作之后,CPU将花费5ms来进行进程切换。
 CPU时间的20%被浪费在了管理开销上了。

 而平时我们电脑经常可以看到有CPU消耗不到1%,有时候突然增长到70%-80%,然后马上降下来,这就是CPU的时
 间轮转机制在起作用,高值时可能是一个很耗CPU的程序在执行,低峰时是一个不太耗CPU的程序在运行;所以CPU
 的时间会呈一个高低起伏的图形。

  时间轮转片具体时长多少?这和操作系统也CPU的时间周期有关

  时间片轮转并不是没有代价的;
  可以想象,当时间片轮转其作用时,我们准备退出当前线程时,会将当前线程的一些数据 保存到内存或者磁盘中,
  以便下一次CPU分发给这个线程时我们能够重新读取数据继续执行
  保存结束后,会将CPU空闲出来交给另一个线程执行,如果这个线程是曾经运行过的,那么它会将保留的数据
  和状态从内存或者硬盘中重新加载到它的工作空间中;
  显然,这么一个切换的过程,就包括了线程数据的存和取过程,这必然会耗费时间
  这种情况在操作系统中有个专业术语叫上下文切换
  一次上下文切换 大概会消耗20000个CPU的时间周期
  一般来说CPU执行一个1+1的操作 消耗的时间  就刚好是一个CPU的时间周期

进程和线程的概念

进程:进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、磁盘IO等,
      用我们常见的手机上的每一个app都是一个进程
     
      同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。
      进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,
      进程是系统进行资源分配和调度的一个独立单位。
      
      进程可以分为系统进程和用户进程。
      凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身,
      用户进程就是所有由你启动的进程
      
线程:线程是CPU调度的最小单位,必须依赖于进程而存在 也就是说进程>线程
       线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。
       线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),
       但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源;
       为什么不能大量创建线程?
       因为新建线程会在内存中开辟栈空间  缺省1M,如果太多 内存消耗太多--所以操作系统会对线程数有限制


注意:在OS(操作系统层面)对线程数做了限制
      Linux 最大线程数不能炒锅1000
      Windows  最大线程数不能炒过2000

并行和并发的概念

并行:  指应用能够同时执行不同的任务
      		这个和CPU核心数一般保持一致,如果使用了超线程技术,那么就是1:2的关系
      		也就是说不使用超线程技术,4核CPU,能并行执行4个线程
      		使用超线程技术,4核CPU,能并行执行8个线程
并发: 指应用能够交替执行不同的任务,也就是在单位时间段内能够执行的任务量
            比如在单位时间内能执行多少个线程
            比如在单位时间通过这条车道的汽车数量等等
            实现并发技术相当复杂,最容易理解的是“时间片轮转进程调度算法”。

两者区别:一个是交替执行,一个是同时执行.

认识Java中的线程

Java里的程序天生就是多线程的

为什么这么说呢?
我们通过一个简单的java程序就可以说明

package com.eric.thread;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class threadDemo 
    public static void main(String[] args) 
   		//虚拟机线程管理的接口
        ThreadMXBean bean =  ManagementFactory.getThreadMXBean();
        //获取线程信息
        ThreadInfo[] threadInfo =  bean.dumpAllThreads(false,false);
        for (ThreadInfo info : threadInfo) 
            System.out.println("["+info.getThreadId()+"]"+"-->"+info.getThreadName());
        
    


运行结果:
[6]-->Monitor Ctrl-Break //监控Ctrl-Break中断信号的
[5]-->Attach Listener //内存dump,线程dump,类信息统计,获取系统属性等
[4]-->Signal Dispatcher //分发处理发送给JVM信号的线程
[3]-->Finalizer // 参考Object中的finalize()  用于资源释放  就是由这个线程调用
[2]-->Reference Handler //清除Reference的线程
[1]-->main  //我们的主线程 

这个程序就是一个简单的获取进程中的线程组的java代码,就这个简单的代码 虚拟机启动了6个线程;
没有GC线程的原因是GC并不是程序启动就立刻会启动的 而是只有当虚拟机发现内存不够需要GC启动去释放资源的时候才会拉起;

线程的启动

启动线程的方式有2种 extends Thread 和 implements Runnable

  1. X extends Thread 通过X.start()
   public static void main(String[] args) 
        UserThread user = new UserThread();
        user.start();
    
    static class UserThread extends Thread
        @Override
        public void run() 
            System.out.println("UserThread");
        
    
    运行结果:UserThread
  1. X implements Runnable 然后交给线程去启动
   public static void main(String[] args) 
        PeopleThread propleThread = new PeopleThread();
         Thread thread = new Thread(propleThread);
         thread.start();
    

    static class PeopleThread implements Runnable
        @Override
        public void run() 
            System.out.println("propleThread");
        
    
        运行结果:propleThread
  1. X implements Callable 然后交给线程去启动
    其实质还是实现Runnable接口 也归属于implements Runnable的启动方式
public static void main(String[] args) 
        UserCall call = new UserCall();
        FutureTask<String> future = new FutureTask<>(call);
        Thread thread = new Thread(future);
        thread.start();
        try 
            System.out.println(future.get());
         catch (InterruptedException e) 
            e.printStackTrace();
         catch (ExecutionException e) 
            e.printStackTrace();
        
    

    //实现Callable接口 允许有返回值
    static class UserCall implements Callable<String>
        @Override
        public String call() throws Exception 
            System.out.println("UserCall");
            return "call return eric";
        
    

	 运行结果:UserCall
			  call return eric

第1、2方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果

  1. 理解Callable、Future和FutureTask
    Runnable是一个接口,在它里面只声明了一个run()方法,由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
    Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call(),这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
    Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
    因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
    FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

  2. 注意
    只有extends Thread才是对线程的唯一抽象
    实现Runnable的都是对任务的抽象
    也就是说 只有extends Thread才能真正的表示一个线程,任务交给线程执行;

以上是关于java基础 多线程概念的主要内容,如果未能解决你的问题,请参考以下文章

初学Java多线程的基本概念

java核心技术-多线程基础

Java基础总结--多线程总结1

java多线程笔记 目录

多线程基础

java核心技术-多线程基础