多线程详解

Posted Neil_Wesley

tags:

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

[多线程详解(一)](http://www.neilx.com)

一、概念准备

1、进程

(1)直译:正在进行中的程序

(2)解释:执行一个程序时,会在内存中为程序开辟空间,这个空间就是一个进程。

(3)注意:一个进程中不可能没有线程,只有有了线程才能执行; 进程只开辟空间,并不执行,执行的是线程.

2、进程

(1)定义:就是进程中一个负责执行的控制单元(执行路径)

(3)注意:一个进程中可以有多个执行路径,称为多线程;一个进程中至少有一个线程

3、任务

开启多个线程,是为了同时运行多个内容,这个内容就是任务

二、多线程的初步认识

jvm(java虚拟机)中的多线程解析

jvm启动时,就启动了多个线程:

其中至少有两个线程:执行main函数的主线程和负责垃圾回收的线程

解释:在执行main中任务的时候,会开启一条线程,当运行垃圾越来越多,又会有垃圾回收任务,所以垃圾回收线程开启。其中,main方法任务的代码都定义主函数代码中。

三、垃圾回收线程和主函数线程示例

1、垃圾回收线程

class person extends Object
{

    public void finalize()
    {
        System.out.println("hahaha!");

    }

}
public class demo1{
  public static void main(String[] args){
    person p1=new person();
    person p2=new person();
  //调用gc方法,运行垃圾回收期
    System.gc();
    System.out.println("hehehe!");
}
}

运行结果:

![垃圾回收线程输出结果](https://img-blog.csdn.net/20160615152724948)

解释:垃圾回收机制是虚拟机调用Object类的finalize方法。当垃圾回收器已经确定不存在对某对象的引用时,垃圾回收器自动调用fianlize方法,子类重写finalize方法,执行回收动作。代码如上

System.gc();是垃圾回收程序,调用后,不定时的执行垃圾回收

小插曲:System.gc()与finalize()的辨析:

(1)gc()只能清除在堆上分配的内存(纯java语言的所有对象都在堆上使用new分配内存),而不能清除栈上分配的内存(当使用JNI技术时,可能会在栈上分配内存,例如java调用c程序,而该c程序使用malloc分配内存时)。因此,如果某些对象被分配了栈上的内存区域,那gc就管不着了,对这样的对象进行内存回收就要靠finalize().

举个例子来说,当java调用非java方法时(这种方法可能是c或是c++的),在非java代码内部也许调用了c的malloc()函数来分配内存,而且除非调用那个了free(),否则不会释放内存(因为free()是c的函数),这个时候要进行释放内存的工作,gc是不起作用的,因而需要在finalize()内部的一个固有方法调用它(free()).

(2)finalize的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存.所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作.

(3)那么什么时候调用finalize()呢?

  • 《1》所有对象被gc()时自动调用,比如运行System.gc()的时候.
  • 《2》程序退出时为每个对象调用一次finalize方法
  • 《3》显式的调用finalize方法

(4)除此以外,正常情况下,当某个对象被系统收集为无用信息的时候,finalize()将被自动调用,但是jvm不保证finalize()一定被调用,也就是说,finalize()的调用是不确定的,这也就是为什么sun不提倡使用finalize()的原因. 简单来讲,finalize()是在对象被GC回收前会调用的方法,而System.gc()强制GC开始回收工作纠正,不是强制,是建议,具体执行要看GC的意思简单地说,调用了 System.gc() 之后,java 在内存回收过程中就会调用那些要被回收的对象的 finalize() 方法。

2、主线程的运行示例

class person extends Object
{  
  int i;    
    private String name;
    person(String name)
    {
        this.name=name;
    }
    public void show()
    {
        for(i=0;i<=10;i++)
        {
            System.out.println(name+"x="+i);
        }
    }

}

public class demo1{
public static void main(String[] args){
    person p1=new person("zhangsan");
    person p2=new person("lisi");
    p1.show();
    p2.show();
    System.out.println("hehehe!");
}
}

执行流程:p1.show执行—结束后——出栈——p2.show执行——执行完毕—出栈

此时,在执行p1.show时,必须执行完张三后,李四才可以执行,如果想让张三执行的同时,李四也执行呢?——使用多线程

四、创建线程

从上面的示例中可以看出,张三show、李四show是在一条主线程上运行,所以输出的结果中是按照一定的顺序打印输出的。这种方法,程序运行的效率较低,如果我们可以让两个对象“同时”运行的话,那么效率会得到极大的提升。

注意:此处说的同时,是依赖于多核处理器运行情况下的同时。我们都知道,单核下所谓的多线程是依赖于cpu高速处理下的程序往返切换而造成的“伪多线程”“伪同时”

在说明线程创建方法之前,我们不妨首先思考一下上述的主线程是如何创建的?

在上面的示例中,我们并没有刻意的添加代码,但是主线程就别创建了,由此,我们可以知道是java虚拟机默认为我们创建了主线程。那么java虚拟机又是如何创建的呢?其实是java虚拟机以来操作系统的一些功能,为我们创建的。说道这里大家可能有些晕了,下面我们来梳理一下思路(学习嘛,就是先把书本学厚,再学薄的过程)

jvm→→→操作系统→→→创建线程

所以在java中我们可以利用对象,通过虚拟机连接底层的操作系统进行线程的创建

方法一:继承Thread类,实现线程的创建
  1. 1、定义一个类,继承Thread类
  1. 2、覆盖Thread类中的run方法

示例:

//继承Thread
class person extends Thread
{ 
  int i;
    private String name;
    person(String name)
    {
        this.name=name;
    }
//调用run方法
    public void run()
    {
        show();
    }

    public void show()
    {
        for(i=0;i<=10;i++)
        {
            System.out.println(name+"x=“+i+”…name=“+getName());
        }
    }
}
public class demo1{
public static void main(String[] args){
    person p1=new person("zhangsan");
    person p2=new person("lisi”);
//开启多线程并调用
    p1.start();
    p2.start();
}
}

可能会有人比较奇怪,既然继承Thread类后就已经创建了线程。run()又是什么鬼???Thread类中为什么会有run()方法呢??请听我慢慢道来

这就需要我们用到之前的关于进程、线程、任务的概念了。

//继承Thread
class person extends Thread
{   int i;
    private String name;
    person(String name)     
    {
        this.name=name;
    }
//调用run方法
    public void run()
    {
        show();
    }

    public void show()
    {
        for(i=0;i<=10;i++)
        {
            System.out.println(name+"x=“+i+”…name=“+getName());
        }
    }
}
public class demo1{
  public static void main(String[] args){
    person p1=new person("zhangsan");
    person p2=new person("lisi”);
//未开启多线程
    p1.run();
    p2.run();
}
}

运行结果:

这里写图片描述

结果是显而易见的,由于未使用start方法所以并没有实现多线程的效果。这也是创建多线程失败的一个原因。

六、显示线程的名字

当我们创建线程成功后,如何直观的看出正在运行的是哪一个线程呢?这就需要我们直接打印输出线程的名字————使用getName()方法

class person extends Thread
{ 
  int i;
    private String name;
    person(String name)
    {   
        this.name=name;
    }
    public void run()
    {
        show();
    }

    public void show()
    {
        for(i=0;i<=10;i++)
        {
            System.out.println(name+"x="+i+"...name="+getName());
        }
    }
}
public class demo1{
public static void main(String[] args){
    person p1=new person("zhangsan");
    person p2=new person("lisi”);
//开启线程
  p1.start();
    p2.start();
}
}

运行结果:

这里写图片描述

此时,我们会发现打印输出的结果中有线程的名字:Thread-0、Thread-1

下面我们修改一下代码,不开启线程,看看会有什么发生??????

class person extends Thread
{ 
  int i;
    private String name;
    person(String name)
    {   
        this.name=name;
    }   
    public void run()
    {
        show();
    }

    public void show()
    {
        for(i=0;i<=10;i++)
        {
            System.out.println(name+"x="+i+"...name="+getName());
        }
    }
}
public class demo1{
   public static void main(String[] args){
   person p1=new person("zhangsan");
     person p2=new person("lisi”);
//注意此时没有开启线程,仅调用run函数,没有使用线程
  p1.run();
    p2.run();
}
}

运行结果:

这里写图片描述

显而易见:输出的结果中,仍然有Thread-0、Thread-1(即线程的名字)

基于上述情况,让我们重新认识一下getName()方法吧!!!!

由上述可知,我们没有启动多线程,但是运行结果却打印输出了线程的名字。其实,在线程创建时,线程就拥有了自己的名字。也就是说此时得到的是对象线程的名字,而不是正在运行的线程名字 这样做的好处是,一旦产生就拥有唯一标识自己身份的ID,便于以后的管理和使用,减少错误情况的产生。就如同我们出生后,往往会尽快的上户口,如果在打预防针的时候,你还没有上户口,那么就会出出现等等问题。

那么如何获得正在运行的线程名字呢?

七、显示正在运行线程的名字

使用Thread类下的currentThread()方法

示例如下

class person extends Thread
{  
  int i;
    private String name;
    person(String name)
    {
        this.name=name;
    }   
    public void run()
    {
        show();
    }

    public void show()
    {
        for(i=0;i<=10;i++)
        {
            System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
        }
    }
}
public class demo1{
  public static void main(String[] args){   
    person p1=new person("zhangsan");
    person p2=new person("lisi");
    p1.start();
    p2.start();
    System.out.println("出来吧,name="+Thread.currentThread().getName());
}
}

运行结果:

这里写图片描述

人类的欲望总是无休止的,现在我们已经输出了正在运行的线程名字,但是为了提高阅读的直观性,我们能不能将线程的名字与对象的名字结合起来呢?比如给一个线程取名字叫张三,一个叫李四

如何给线程取名字呢?

class person extends Thread
{ 
  int i;
    private String name;
    person(String name)
    {   
//给线程命名,使用super
        super(name);
        this.name=name;
    }
    public void run()
    {
        show();
    }

    public void show()
    {
        for(i=0;i<=10;i++)
        {
            System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
        }
    }
}
public class demo1{
  public static void main(String[] args){
    person p1=new person("zhangsan");
    person p2=new person("lisi");
    p1.start();
    p2.start();
      System.out.println("出来吧,name="+Thread.currentThread().getName());
}
}

运行结果:

这里写图片描述

未完待续,这只是创建线程的第一种方法,第二种方法及其他介绍,请阅读“多线程(二)”

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

20160226.CCPP体系详解(0036天)

多线程 Thread 线程同步 synchronized

LINUX操作系统知识:进程与线程详解

JAVA多线程synchronized详解

多个用户访问同一段代码

20160227.CCPP体系详解(0037天)