多线程基础-1-线程概述

多线程

1 线程概述

1.1 线程相关概念

进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位。

可以把进程简单的理解为正在操作系统中运行的一个程序。

线程

线程(thread)是进程的一个执行单元。

一个线程就是进程中一个单一顺序的控制流,进程的一个执行分支。

进程是线程的容器,一个进程至少有一个线程。一个进程中也可以有多个线程。

在操作系统中是以进程为单位分配资源,如虚拟存储空间,文件描述符等。每个线程都有各自的线程栈,自己的寄存器环境,自己的线程本地存储。

主线程与子线程

JVM 启动时会创建一个主线程,该主线程负责执行main方法。主线程就是运行main方法的线程。

Java 中的线程不孤立的,线程之间存在一些联系。如果在A线程中创建了B线程, 则称B线程为A线程的子线程, 相应地,A线程是B线程的父线程。

串行,并发与并行

串行,并发与并行

并发可以提高以事物的处理效率,即一段时间内可以处理或者完成更多的事情。

并行是一种更为严格,理想的并发。

从硬件角度来说,如果是单核CPU,一个处理器一次只能执行一个线程的情况下,处理器使用时间片轮转技术,可以让CPU快速的在各个线程之间进行切换,对于用户来说,感觉是三个线程在同时执行。如果是多核心 CPU,可以为不同的线程分配不同的 CPU 内核。

1.2 线程的创建与启动

在 Java 中,创建一个线程就是创建一个Thread类(或其子类)的对象(实例)。

Thread类有两个常用的构造方法:Thread()与Thread(Runnable)。对应的创建线程的两种方式,这两种创建线程的方式没有本质的区别

  • 定义Thread 类的子类

    package com.yupeng.createthread.p1;
    
    /**
     * @author Yupeng
     * @create 2020-12-05 10:42
     * 1)定义类继承Thread类
     */
    public class MyThread extends Thread{
        // 2)重写run()方法
    
        @Override
        public void run() {
            System.out.println("子线程打印的内容");
        }
    }
    
    package com.yupeng.createthread.p1;
    
    /**
     * @author Yupeng
     * @create 2020-12-05 10:44
     */
    public class Test {
        public static void main(String[] args) {
            System.out.println("JVM启动main线程,执行main方法");
            // 3)创建子线程对象
            MyThread thread = new MyThread();
            // 4)启动线程
            thread.start();
            /*
            调用线程的 start()方法来启动线程, 启动线程的实质就是请求JVM运行相应的线程,这个线程具体在什么时候运行由线程调度器(Scheduler)决定.
            注意:
            start()方法调用结束并不意味着子线程开始运行
            新开启的线程会自动执行run()方法
            如果开启了多个线程,start()调用的顺序并不一定就是线程启动的顺序
            多线程运行结果与代码执行顺序或调用顺序无关
            */
            System.out.println("继续执行main线程后面的代码");
        }
    }
    
  • 定义Runnable接口的实现类

    package com.yupeng.createthread.p2;
    
    /**
     * @author Yupeng
     * @create 2020-12-05 10:52
     * 当线程类已经有父类了,就不能用继承Thread类的形式创建线程,可以使用实现Runnable接口的形式
     * 1)定义类,实现Runnable接口
     */
    public class MyRunnable implements Runnable{
        /**
         *重写run()方法
         */
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("sub thread -->" + i);
            }
        }
    }
    
    
    package com.yupeng.createthread.p2;
    
    /**
     * @author Yupeng
     * @create 2020-12-05 10:57
     */
    public class Test {
        public static void main(String[] args) {
            // 3)创建Runnable接口的实现类对象
            MyRunnable runnable =  new MyRunnable();
            // 4)创建线程对象
            Thread thread = new Thread(runnable);
            // 5)开启线程
            thread.start();
    
            // 以下代码也属于main线程
            for (int i = 0; i < 100; i++) {
                System.out.println("main thread -->" + i);
            }
        }
    }
    

1.3 线程的常用方法

1.3.1 currentThread()方法

Thread.currentThread()方法可以获得当前线程。

