【Java】多线程

原文链接:https://blog.csdn.net/Evankaka/article/details/44153709


前言

进程:每个进程都有独立的代码和数据空间,一个进程包含1 - n个线程。进程是资源分配的最小单位。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器。线程是cpu调度的最小单位。
  
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统可同时运行多个进程。
多线程是指在同一程序中有多个线程在执行。
  
在Java中,如果需要实现多线程,有三种方式:实现Runable接口实现Callable接口继承Thread类

java.lang.Thread类

使用继承Thread类的方式创建线程。

public class MyThread extends Thread{
    private String name;
    public MyThread(String name) {
        this.name=name;
    }
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行  :  " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Main {

    public static void main(String[] args) {
        MyThread myThread1=new MyThread("A");
        MyThread myThread2=new MyThread("B");
        myThread1.start();
        myThread2.start();
    }

}

第一次运行结果:
这里写图片描述
第二次运行结果:
这里写图片描述
程序说明:程序开始运行后,java虚拟机启动一个进程,主线程main在main方法被调用时创建。调用MyThread对象的start的方法时,另外两个线程被创建。这样,一个简单的多线程程序就完成了。

注意事项

  • start方法被调用后并不是立即执行当前线程代码,而是使该线程变为可运行态(Runnable),什么时候运行由操作系统决定。
  • 从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
  • Thread.sleep()方法调用的目的是不让当前线程霸占该进程所获取的CPU资源,给其他线程执行的机会。
  • 实际上,所有多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
  • 但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。如下图所示:
    这里写图片描述
java.lang.Runnable接口

使用实现Runnable接口的方式创建线程。

public class MyThread implements Runnable{
    private String name;

