个人笔记 Java多线程(一)

这段笔记是参照b站教程BV1Rv411y7MU整理而来的。用于个人备忘以便复习,有需要的朋友可以自取。

线程概述


1. 概念

  1. 线程

    • 线程是进程的一个执行单元。
    • 一个线程就是进程中一个单一顺序的控制流,是进程的一个执行分支。
    • 进程是线程的容器,一个进程中至少有一个线程。在操作系统中是以进程为单位分配资源的。
    • 每个线程都有各自的线程栈、寄存器环境和本地存储。
  2. 进程

    • 进程是计算机程序关于某数据集合上的一次运行活动,是操作系统进行资源调度和分配的基本单位。
  3. 主线程和子线程

    • JVM启动的时候会创建一个主线程,该线程负责执行main方法,主线程就是运行main方法的线程。
    • Java中线程不是孤立的,线程之中存在一些联系,如果在A线程中创建了B线程,则称B线程为A线程的子线程,相对A线程就为B线程的父线程。
  4. 串行、并发和并行

    • 并发 可以提高对事务的处理效率,在一段时间内可以处理或者完成更多的事情。
    • 并行 是一种更为严格的并发。
    • 从硬件角度来说,如果是单核CPU,一个处理器一次只能执行一个线程的情况下,处理器可以使用时间片轮转技术,可以让CPU快速的在各个线程之间切换;对于用户来说,感觉则是多个进程同时进行。如果是多核CPU,则可以为每个线程分配不同的CPU内核。

2. Thread操作

线程创建

