多线程

线程是进程的基本执行单元,一个进程的所有任务都在线程中执行

进程要想执行任务,必须得有线程,进程至少要有一条线程

程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程
2:进程定义

进程是指在系统中正在运行的一个应用程序

每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存
3:进程与线程的区别

地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。

一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程

执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

线程是处理器调度的基本单位,但是进程不是。
4:多线程的意义

优点

  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率(CPU,内存)
  • 线程上的任务执行完成后,线程会自动销毁

缺点

  • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
  • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 线程越多,CPU 在调用线程上的开销就越大
  • 程序设计更加复杂,比如线程间的通信、多线程的数据共享

java是多线程的编程语言

jdk1.5之后开始有线程池,但Thread从1.0就开始了

java.lang.Thread

Thread

class MyThread extends Thread{//线程的主体类
    private String title;
     MyThread(String title){
        this.title=title;
    }
    @Override
    public void run(){
        for (int x=0;x<10000;x++){
            System.out.println(this.title+"运行,x"+x);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //其实start(底层还是会调用run方法)
        new MyThread("线程1").start();
        new MyThread("线程2").start();
        new MyThread("线程3").start();

    }
}

接下来来看看

start函数的源码

public synchronized void start() {
        /**
         * *通知组该线程即将开始,
         以便可以将其添加到组的线程列表*中
         并且该组的未启动计数可以减少。
         */
        if (threadStatus != 0)
            //如果之前启动过这个进程,则会抛出一个运行时异常
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

try{}中调用了start0();方法,他的定义如下

private native void start0();

再java程序执行的过程中,考虑到对于不同层次开发者的需求,所以支持又本地的操作系统的函数,JNI(Java Native Inteface)技术,但是java开发过程之中并不推荐这样使用,利用这个即使可以使用一些操作系统底层函数进行一些特殊的处理,再Thread类里面提供的start0()就表示需要将此方法以来于不同的操作系统实现.

  1. 一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现。

也可以实现Runnable的方式来进行,这样的好处是避免了单继承的缺陷(其实Thread类也是继承了Runnable接口)

class MyThread implements Runnable {//线程的主体类
    private String title;
     MyThread(String title){
        this.title=title;
    }
    @Override
    public void run(){
        for (int x=0;x<10000;x++){
            System.out.println(this.title+"运行,x"+x);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //其实start(底层还是会调用run方法)
        Thread thread1 = new Thread(new MyThread("线程1"));
        Thread thread2 = new Thread(new MyThread("线程1"));
        Thread thread3 = new Thread(new MyThread("线程1"));
        thread1.start();//启动多线程
        thread2.start();//启动多线程
        thread3.start();//启动多线程
    }
}

lambda表达式运行

//lambda
public class Main {
    public static void main(String[] args) {
        for (int x=0;x<3;x++){
            String title="线程对象"+x;
            Runnable run=()->{
                for(int y=0;y<100;y++){
                    System.out.println(title+"运行,y="+y);
                }
            };
            new Thread(run).start();
        }
    }
}

多线程 设计模式当中,使用了代理设计结构,用户自定义的线程主体只是负责项目核心功能的实现,多线程的任务全部交给Thread类来处理

当通过Shead类传递了一个Runnable接口对象的时候,name该接口对象将被Thread类中的target属性所保存,在start执行的时候,会调用Thread()类当中的run方法,而这个run()方法调用Runnable接口子类被复写过的run方法.

多线程开发的本质是,多个线程可以进行同一资源的抢占.那么Thread主要描述的是线程,而资源的描述是通过Runnable完成的.

callable

有一点不会改变,想要启动多线程类,必须使用Thread类当中的start方法

jdk1.5之后提出了一个新的线程实现接口,java.util.Callable接口

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

class MyThread implements Callable <String>{

    @Override
    public String call()  {
        for (int x=0;x<10;x++){
            System.out.println("线程执行:x="+x);
        }
        return "线程执行完毕";
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        //FutureTask这个类实现了Runnable
        FutureTask<String>task=new FutureTask<>(new MyThread());
        new Thread(task).start();
        System.out.println(task.get());
    }
}

Runnable与callable的区别

Runnable是在jdk1.0的时候提出来的多线程的实现接口,而Callable是在jdk1.5之后提出来的

java.lang.Runnable接口之中只提供有一个run()方法,并且没有返回值

java.util.concyrrent.Callable接口中提供的call()方法可以有返回值

线程操作状态

想要实现多线程必须在主线程中创建新的对象.任何线程一半都具有五种状态,即创建,就绪,运行,堵塞和终止

创建状态

在程序调用构造方法创建一个线程对象之后,新的线程对象便处于新建状态,此时,它已经有相应的内存空间和其他资源,但还处于不可运行状态.新建一个线程对象可采用Thread类的构造方法来实现,

就绪状态

,新建线程对象后,调用该线程的start()方法就可以启动线程.当线程启动时,线程就会进入就绪状态,此时线程将进入线程队列排队,等待cpu服务,这表明它已经具备了运行条件

运行状态

当就绪状态的线程在默写特殊的情况下,如被人为挂起,或需要执行耗时的输入输出操作的时候,将让出cpu 并暂时终止自己的执行进入堵塞状态.在可执行状态下,如果调用sleep(),supend(),wait()等方法,线程都将进入堵塞状态,堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程才可以转入就绪状态

终止状态

线程调用stop()方法时或run()方法执行结束后,就处于终止运行状态.处于终止状态的线程不具有继续运行的能力

Last modification:April 21, 2022
如果觉得我的文章对你有用,请随意赞赏