多线程详解(1)

1.线程概述

1.1 线程相关概念

什么是进程?什么是线程?

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位。
可以把进程简单的理解为正在操作系统中运行的一个程序
线程(Thread)是进程的一个执行单元。一个线程就是进程中一个单一顺序的控制流,进程的一个执行分支。
进程是线程的容器,一个进程至少有一个线程。一个进程中也可以有多个线程。


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


线程与子线程
JVM启动时会创建一个主线程,该主线程负责执行main方法。主线程就是运行main方法的线程。
java中的线程不是孤立的,线程之间存在一些联系。如果在A线程中创建了B线程,称B线程为A线程的子线程,相应的A线程就是B线程的父线程。

1.2 串行、并发、并行

在这里插入图片描述
并发可以提高事物的处理效率,即一段时间内可以处理或完成更多的事情。
并行是一种更为严格,理想的并发。


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

1.3 线程的创建与启动

在JAVA中,创建一个线程就是创建一个Thread类(子类)的对象(实例)
Thread类有两个常用的构造方法:Thread()与Thread(Runnable).对应的创建线程的两种方式:

  • 定义Thread类的子类
  • 定义一个Runnable接口的实现类

这两种创建线程的方式没有什么本质区别。

public class TestThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我在走路......");
        }
    }

    public static void main(String[] args) {
        new TestThread().start();

        for (int i = 0; i < 100; i++) {
            System.out.println("我在看手机......");
        }
    }
}
public class TestRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我在走路......");
        }
    }

    public static void main(String[] args) {
        TestRunnable t2 = new TestRunnable();
        new Thread(t2).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("我在看手机......");
        }
    }
}

1.4 线程的常用方法

1.4.1 currentThread()方法

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

public class SubThread extends Thread{
    public SubThread(){
        System.out.println("子线程的构造方法打印当前线程的名称:"+Thread.currentThread().getName());
    }

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

public class Test01CurrentThread {
    public static void main(String[] args) {
        System.out.println("main方法打印当前线程的名称:"+Thread.currentThread().getName());

        //创建子线程,调用构造方法,在main线程中调用构造方法,所以输出当前线程 main 线程
        SubThread subThread = new SubThread();
        //输出run方法的,使用的是 Thread-0线程
        subThread.start();
    }
}
1.4.2 setName()/getName()方法

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

1.4.3 isAlive()方法

thread.isAlive()判断当前线程是否处于活动状态。

public class SubThread3 extends Thread{
    @Override
    public void run() {
        System.out.println("run方法,isAlive = "+this.isAlive());
    }
}

public class Test {
    public static void main(String[] args){
        SubThread3 t3 = new SubThread3();
        System.out.println("begin=="+t3.isAlive());
        t3.start();
        //Thread.sleep(400);
        //这一行不确定,如果线程运行结束,则是false , 如果线程还在继续 就是 true
        System.out.println("end=="+t3.isAlive());
    }
}
1.4.4 sleep()方法

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

public class SimpleTimer {
    public static void main(String[] args) {
        int remaining = 10;

        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.4.5 getId()方法

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

public class SubThread4 extends Thread{
    @Override
    public void run() {
        System.out.println("thread name = "+Thread.currentThread().getName() +
                ", id == "+this.getId());
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println("thread name = "+Thread.currentThread().getName() +
                ", id == "+Thread.currentThread().getId());

        for (int i = 0; i < 5; i++) {
            //线程终止后,编号可能会分配给其他后面创建的线程使用
            new SubThread4().start();
        }

    }
}
1.4.6 yieId()方法

Thread.yieId()方法的作用是放弃当前的CPU资源。

1.4.7 setPriority()方法

thread.setPriority(num),设置线程优先级,默认值是5

  1. java线程的优先级取值范围是 1 ~10,如果超出这个范围会抛异常:IllegalArgumentException.
  2. 在操作系统中,优先级较高的线程获得CPU的资源越多。
    线程优先级本质上只是给线程调度器一个提示信息,以便于调度器决定先调度哪些线程。注意不能保证优先级高的线程先运行。
  3. Java优先级设置不当或着滥用可能会导致某些线程永远无法得到运行,即产生了线程饥饿。
  4. 线程的优先级并不是设置的越高越好,一般情况下使用普通的优先级即可,即在开发时不必设置线程的优先级。
  5. 线程的优先级具有继承性,在A线程中创建了B线程,则B线程的优先级和A是一样的。
1.4.8 interrupt()方法

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

public class TestThreadInterrupt {
    public static void main(String[] args) {
        //定义一个新线程
        Thread t = new Thread(()->{
            System.out.println("1.进入新线程");
            try {
                //让线程休眠五秒钟、目的是先让Main线程工作
                Thread.sleep(5000);
                System.out.println("2、新线程已经完成了休眠") ;
            } catch (InterruptedException e) {
                System.out.println("3、新线程休眠被终止");
                return;
            }
            System.out.println("4、新线程run()方法正常结束");
        });
        t.start(); //新线程启动

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            System.out.println("主线程休眠被终止") ;
        }
        t.interrupt();  //此时主线程唤醒了,但是新线程还在休眠。主线程吧新线程 中断
    }
}
1.4.9 setDaemon()方法

JAVA中的线程分为用户线程与守护线程
守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程
守护线程不能单独运行,当JVM中没有其他用户线程,只有守护线程时,守护线程会自动销毁,JVM会退出。

public class TestThreadDaemon {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while (true){
                System.out.println(Thread.currentThread().getName()+"在执行");
            }
        },"守护线程");

        t.setDaemon(true);  //开启后台线程(守护线程),
        t.start();

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

    }
}

1.5 线程的生命周期

线程的生命周期是线程对象的生老病死,即线程的状态。
线程的生命周期可以通过getState()方法获得,线程的状态是Thread.State枚举类型定义的。由以下几种:

  • New,新建状态。创建了线程对象,在调用start()启动之前的状态;
  • RUNNABLE,可运行状态,它是一个复合状态,包含:READY和RUNNING两个状态。READY状态该线程可以被线程调度器进行调度使它处于RUNNING状态,RUNNING状态表示该线程正在执行。Thread.yieId()方法可以把线程由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.6 多线程编程的优势与存在的风险

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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值