java并发编程(一)——进程与线程

基本概念

进程:进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

一个应用程序至少对应着一个进程,对于一些应用程序,如浏览器或者QQ,允许启动多个同一应用程序,会对应多个进程。

每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是系统资源分配和调度的最小单位

线程:线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。如360安全卫士可以同时进行病毒查杀、加速、电脑清理等不同的子任务,每一个子任务都对应着一个线程。一个应用程序运行起来之后,一定会首先创建一个首要线程-,称作主线程。

一个线程必须拥有一个父进程。线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源(进程是系统资源分配和调度的最小单位)。它与父进程的其他线程共享该进程的堆和方法区资源。

线程是cpu调度的最小单位,是比进程更小的独立运行的单位。线程之间切换的开销小,所以线程也称为轻量级进程。

堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

进程与线程的区别:

1、线程和进程最大的不同在于各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。

2、一个进程中可以有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。

3、进程切换时消耗的资源大,线程切换消耗的资源较小。

4、进程是系统资源分配和调度的最小单位,线程是CPU调度的最小单位。

 线程的创建

java中创建线程有两种方式,一种是继承Thread类,第二种是实现Runnable接口。继承Thread类是将线程和任务合在一起,实现Runable接口是将线程和任务分开。

/**继承Thread类
 * 1、创建一个类继承Thread类,重写run方法,run方法中的内容即是线程需要执行的任务
* 2、创建子类的实例,即创建了一个线程对象 * 3、调用该线程对象的start方法启动线程,会使线程执行run方法中的内容
*/ public class MyThread extends Thread{ @Override public void run(){ for (int a=0; a<5; a++){ System.out.println("thread========="); } } public static void main(String args[]){ MyThread myThread = new MyThread(); myThread.start(); } }
/**实现Runnable接口
 * 1、创建一个类实现Runnable接口,重写run方法
 * 2、将之前Runnable的实现类的对象作为参数,创建一个Thread类,该Thread类即为线程对象
 * 3.调用线程对象的start方法启动线程,就会让线层执行run方法中的内容
 */
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int a=0; a<5; a++){
            System.out.println("thread=========");
        }
    }

    public static void main(String args[]){
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}
/**
 * 通过匿名内部类创建线程
 */
public class Test extends Thread{

    public static void main(String args[]){
        
        new Thread(){
            @Override
            public void run(){
                System.out.println("=========");
            }
        }.start();
        
        //其实是向上转型,实质是 Runnable runnable = new RunnableImpl();
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                System.out.println("=========");
            }
        };
        new Thread(runnable).start();
    }
}
/**使用Lambda创建线程,其实就是简化代码
 * Lambda表达式的标准格式有三部分给组成
 *  1、一些参数,用()括起来
 *  2、一个箭头,传递的意思,把前面的参数传递个后面的代码
 *  3、一段代码,用{}括起来的代码块,重写接口抽象方法的方法体
 * */

public class LambdaTest {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类创建线程");
            }
        }).start();

        //使用Lambda表达式
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "正在执行");
        }).start();
    }
}

实现Runnable接口比继承Thread类所具有的优势:

1、适合多个相同的程序代码的线程去处理同一个资源

2、可以避免java中的单继承的限制

3、增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4、线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

线程中的常用方法

1、void start(),该方法使线程处于可执行状态,但不一定马上执行,需要和其他线程一起抢占CPU资源。

2、String getName(),通过该方法可以获取线程的名字,该方法返回值为String。mt.getName(),获取线程mt的名字。默认主线程的名字是main,其他线程的名字是Thread-0,Thread-1,Thread-2,。。。。

3、void setName(String name),设置线程名称,该方法返回值为void。mt.setName("myThread"),设置线程名称为myThread。

4、static Thread currentThread(),通过该方法可以获取当前CPU执行的是哪个线程这是Thread类的一个静态方法,该方法返回值为Thread。Thread.currentThread()。

5、static void sleep(long millis),使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),这是一个静态方法,返回值为void。Thread.sleep(100),是当前线程暂停100毫秒 

6、void join(),join()方法的作用是等待该线程结束(正常执行完或者异常结束),如果在b线程里面使用a.join(),可以理解为将a线程加入b线程,这样b线程执行的时候就只能等a线程执行完。该方法可以设置最多等待多少毫秒,join(1000),最多等待1000毫秒。

class JoinTest {
    public static void main(String[] args) {
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Thread a = new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName() + "执行结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"线程2");
            a.start();
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行结束");
        },"线程1").start();
    }
}

/**运行结果
 * 线程1开始执行
 * 线程2开始执行
 * 线程2执行结束
 * 线程1执行结束
 * */

7、static void yield(),暂停当前线程,使其让出当前正在使用的处理器回到可执行状态。需要注意的是,即使线程调用了yield(),让出了正在使用的处理器,该线程还是会和其他同一优先级的线程再次抢占CPU资源,也就是说有可能该线程执行了yield()后会再次执行。sleep()会使线程进入不可执行状态,指定毫秒数内不执行,而yield()不一定会使线程停止执行。

8、void interrupt(),中断一个线程,该方法通常会与isInterrupted()配合使用。注意,中断使用了sleep,wait,join这类阻塞状态的线程,isInterrupted()的返回值是false,而中断正常运行的线程,isInterrupted()的返回值为true,通过判断返回值来确定是否确实要中断该进程。

守护线程

java程序启动时,第一个创建的线程是主线程,之后再创建其他的线程,只要还有一个线程没有结束,这个java应用程序对应的进程就不会结束,即使主线程已经结束了。如果将一个线程设置为守护线程,那么当其他的非守护线程执行完后,不论该守护线程是否执行完毕,都会强制结束。使用 t.setDaemon(true) 将线程t设置为守护线程。

垃圾回收器线程就是一个守护线程

线程的生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多个状态之间切换。

线程的五种状态——从操作系统角度描述

在线程的生命周期中,它要经过新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)和死亡(Dead) 这 5 种状态。

新建状态(New):当程序使用 new 关键字创建了一个线程对象后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值。

就绪状态(Runnable):当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。

运行状态(Running):如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。

阻塞状态(Blocked):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,暂时停止运行。直到线程进入可运行状态(runnable),才有机会再次获得 cpu 时间片 转到运行状态(running)。阻塞状态分为三种:

1、等待阻塞:运行的线程执行wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。(wait会释放持有的锁)

2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。

3、其他阻塞:运行的线程执行sleep()方法或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。(sleep不会释放持有的锁)

死亡状态(Dead):线程执行完后正常结束或者因异常退出了run()方法,该线程结束生命周期。

线程的六种状态——从java API层面描述

从java API角度来描述线程可以分为六种状态,新建状态(NEW),运行状态(RUNNABLE),等待状态(WAITING),超时等待(TIMED_WAIING),阻塞状态(BLOCKED),终止状态(TERMINATED)。这六种状态的划分其实是根据类Thread.state枚举得到的。注意:这里的RUNNABLE状态包括了操作系统层面的就绪状态和运行状态,甚至还包括了某些原因引起的阻塞状态(如BIO导致的线程阻塞在java中无法区分,仍然认为是可运行状态).。BLOCKED、WAITING、TIMED_WAIING都是java API对操作系统阻塞状态的细分。

 

 

修正:原图中 wait到 runnable状态的转换中,join实际上是Thread类的方法,但这里写成了Object

参考资料

Java多线程学习(吐血超详细总结)

Java 并发基础常见面试题总结

posted @ 2020-06-16 20:06  路半_知风  阅读(107)  评论(0编辑  收藏  举报