JAVA多线程深入1

目录

1.线程与进程

2.Thread类

2.1如何实现多线程?

2.2 Thread类概述

2.3利用继承Thread类的方式来创建新线程

2.4为啥调用start()方法,而不是run()方法?

3.实现Runable()接口

 3.1 public Thread(Runable target)

4.线程交互浅入

4.1不共享数据的情况

4.2共享数据的情况

4.4.方法currentThread()

4.5.方法isAlive()

4.6 sleep()方法

1.线程与进程

    进程可以看成是一段程序的执行过程。线程可以理解为在进程中独立运行的子任务,线程依托与进程而存在,一个进程中可以有多个线程,共用进程的资源,而进程负责向操作系统申请资源。因此先有进程,后有线程,进程之间相互独立。

   在上图中将进程定义为资源和指令执行序列的总和,这里可以将指令执行序列理解为程序。下图为进程的大致结构:

  注意:1.进程之间虽然是相互独立的。但进程与进程之间可以互相通信,例如使用Socket或这Http。

             2.进程拥有某些共享的资源,例如内存、端口等,供其内部的线程使用。

             3.进程是个很大的单位,不是轻量级的,因为进程的创建需要操作系统分配资源,会占用内存。

             4.虽然线程更轻,但线程上下文切换的时间成本更高。

             5.多线程可以提高CPU的利用率。

 图1.1 进程与线程

-------------------------------------------------------------------------------------------------------------------------------=

case1:同步:可以理解为排队执行。

case2:异步:不等任务执行完,直接执行下一个任务。(参考操作系统中的多道程序模型)

case2:多线程是异步的

 上图为进程状态图,前面说过,线程相当于进程里面独立运行的子任务。线程的结构大致如下图所示:

 -------------------------------------------------------------------------------------------------------------------------------

线程ID(线程标识符)。线程唯一的标识,同一个进·程内不同线程的ID不会重复。

线程名称。主要方便用户识别,用户可以指定线程的名字,如果没有指定,系统就会自动分配一个名称。(参考4.4.方法currentThread())

线程优先级。表示线程调度的优先级,优先级越高,获得CPU的执行机会越大。(这里是概率问题,不是优先级高的就一定比优先级低的先执行完)。

线程状态。表示当前线程的执行状态。为新建、就绪、运行、阻塞、结束等状态中的一种。(这个在Java的源码里面是被定义成了一个State的枚举类,如下图:)

************************************************************************************************************

因此,线程与进程的区别主要有以下几点:

(1) 线程是“进程代码段”的一次顺序执行流程。一个进程由一个或多个线程组成,一个进程至少有一个线程。

(2) 线程是CPU调度的最小单位。进程是操作系统分配资源的最小单位。线程的划分尺度小于进程,使得多线程程序的并发性高。

(3)线程是出于高并发的调度诉求从进程内部演进而来的。线程的出现既充分发挥了CPU的计算性能,又弥补了进程调度过于笨重的问题。

(4) 进程之间是相互独立的,但进程内部的各个线程之间并不完全独立。各个线程之间共享进程的方法区内存、堆内存、系统资源(文件句柄、系统信号等)。

(5)切换速度不同:线程上下文切换比进程上下文切换要快得多。所以,有的时候,线程也称为“轻量级进程”。

2.Thread类

一个进程在运行时至少会有一个线程在运行。如下 Java代码示例:

 这里调用的是main()函数,在控制台输出的是main,其实就是名为main的线程在执行main函数中的代码,main线程是有JVM创建的。

2.1如何实现多线程?

方法一:继承Thread类

方法二:实现Runable接口

2.2 Thread类概述

如上图,从源码看,Thread类实现了Runable接口,因此,他们具有多态关系,即可以这样创建对象:

 Runnable run=new Thread();

   但使用 继承Thread这种方式来创建新线程的弊端之一就在这里,由于Java只支持单继承,不支持多继承。所以,Thread不支持多继承。

   因此为了支持多继承效果,可以通过实现Runnable接口的方式,来创建新线程。