Java 中的任何一段代码都是执行在某个线程当中的,执行当前代码的线程就是当前线程。

同一段代码可能被不同的线程执行, 因此当前线程是相对的,Thread.currentThread()方法的返回值是在代码实际运行时候的线程对象

例1:

package com.yupeng.threadmethod;

/**
 * 定义线程类,分别在构造方法和run方法中打印当前线程
 * @author Yupeng
 * @create 2020-12-05 11:17
 */
public class SubThread1 extends Thread{
    public SubThread1() {
        System.out.println("构造方法打印当前线程:" + Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("run方法打印当前线程:" + Thread.currentThread().getName());
    }
}

package com.yupeng.threadmethod;

/**
 * @author Yupeng
 * @create 2020-12-05 11:20
 */
public class Test01CurrentThread {
    public static void main(String[] args) {
        System.out.println("mian方法中打印当前线程" + Thread.currentThread().getName()); //mian方法中打印当前线程main

        // 创建子线程,调用构造方法,构造方法的当前线程是main线程
        SubThread1 t1 = new SubThread1(); //构造方法打印当前线程:main
        t1.start(); //run方法打印当前线程:Thread-0
//        t1.run(); //run方法打印当前线程:main.在main线程中直接调用run()方法,没有开启新线程,所以run方法中的当前线程就是main线程
    }
}

例2:

package com.yupeng.threadmethod;

/**
 * @author Yupeng
 * @create 2020-12-05 11:38
 */
public class SubThread2 extends Thread{
    public SubThread2() {
        System.out.println("构造方法中,Thread.currentThread().getName():" + Thread.currentThread().getName());
        System.out.println("构造方法中,this.getName():" + this.getName());
    }

    @Override
    public void run() {
        System.out.println("run方法中,Thread.currentThread().getName():" + Thread.currentThread().getName());
        System.out.println("run方法中,this.getName():" + this.getName());
    }
}

package com.yupeng.threadmethod;

/**
 * @author Yupeng
 * @create 2020-12-05 11:41
 */
public class Test02CurrentThread {
    public static void main(String[] args) throws InterruptedException {
        // 创建子线程对象
        SubThread2 t2 = new SubThread2();
        // 设置线程名称
        t2.setName("t2");
        t2.start();

        // main线程睡眠500ms
        Thread.sleep(500);
        // Thread(Runnable)构造方法的形参是Runnable接口,调用时传递的实参是接口的实现类实例对象
        Thread t3 = new Thread(t2);
        t3.start();
    }
}

// 运行结果
构造方法中,Thread.currentThread().getName():main
构造方法中,this.getName():Thread-0
run方法中,Thread.currentThread().getName():t2
run方法中,this.getName():t2
run方法中,Thread.currentThread().getName():Thread-1
run方法中,this.getName():t2
1.3.2 setName()/getName()

thread.setName(线程名称),设置线程名称。

thread.getName(),返回线程名称。

通过设置线程名称,有助于程序调试,提高程序的可读性, 建议为每个线程都设置一个能够体现线程功能的名称。

1.3.3 isAlive()

thread.isAlive()判断当前线程是否处于活动状态,活动状态就是线程已启动并且尚未终止。

package com.yupeng.threadmethod.p2isalive;

/**
 * @author Yupeng
 * @create 2020-12-05 11:49
 */
public class SubThread3 extends Thread{
    @Override
    public void run() {
        System.out.println("run方法,isAlive = " + this.isAlive()); //运行状态,true
    }
}

package com.yupeng.threadmethod.p2isalive;

package com.yupeng.threadmethod.p2isalive;

/**
 * 测试线程的活动状态
 * @author Yupeng
 * @create 2020-12-05 12:00
 */
public class Test {
    public static void main(String[] args) {
        SubThread3 t3 = new SubThread3();
        System.out.println("begin --> " + t3.isAlive()); //begin --> false,启动之前肯定是false
        t3.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("---");
        }
        System.out.println("end --> " + t3.isAlive()); //结果不一定,打印这一行时,如果 t3线程还没结束就返回true, 如果t3线程已结束,返回false
    }
}
1.3.4 sleep()

