Java多线程入门(一)使用皇上宠幸妃子的方式理解多线程

其实吧,从大学的时候学java和操作系统的时候就接触多进程,线程的概念,心理也大概有个印象,但是没怎么深入了解或者体会过,现在工作了,需要更透彻的去在实践中体会技术点的使用,那么小哥哥就开一次车,打家一起慢悠悠的学习一下java的多线程的基本知识和应用。

首先线程,进程都是操作系统中的概念,进程的定义是:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

可以说是很抽象了,简单的说核心要素有两点:一是“进”,二是“程”,“进”是一个动词,“程”是一个名词,那么就有两个层面去理解,那么进程就一定是个程序的尸体,啊不对,是实体!不是死的哈,实体啥意思,就是有存在的,不是概念上的,不只是一个定义说明,而是有具体的实现,有血有肉的包子,另一方面,进程是要运行在计算机上的,也就是活着的,当然他运行结束就死掉了,但和不运行不一样,要能运行。或者说是一个组织吧,专门负责某种活动的组织,要有人,要真的去组织活动而不是虚张声势的,而进程运行在计算机上是要搞事情的,那么那就要有权利,获取cpu的处理资源就相当于获得了尚方宝剑,圣旨一样,就可以工作了,而他也会调度各种计算机的资源以及宝贵的内存资源。那么进程就是计算机的基本的资源调度单位了,就相当于一个帝国最基本的下属组织,古代可能就是区啊,县啊,现在可能中国就是个村委会吧或者街道党组织。

那么线程是什么呢?线程的定义:
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

看的头晕的话听我讲解吧,不过还是推荐把定义好好琢磨透,上边我们把进程比作一个组织,那么线程就是这个组织中的个体成员,她可能是一个个体也可能是多个个体,他们就是组织组织活动时具体的活动实施操作者,他们不占有资源,资源都是组织的,他们只负责干活。他们之间相互相对独立,并发运行就是可以老大,老二,老三一起出去挑水喝,嗯嗯大概如此。
理解了线程,进程的概念那么对应到java中那就是一个jar包的事,如果不行那就再来一个jar包,哈哈哈,java是典型的面向对象语言,那么自然线程就被封装成了一个对象,这个对象就是Thread对象,表现在java中那就是一个类,那么要想获得一个线程对象有哪些方式呢,java大佬可不会那么保守,自然会有多种方法,一个就是可以new一个Thread对象,或者使用Thread类的子类,二是实现Runable接口

那么你就获得了一个线程对象,一旦她被生下来,就是处在创建状态的,执行了start()方法之后就编程了就绪状态,直到系统调度轮到她的时候运行run()方法才进入运行状态,执行完就进入了死亡状态。你可以理解成每个线程都是皇宫里的妃子,而cpu就是皇上,每个妃子出生是创建状态,被选入皇宫变为就绪状态,而一旦皇上今晚宠幸她(嘿嘿嘿)那就变成了运行状态,而运行状态的复杂就和妃子一样,有的妃子一次之后就进入了死亡状态被打入冷宫,而有的则再次进入就绪状态这种可能就是贵妃,而有的进入了阻塞状态,好比皇后和一般的妃子,一旦皇上哪天有兴致了对她们说今晚寡人到你那去,那么就从阻塞变为了就绪状态,然后就不可描述的运行状态了。。哈哈你就这么理解吧。
上个图吧
这里写图片描述

接下来就是具体的小例子:
方式一,实现Runable接口:

package com.nms.common;

public class threadTest implements  Runnable {
    private Thread t;
    private String threadName;
    /**重写构造方法*/
    threadTest( String name) {
        threadName = name;
       System.out.println("新线程创建: " +  threadName );
    }

    @Override
    public void run () {
        // TODO Auto-generated method stub
        System.out.println("正在运行线程:" +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("线程: " + threadName + ",正在倒计时: " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("线程 " +  threadName + " 被打断了");
      }
      System.out.println("线程 " +  threadName + " 结束啦.");
    }
      public void start () {
          System.out.println("线程就绪啦: " +  threadName );
          if (t == null) {
             t = new Thread (this, threadName);
             t.start ();
          }
       }
}

测试运行(1)调用start()方法:

public static void main (String args[]) {       
        threadTest test1 = new threadTest("吉尔伽美什");
        test1.start();
        threadTest test2 = new threadTest("鲁鲁克");
        test2.start();
    }

运行结果

这里写图片描述

测试运行(2)直接调用run()方法:

扫描二维码关注公众号,回复: 2685784 查看本文章
    public static void main (String args[]) {       
        threadTest test1 = new threadTest("吉尔伽美什");
        test1.run();
        threadTest test2 = new threadTest("鲁鲁克");
        test1.run();
    }

运行结果:

这里写图片描述
从上面看出直接调用run方法()和调用start()方法的区别。

方式二,继承Thread类:

package com.nms.common;

public class threadTest2 extends Thread {
    private Thread t;
    private String threadName;
    /**重写构造方法*/
    threadTest2( String name) {
        threadName = name;
       System.out.println("新线程创建: " +  threadName );
    }

    @Override
    public void run () {
        // TODO Auto-generated method stub
        System.out.println("正在运行线程:" +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("线程: " + threadName + ",正在倒计时: " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("线程 " +  threadName + " 被打断了");
      }
      System.out.println("线程 " +  threadName + " 结束啦.");
    }
      public void start () {
          System.out.println("线程就绪啦: " +  threadName );
          if (t == null) {
             t = new Thread (this, threadName);
             t.start ();
          }
       }
}

测试运行:

public static void main (String args[]) {       
        threadTest2 test1 = new threadTest2("步枪消音器");
        test1.start();
        threadTest2 test2 = new threadTest2("八倍镜");
        test1.start();
    }

运行结果:

这里写图片描述

方法三:通过 Callable 和 Future 创建线程
1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

package com.nms.common;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class threadTest3 implements Callable<Integer> {

     public static void main (String[] args)  
    {  
          threadTest3 ctt = new threadTest3();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for(int i = 0;i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft,"有返回值的线程").start();  
            }  
        }  
        try  
        {  
            System.out.println("子线程的返回值:"+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  

    }
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  

}

运行结果:

这里写图片描述
这里写图片描述
这里写图片描述
从结果可以看出,循环变量增加的线程先开始运行并且比有返回值的线程运行的快。
创建线程的三种方式的对比
1. 采用实现 Runnable、Callable 接口的方式创见多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
线程的几个主要概念
在多线程编程时,你需要了解以下几个概念:
线程同步
线程间通信
线程死锁
线程控制:挂起、停止和恢复
多线程的使用
有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!

猜你喜欢

转载自blog.csdn.net/Phoenix_smf/article/details/79651464