2.3利用继承Thread类的方式来创建新线程

示例代码如下:

package org.example.threadTest1.node1;
import java.lang.Thread;
public class TestThread1 extends Thread{
    @Override
    public void run(){
        super.run();
        System.out.println("Thread1");
    }
}

public class Test3 {
    public static void main(String[] args) {
        TestThread1 thread1=new TestThread1();
        thread1.start();
        System.out.println("******************************************");
    }
}

如上示例:使用继承Thread类这种方式来创建新线程时,应当重写run方法。上面的代码使用start()来启动一个线程(注意:不要调用run()方法),线程启动后,会自动调用线程对象中的run()方法,在run()方法里面可以放线程对象需要执行的任务,也就时线程执行任务的入口。

 如上图,是示例测试类的运行结果,但为什么会产生如上运行结果(如果程序顺序执行)?

   从运行结果来看,TestThread1类的run方法相对于系统输出”***********************“的执行时间晚,这是为啥呢?因为执行start()方法比较耗时。

    运行结果是概率事件, 方法start()执行耗时多的原因是内不执行了多个步骤,步骤如下:

     T1:通过JVM告诉系统创建Thread.

     T2:操作系统开辟内存,同时,使用Window SDK中的createThread()函数创建Thread的线程对象。

    T3:操作系统对Thread对象进行调度,以确定执行时机。

    T4:Thread在操作系统中被成功执行。

 线程执行的顺序具有随机性。多线程执行的随机性是因为每个线程被分配一个时间片,线程在获得时间片(时间片:CPU分配给各个程序的时间)后就执行任务。

注意:CPU在不同的线程上切换时是需要耗时的(这是一个异步过程),因此,并不是创建的线程越多,软件运行效率就越快。相反,线程数过多反而会降低软件的执行效率。

2.4为啥调用start()方法,而不是run()方法?

   注意前面的描述,线程的执行过程是异步,随机的。如果调用run()方法而不是start()方法这样就不是异步执行的了,而是同步执行,那么线程对象就不会交给线程规划器来处理,而是有main()方法来直接调用run()方法,也就是说必须要等到run()方法中的代码执行完毕后才可以执行后面的代码。

  因此,执行start()方法的顺序不代表是执行run()方法的顺序,方法run()是随机调用的。 

3.实现Runable()接口

   试想一下这种情况:

  如果想创建的线程类如果已经有一个父类了该怎么办?

   这时自然就不能选择继承Thread()类这种方式,因为Java不支持多继承。

   此时就需要实现Runable接口来解决问题。

   如上所述,使用继承Thread类的方法来开发多线程应用是有局限的,因为Java只支持单继承不支持多继承。

  因此,为了改变这种限制,更推荐使用实现Runable接口这种方式来实现多线程。

  如下是通过实现Runable接口来实现多线程的示例代码:

首先是A业务类:

public class AServer {
    public void a_server_method(){
        System.out.println("执行A业务的方法!");
    }
}

然后是B业务类,它继承自A业务类,这时,如果想在B业务类中实现多线程,就不能使用继承Thread类这种方式来实现,此时让他实现Runable接口,这样可以间接实现多继承的效果。如下:

public class BServer1 extends AServer implements Runnable{
    public void b_server_method(){
        System.out.println("执行B业务的方法!");
    }

    @Override
    public void run() {
        b_server_method();
    }
}

测试类与运行结果:

另外,使用实现Runable接口的方式还可以把“线程”与"任务"分离,实现解耦的效果。参考:

 Java多线程实践2-Thread类与Runable接口-CSDN博客

Thread代表进程,Runable表示可运行的任务,Runable里面包含Thread线程要执行的代码,这样处理可以实现多个Thread共用一个任务,也可以一个任务被多个线程共用。

 3.1 public Thread(Runable target)