Thread.sleep(millis); 让当前线程休眠指定的毫秒数,当前线程是指 Thread.currentThread()返回的线程。

例1:

package com.yupeng.threadmethod.p3sleep;

/**
 * 子线程休眠
 * @author Yupeng
 * @create 2020-12-05 12:10
 */
public class SubThread4 extends Thread{
    @Override
    public void run() {
        try {
            System.out.println("run,threadname = " + Thread.currentThread().getName() +
                    ", begin = " + System.currentTimeMillis());
            Thread.sleep(2000); // 当前线程睡眠2000ms
            System.out.println("run,threadname = " + Thread.currentThread().getName() +
                    ", end = " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            // 在子线程的run方法中, 如果有受检异常(编译时异常)需要处理,只有选择捕获处理,不能抛出处理
            // 因为此类继承了Thread类,Thread类中的run方法没有抛出异常,而子类不能抛出比父类更宽泛的异常
            e.printStackTrace();
        }
    }
}

package com.yupeng.threadmethod.p3sleep;

/**
 * @author Yupeng
 * @create 2020-12-05 12:14
 */
public class Test {
    public static void main(String[] args) {
        SubThread4 t4 = new SubThread4();
        System.out.println("main__begin = " + System.currentTimeMillis());
        t4.start();
        System.out.println("main__end = " + System.currentTimeMillis());
    }
}

例2:

package com.yupeng.threadmethod.p3sleep;

/**
 * 简易的倒计时器
 * @author Yupeng
 * @create 2020-12-05 12:26
 */
public class SimpleTimer {
    public static void main(String[] args) {
        int remaining = 60; // 从60秒开始计时
        // 读取main方法的参数
        // 可以在菜单栏Run-->Edit Configurations-->Program arguments赋参数
        if (args.length == 1){
            remaining = Integer.parseInt(args[0]);
        } 
        
        while(true){
            System.out.println("Remaining: " + remaining);
            remaining--;
            if (remaining < 0 ){
                break;
            } try {
                Thread.sleep(1000); // 线程休眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } 
        System.out.println("Done!!");
    }
}
1.3.5 getId()

thread.getId()可以获得线程的唯一标识。

注意:某个编号的线程运行结束后,该编号可能被后续创建的线程使用,重启JVM 后,同一个线程的编号可能不一样。

package com.yupeng.threadmethod.p4getid;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.util.Random;

/**
 * 获取线程id
 * @author Yupeng
 * @create 2020-12-05 15:41
 */
public class SubThread5 extends Thread{
    @Override
    public void run() {
        System.out.println("线程名称:" + Thread.currentThread().getName() + ",线程id:" +
                Thread.currentThread().getId());
    }
}

package com.yupeng.threadmethod.p4getid;

/**
 * 测试获取线程id
 * @author Yupeng
 * @create 2020-12-05 15:42
 */
public class Test {
    public static void main(String[] args) {
        System.out.println("线程名称:" + Thread.currentThread().getName() + ",线程id:" +
                Thread.currentThread().getId());
        for (int i = 0; i < 10; i++) {
            new SubThread5().start();
        }
    }
}
1.3.6 yield()

public static native void yield(),作用是放弃当前的CPU资源。

package com.yupeng.threadmethod.p5yield;

/**
 * @author Yupeng
 * @create 2020-12-05 15:56
 */
public class SubThread6 extends Thread{
    @Override
    public void run() {
        long begin = System.currentTimeMillis();
        long sum = 0;
        for (int i = 0; i < 10000000; i++) {
            sum += i;
            Thread.yield(); // 线程让步,放弃CPU执行权
        }
        long end = System.currentTimeMillis();
        System.out.println("子线程用时:" + (end - begin) + "毫秒");
    }
}

package com.yupeng.threadmethod.p5yield;

/**
 * @author Yupeng
 * @create 2020-12-05 16:02
 */
public class Test {
    public static void main(String[] args) {
        SubThread6 t6 = new SubThread6();
        t6.start();

        long begin = System.currentTimeMillis();
        long sum = 0;
        for (int i = 0; i < 10000000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("主线程用时:" + (end - begin) + "毫秒");
    }
}

// 运行结果
主线程用时:6毫秒
子线程用时:1115毫秒
1.3.7 setPriority()

thread.setPriority(num); 设置线程的优先级

java线程的优先级取值范围是1~10 , 数值越大,优先级越高,如果超出这个范围会抛出异常IllegalArgumentException。

在操作系统中,优先级较高的线程获得CPU的资源越多,线程优先级本质上只是给线程调度器一个提示信息,以便于调度器决定先调度哪些线程。注意:不能保证优先级高的线程先运行。

Java 优先级设置不当或者滥用可能会导致某些线程永远无法得到运行,即产生了线程饥饿

线程的优先级并不是设置的越高越好,一般情况下使用普通的优先级即可,即在开发时不必设置线程的优先级,默认值是5。

线程的优先级具有继承性,在 A 线程中创建了 B 线程,则 B 线程的优先级与 A 线程是一样的。

下面的例子中,线程B的优先级为10,线程A的优先级为1,则先执行线程B的概率较高。

package com.yupeng.threadmethod.p6priority;

/**
 * @author Yupeng
 * @create 2020-12-05 16:21
 */
public class ThreadA extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

package com.yupeng.threadmethod.p6priority;

/**
 * @author Yupeng
 * @create 2020-12-05 16:21
 */
public class ThreadB extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}


package com.yupeng.threadmethod.p6priority;

/**
 * @author Yupeng
 * @create 2020-12-05 16:27
 */
public class Test {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();
        threadA.setName("threadA");
        threadB.setName("threadB");

        threadA.setPriority(1);
        threadB.setPriority(10);

        threadA.start();
        threadB.start();
    }
}
1.3.8 interrupt()

interrupt()的作用是中断线程。

注意:调用 interrupt()方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程。

package com.yupeng.threadmethod.p7interrupt;

/**
 * @author Yupeng
 * @create 2020-12-05 16:36
 */
public class SubThread7 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            if ( this.isInterrupted() ) {
                System.out.println("当前线程的中断标志为true,即将退出");
                // break; //中断循环, run()方法体执行完毕,子线程运行完毕
                return; //直接结束当前 run()方法的执行
            }
            System.out.println("sub thread --> " + i);
        }
    }
}