    public MyThread(String name) {
        this.name=name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行  :  " + i);
            try {
                Thread.sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Main {
    public static void main(String[] args) {
        new Thread(new MyThread("C")).start();
        new Thread(new MyThread("D")).start();
    }
}

运行结果:
这里写图片描述
说明

  • MyThread类通过实现Runnable接口,使得该类具有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
  • 在启动线程的时候,需要先通过Thread类的构造方法Thread(Runnable target)构造出Thread对象。然后调用Thread对象的start方法来运行线程代码。
  • 实际上所有的线程代码都需要通过Thread的start方法来运行。不管是扩展Thread类还是实现Runnable接口来实现多线程,最终都是通过Thread对象的API来控制线程。因此,熟悉Thread类的API是进行多线程编程的基础。
Thread与Runnable的选择

一般推荐使用实现Runnable接口的方式来创建线程。理由如下:

  • Java不支持多重继承,因此,继承了Thread类就无法继承其它类。但是,Java支持实现多个接口。
  • 继承Thread类比实现Runnable接口开销大。
  • 线程池只能放入实现Runnable接口或callable接口的类的线程。不能直接放入继承Thread的类的线程。
线程状态转换

这里写图片描述

  • 新建状态(New):创建了一个线程对象。
  • 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,等待获得CPU的时间片。
  • 运行状态(Running):就绪状态的线程获的了CPU时间片,执行线程代码。
  • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃了CPU的使用权,暂时停止运行。直到线程重新进入就绪状态,才有机会再次运行。
  • 终止状态(Dead):线程执行完毕或因异常退出了run方法。该线程生命周期结束。
线程调度
  • 调整线程优先级。Java线程有优先级,优先级高的线程会获得较多的运行机会。Java线程的优先级用整数表示,取值范围是1 ~ 10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
          线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
          线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
          分配给线程的默认优先级,取值为5。

Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如在A线程中创建了B线程,那么B线程将和A线程具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类的三个静态常量,这样能保证同样的优先级采用了同样的调度方式。

  • 线程睡眠。Thread.sleep(long millis)方法会使线程转换到阻塞状态。millis参数设定睡眠的时间,单位是毫秒。当睡眠结束,线程自动转换为就绪态。sleep()平台移植性好。
  • 线程等待。Object类中的wait()方法,会使线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法。
  • 线程让步。join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个线程运行结束,当前线程再由阻塞态转换为就绪态。
  • 线程唤醒。Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果等待的线程有多个,则会选择唤醒其中一个线程。选择是任意的,并在对实现做出决定时发生。notifyAll()方法唤醒在此对象监视器上等待的所有线程。

注意:Thread中的suspend方法和resume方法在JDK1.5中已经废除,因为有死锁倾向。这里不再介绍。

常用函数说明

sleep(long millis)
在指定的毫秒内让当前正在执行的线程休眠(暂停执行)。sleep()是Thread类的静态方法。线程在sleep()期满后,不一定会立即执行,因为可能有其他线程正在占用CPU。

join()
指等待线程终止。join是Thread类的一个方法。主线程等待子线程终止。

yield()
暂停当前正在执行的线程对象,并执行其他线程。使调用yield的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让具有相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()可以达到让步的目的,因为让步的线程还有可能被线程调度再次选中。

sleep()和yield()的区别
sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入可执行转台后马上又被执行。
sleep()方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处于可运行状态,不可能让较低优先级的线程获得CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep()方法,又没有受到I/O阻塞,那么,较低优先级的线程只能等待所有较高优先级的线程运行结束,才有机会执行。

setPriority()
更改线程的优先级。例如:new MyThread().setPriority(Thread.MAX_PRIORITY);。线程的优先级越高,表示线程被执行的机会越多。当程序中同时存在两个线程A和B,A的优先级比B高,那么A执行的机会就多,不能说A执行完了才会执行B,它们会交替执行。

interrupt()
不要以为它是中断某个线程。它只是向线程发送一个中断信号,让线程在无限等待(如出现死锁)时能抛出异常,从而结束线程,但是如果你吃掉了这个异常,那么这个线程不会中断。

wait()
Object.wait()和Object.notify()必须要与synchronized(Object)一起使用,Object.wait()和Object.notify()必须在synchronized(Object){ … }语句块内。从功能上看,wait就是将已获得对象锁的线程主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,重新获取对象锁,继续执行。但有一点需要注意,调用norify方法,并不是马上就释放对象锁,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一个线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU的同时,也会释放对象锁。

wait和sleep的比较

  • 在多线程环境下,它们都可以将线程阻塞指定毫秒数。
  • wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。
  • wait属于Object类;sleep属于Thread类。
  • 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。sleep方法没有释放锁;而wait方法释放了锁,使得其他线程可以使用同步控制块或方法。
  • wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 。
常见线程名词解释

主线程:JVM调用程序main()所产生的线程。
当前线程:一般指通过Thread.currentThread()来获取的线程。
后台线程:指为其它线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。前台线程和后台线程的区别在于:是否随主线程结束而结束。
前台线程:是指接受后台线程服务的线程。可以通过isDaemon()和setDaemon()方法来判断和设置后台线程。

Thread类的一些常用方法
  • sleep(): 强迫一个线程睡眠N毫秒。
  • isAlive(): 判断一个线程是否存活。
  • join(): 等待线程终止。
  • activeCount(): 程序中活跃的线程数。
  • enumerate(): 枚举程序中的线程。
  • currentThread(): 得到当前线程。
  • isDaemon(): 一个线程是否为守护线程。
  • setDaemon(): 设置一个线程为守护线程。
  • setName(): 为线程设置一个名称。
  • wait(): 强迫一个线程等待。
  • notify(): 通知一个线程继续运行。
  • setPriority(): 设置一个线程的优先级。
线程同步

1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象。

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,即同步方法和同步语句块。

在进一步阐述前,我们需要明确几点:

  • 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

总结一下:

  • 线程同步的目的是控制多个线程对共享资源的互斥访问。
  • 线程同步通过锁来实现,每个对象都有且仅有一个锁,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
  • 对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
  • 对于同步,要时刻清醒在哪个对象上同步,这是关键。
  • 编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
  • 当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
  • 死锁是线程间相互等待锁造成的,在实际中发生的概率非常的小。
线程数据传递

在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来获取最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法像函数一样通过函数参数和return语句来完成。

通过成员变量和构造方法传递数据
在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用成员变量保存起来,以便线程使用(其实就是在run方法中使用)。

通过成员变量和set方法传递数据
向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入;另外一次机会就是在类中定义一系列的public方法或变量(也可称之为字段)时将数据传入。对象创建完成后,通过set方法给变量赋值。

通过回调函数传递数据
上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,这时就可以使用回调函数的方式来传递数据。

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REaDME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 、资源1项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值