这是Thread类的关键源码:

4.线程交互浅入

   自定义线程类中的实例变量针对其他线程有共享和不共享之分。

4.1不共享数据的情况

如下示例代码:


public class TestThread3 extends Thread{
    private int count=5;
    public  TestThread3(String name){
        super();
        this.setName(name); //将此设置为线程的名称
    }

    @Override
    public void run() {
        super.run();
        while (count>0){
            count--;
            System.out.println("由"+this.currentThread().getName()
            +"计算: count="+count);
        }
    }
}

  其运行测试类代码如下:

public class RunTest {
    public static void main(String[] args) {
        TestThread3 run1=new TestThread3("A");
        TestThread3 run2=new TestThread3("B");
        TestThread3 run3=new TestThread3("C");
        run1.start();
        run2.start();
        run3.start();
    }
}

其运行结果如下图所示:

 由此运行结果推断,每个线程都有各自的count变量,自己对自己的count的值进行减少。这种情况就是变量不共享的情况。此示例不存在多个线程访问同一个实例变量的情况。

 如果要实现多个线程访问一个实例变量的情况该怎么做呢?

4.2共享数据的情况

该情况就是多个线程可以访问同一个变量,如在实现投票业务,多个线程需要处理同一个人的票数;还有那种上商品秒杀业务中,需要处理统计商品商量。

下面代码示例演示共享数据的这种情况:

public class TestThread4 extends Thread{
    private int count=5;

    @Override
    public void run() {
        super.run();
            count--;
             System.out.println("由"+this.currentThread().getName()+"计算:count"+count);
    }

}

运行测试函数:

public class RunTest2 {
    public static void main(String[] args) {
        TestThread4 thread4=new TestThread4();

        Thread a=new Thread(thread4,"A");
        Thread b=new Thread(thread4,"B");
        Thread c=new Thread(thread4,"C");
        Thread d=new Thread(thread4,"D");
        Thread e=new Thread(thread4,"E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

运行结果:

 这里先埋一个坑,什么叫线程安全?什么叫非线程安全?

4.4.方法currentThread()

本方法返回代码端正在被哪个线程调用。具体实例见上面的示例中的代码。

4.5.方法isAlive()

本方法如字面意思,用于判断线程对象是否存活。参考:

为什么启动一个线程不直接调用run(),而要调用start()启动?_线程的启动是调run吗_司马万的博客-CSDN博客

该方法由native关键字修饰,所以,这个方法具体实现是用其他语言写的。

示例代码如下:

public class Test5Thread extends Thread {
    @Override
    public void run() {
        System.out.println("本线程是/否存活"+this.isAlive());
    }
}

 测试函数:

public class RunTest3 {
    public static void main(String[] args) {
        Test5Thread thread=new Test5Thread();

        System.out.println("begin(线程启动前)=="+thread.isAlive());
        thread.start();
        System.out.println("after(线程启动后)=="+thread.isAlive());
    }
}

运行结果如下:

本方法的作用是测试线程是否处于活动状态。那么什么是活动状态(注意:这个和运行状态是有区别的,可以与软件的生命周期进行对比)?及线程已经启动,但线程并未终止。要获取线程当前的确切状态,这需要调用getState()方法。

4.6 sleep()方法

本方法的作用,顾名思义,就是在指定毫秒数内让当前(正在执行的线程)线程休眠(暂停执行),这个线程是指this.currentThread()返回的线程。

示例代码如下:

public class TestThread6 extends Thread{
    @Override
    public void run() {
        super.run();
        System.out.println("run threadName="+this.currentThread().getName()+"begin");
        try {
            Thread.sleep(3000);
            System.out.println("run threadName="+this.currentThread().getName()+"end");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

测试类1(注意,这里调用的是run()方法,根据运行结果可以看出,此时是同步(顺序)运行的):

 

 测试类2:(这里调用的是start()方法)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值