package com.yupeng.threadmethod.p7interrupt;

/**
 * @author Yupeng
 * @create 2020-12-05 16:37
 */
public class Test {
    public static void main(String[] args) {
        SubThread7 t7 = new SubThread7();
        t7.start();
        // 主线程
        for (int i = 0; i < 100; i++) {
            System.out.println("main --> " + i);
        }
        // 中断子线程
        t7.interrupt(); // 并没有真正中断,本例中,子线程中判断中断标志位,return返回
    }
}
1.3.9 setDaemon()

Java 中的线程分为用户线程与守护线程:

守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程。

守护线程不能单独运行, 当 JVM 中没有其他用户线程,只有守护线程时,守护线程会自动销毁, JVM 会退出。

package com.yupeng.threadmethod.p8daemon;

/**
 * @author Yupeng
 * @create 2020-12-05 16:54
 */
public class SubDaemonThread extends Thread{
    @Override
    public void run() {
        while (true) {
            System.out.println("sub thread...");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package com.yupeng.threadmethod.p8daemon;

/**
 * 设置线程为守护线程
 * @author Yupeng
 * @create 2020-12-05 16:56
 */
public class Test {
    public static void main(String[] args) {
        SubDaemonThread thread = new SubDaemonThread();
        // 设置线程为守护线程,应该在启动之前就设置
        thread.setDaemon(true);
        thread.start();

        // main线程
        for (int i = 0; i < 20; i++) {
            System.out.println("main --> " + i);
        }
        // main线程结束后,守护线程thread也结束
    }
}

1.4 线程的生命周期

线程的生命周期是线程对象的生老病死,即线程的状态。

线程生命周期可以通过 getState()方法获得, 线程的状态是Thread.State 枚举类型定义的, 有以下几种:

  • NEW,新建状态。 创建了线程对象,在调用 start()启动之前的状态。
  • RUNNABLE,可运行状态。 它 是一个复合状态,包含:READY 和RUNNING 两个状态。READY 状态该线程可以被线程调度器进行调度使它处于RUNNING状态,RUNING状态表示该线程正在执行。Thread.yield()方法可以把线程由 RUNNING 状态转换为 READY 状态。
  • BLOCKED 阻塞状态。线程发起阻塞的I/O操作,或者申请由其他线程占用的独占资源,线程会转换为 BLOCKED 阻塞状态。处于阻塞状态的线程不会占用CPU资源。当阻塞I/O操作执行完,或者线程获得了其申请的资源,线程可以转换为 RUNNABLE。
  • WAITING 等待状态。线程执行了object.wait(),thread.join()方法会把线程转换为 WAITING 等待状态,执行 object.notify()方法,或者加入的线程执行完毕,当前线程会转换为 RUNNABLE 状态。
  • TIMED_WAITING 状态,与 WAITING 状态类似,都是等待状态。区别在于处于该状态的线程不会无限的等待,如果线程没有在指定的时间范围内完成期望的操作,该线程自动转换为RUNNABLE。
  • TERMINATED 终止状态,线程结束处于终止状态。

线程的生命周期

1.5 多线程编程的优势与存在的风险

多线程编程具有以下优势:

  • 提高系统的吞吐率(Throughout):多线程编程可以使一个进程有多个并发(concurrent,即同时进行的)的操作。
  • 提高响应性(Responsiveness):Web服务器会采用一些专门的线程,负责用户的请求处理,缩短了用户的等待时间。
  • 充分利用多核(Multicore)处理器资源:通过多线程可以充分的利用 CPU 资源。

多线程编程存在的问题与风险:

  • 线程安全(Thread safe)问题.多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数
    据(过期的数据), 如丢失数据更新.
  • 线程活性(thread liveness)问题.由于程序自身的缺陷或者由资源稀缺性导致线程一直处于非 RUNNABLE 状态,这就是线程活性问题,
    常见的活性故障有以下几种:
    • 死锁(Deadlock). 类似鹬蚌相争.
    • 锁死(Lockout), 类似于睡美人故事中王子挂了
    • 活锁(Livelock). 类似于小猫咬自己尾巴
    • 饥饿(Starvation).类似于健壮的雏鸟总是从母鸟嘴中抢到食物.
  • 上下文切换(Context Switch). 处理器从执行一个线程切换到执行另外一个线程
  • 可靠性. 可能会由一个线程导致 JVM 意外终止,其他的线程也无法执行.

MvmQr-1607867011877)]

1.5 多线程编程的优势与存在的风险

多线程编程具有以下优势:

  • 提高系统的吞吐率(Throughout):多线程编程可以使一个进程有多个并发(concurrent,即同时进行的)的操作。
  • 提高响应性(Responsiveness):Web服务器会采用一些专门的线程,负责用户的请求处理,缩短了用户的等待时间。
  • 充分利用多核(Multicore)处理器资源:通过多线程可以充分的利用 CPU 资源。

多线程编程存在的问题与风险:

  • 线程安全(Thread safe)问题.多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数
    据(过期的数据), 如丢失数据更新.
  • 线程活性(thread liveness)问题.由于程序自身的缺陷或者由资源稀缺性导致线程一直处于非 RUNNABLE 状态,这就是线程活性问题,
    常见的活性故障有以下几种:
    • 死锁(Deadlock). 类似鹬蚌相争.
    • 锁死(Lockout), 类似于睡美人故事中王子挂了
    • 活锁(Livelock). 类似于小猫咬自己尾巴
    • 饥饿(Starvation).类似于健壮的雏鸟总是从母鸟嘴中抢到食物.
  • 上下文切换(Context Switch). 处理器从执行一个线程切换到执行另外一个线程
  • 可靠性. 可能会由一个线程导致 JVM 意外终止,其他的线程也无法执行.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值