public class MyThread extends Thread{
    @Test
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("Sub Thread : "+i);
            int time = (int) (Math.random()*1000);
            try {
                Thread.sleep(time); /*线程睡眠,单位为毫秒*/
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

常用方法

  • currentThread()方法
    Thread.currentThread()方法可以获得当前线程。
    Java中任何一段代码都是执行在某个线程中的,执行当前代码的线程就是当前线程。
    同一段代码可以被不同的线程执行,因此当前线程是相对的。
    Thread.currentThread()返回值是代码实际运行时候的线程对象。
/*
* MyThread方法
*/
public class MyThread extends Thread{

    public MyThread(){
        System.out.println("打印执行构造函数的线程 :"+Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("打印执行run方法的线程 :" + Thread.currentThread().getName());
    }
}
/*
* Main方法
*/
public class ThreadPoolExecutorDemo {

    public static void main(String[]args){
        System.out.println("打印执行main方法的线程 :"+Thread.currentThread().getName());

        MyThread myThread = new MyThread();
        myThread.start();//直接使用myThread.run()则为main线程执行run()方法
    }
}
/*打印结果为:
 *       打印执行main方法的线程 :main
 *       打印执行构造函数的线程 :main
 *       打印执行run方法的线程 :Thread-0
 * 因为MyThread的构造函数在main方法中调用,所以执行这段代码的是main线程
 * 而start()函数是调用子线程执行方法,所以执行run()方法的是子线程
 */

public class MyThread extends Thread{

    public MyThread(){
        System.out.println("打印执行构造函数的线程-1 :"+Thread.currentThread().getName());
        System.out.println("打印执行构造函数的线程-2 :"+this.getName());
    }

    @Override
    public void run() {
        System.out.println("打印执行run方法的线程-1 :" + Thread.currentThread().getName());
        System.out.println("打印执行run方法的线程-2 :" + this.getName());
    }
}


public class ThreadPoolExecutorDemo {

    public static void main(String[]args) throws InterruptedException {
        //创建子线程对象
        MyThread myThread = new MyThread();
        myThread.setName("xiye");//设置线程名字
        myThread.start();

        Thread.sleep(500);

        //Thread(Runnable)构造方法是Runnable接口,调用时传递的实参是接口的实现类方法
        Thread thread = new Thread(myThread);
        thread.start();
    }
}

/*
*        打印执行构造函数的线程-1 :main
*        打印执行构造函数的线程-2 :Thread-0
*        打印执行run方法的线程-1 :xiye
*        打印执行run方法的线程-2 :xiye
*        打印执行run方法的线程-1 :Thread-1
*        打印执行run方法的线程-2 :xiye
*myThread线程子主线程中创建,但是构造方法中的this指向自己,所以此时返回应该是子线程的名字。
*随后我们对子线程进行了更名,调用子线程的run()方法的时候,Thread.currentThread()和this都指向子线程,所以输出结果一样。
*Runnable接口不会调用构造函数,而创建thread的另一个子线程,所以打印Thread-1.Runnable接口中传入的是myThread,所以调用start()的时候是myThread线程执行。
*/
  • setName()/getName()

    • thread.setName()设置线程名称
    • thread.getName()得到线程名称
    • 通过设置线程名称可以提高程序可读性,所以建议为每个线程都设置一个可以体现线程功能的名称。
  • isAlive()

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

    • Thread.sleep()让当前线程休眠指定毫秒数。且当前线程指Thread.currentThread()返回的线程。
@Test
@Override
public void run() {
    System.out.println(Thread.currentThread().getName()+"启动时间 : "+System.currentTimeMillis());
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"结束时间 : "+System.currentTimeMillis());
}
简易计时器
@Test
@Override
public void run() {
    int remaining=60;//计时器

    while (true){
        System.out.println("Remaining :"+remaining);
        remaining--;
        if(remaining < 0)//结束条件
            break;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("End!");

}
  • getId()

    • Thread.getId()返回线程的唯一编号
    • 某个编号线程运行结束后,该编号可能被后续创建的进程使用。
    • JVM重启后,同一个线程编号可能不一样。
  • yield()

    • Thread.yield()方法的作用的放弃当前CPU资源,
public class MyThread extends Thread{
    @Override
    public void run() {
        long begin = System.currentTimeMillis();
        long sum=0;
        for(int i=1;i<=1000000;i++){
            sum += i;
            Thread.yield();
        }

        long end = System.currentTimeMillis();
        System.out.println("子线程用时:"+(end-begin));
    }
}

public class ThreadPoolExecutorDemo {

    public static void main(String[]args) throws InterruptedException {

        MyThread myThread = new MyThread();
        myThread.start();

        //在main线程中计算累加和
        long begin = System.currentTimeMillis();
        long sum=0;
        for(int i=1;i<=100000000;i++){
            sum += i;
        }

        long end = System.currentTimeMillis();
        System.out.println("main用时:"+(end-begin));
    }
}
  • setPriority()
    • thread.setPriority()可以设置线程的优先级。
    • 线程优先级取值范围为1-10,如果超出范围则会抛出IllegalArgumentException异常。
    • os中,优先级越高的线程获得的CPU资源就越多。
    • 线程的优先级本质上是给线程调度器的提示信息,便于线程调度器决定先调度哪些线程。
    • 不能保证优先级高的线程先运行。
    • Java优先级设置不当或滥用,可能导致某些线程永远无法运行,即产生线程饥饿。
    • 线程优先级并不是设置越高越好,一般开发情况下不必设置线程优先级
public class Thread_A extends Thread{
    @Override
    public void run() {
        int sum=0;
        long begin = System.currentTimeMillis();
        for(int i=0;i<100000000;i++){
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("Thread_A Time : "+(end-begin));
    }
}

public class Thread_B extends Thread{
    @Override
    public void run() {
        int sum=0;
        long begin = System.currentTimeMillis();
        for(int i=0;i<100000000;i++){
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("Thread_B Time : "+(end-begin));
    }
}

public class Main {

    public static void main(String[]args){
        Thread_A thread_a = new Thread_A();
        thread_a.setPriority(1);
        thread_a.start();

        Thread_B thread_b = new Thread_B();
        thread_b.setPriority(10);
        thread_b.start();
    }
}
  • interrupt()
    • thread.interrupt()用于中断线程。
    • 调用此方法仅仅是在当前线程打上一个停止标志,并不是真的停止进程
public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<1000000;i++){
            //判断线程额中断标志,线程isInterred()方法,该方法返回线程中断标志
            if(this.isInterrupted())
                return;
            System.out.println("子线程 -> "+i);
        }
    }
}

public class ThreadPoolExecutorDemo {

    public static void main(String[]args) throws InterruptedException {

        MyThread myThread = new MyThread();
        myThread.start();//开启子线程

        //主线程
        for (int i=0;i<100;i++){
            System.out.println("main -> "+i);
        }
        //for循环结束后中止子线程
        myThread.interrupt();
    }
}
  • setDaemon()
    • Java中线程分为***用户线程*** 和***守护线程***。守护线程是为其他线程提供服务的线程,例如垃圾回收器(GC)就是一个典型的守护线程。
    • 守护线程不能单独运行,当JVM中没有其他用户线程,只有守护线程的时候,守护线程会自动销毁,且JVM会退出。
    • thread.setDaemon(true)将线程设置为守护线程。

下方例子中,在运行时main线程结束后但是子线程仍然在控制台有输出。原因为main线程销毁的时候守护线程还在运行,当main销毁步骤完成后守护线程停止,此时没有其余用户线程,JVM关闭。

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        while (true){
            System.out.println("Sub Thread...");
        }
    }
}

public class ThreadPoolExecutorDemo {

    public static void main(String[]args) throws InterruptedException {

        MyThread myThread = new MyThread();
        myThread.setDaemon(true);//在线程启动之前设置为守护线程
        myThread.start();

        for(int i=0;i<100;i++){
            System.out.println("main -> "+i);
        }
    }
}

3. 生命周期

  1. 线程的生命周期即线程对象的生老病死,即线程状态。
  2. 线程生命周期可以通过getState()方法获得,线程状态是Thread.State枚举类型,有以下几种定义方式:
    Thread.StateDescription
    NEW新建状态,创建了线程对象,在调用start()启动之前。
    RUNNABLE可运行状态,是一个复合状态,包含:READY和RUNNING两个状态。READY表示该线程可以被线程调度器调度使其处于RUNNING状态。RUNNING状态表示该线程正在执行【Thread.yield()方法可以将RUNNING转换为READY状态】
    BLOCKED阻塞状态,线程发起一个阻塞的I/O操作,或者申请一个由其他线程占用的独占资源。线程会转换为BLOCKED状态,处于阻塞状态的线程不会占用CPU资源,当阻塞I/O操作执行完,或者线程获得了其申请的资源,线程可以转换为RUNNABLE。
    WAITING等待状态,线程执行了object.wait(),thread.join()方法会把线程转换为WAITING状态,执行object.notify()或者加入的线程执行完毕后,当前线程会转换乘RUNNABLE状态
    TIMED_WITING与WAITING状态类似,都为等待状态。但处于TIMED_WAITING的线程不会无限的等待,如果线程没有在指定的时间范围内完成期望的任务,该线程会自动转换为RUNNABLE。
    TERMINATED终止状态,线程结束处于终止状态。

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

优势

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

风险

  1. 线程安全问题(Thread safe)。多线程共享数据池的时,如果没有采用正确的并发访问控制措施,就可能会产生一致性问题,如读脏数据(过期数据),如丢失数据。
  2. 线程活性问题(Thread liveness)问题。由于程序自身的缺陷或者有资源的稀缺性,可能会导致线程一直处于非RUNNABLE状态,这就是线程活性问题。常见活性故障有:
    1. 死锁(Deadlock)
    2. 锁死(Lockout)
    3. 活锁(Livelock)
    4. 饥饿(Starvation)
  3. 上下文切换(Content Switch)。处理器从执行一个线程切换到执行另一个线程。
  4. 可靠性问题。可能会有一个线程导致JVM意外终止,其他得线程也无法执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值