Java多线程
Posted Lam
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程相关的知识,希望对你有一定的参考价值。
一、线程概述
线程就是进程中一个负责程序执行的控制单元(执行路径),每一个线程都有自己运行的内容,这个内容可以称之为线程要执行的任务;
一个进程中可以多执行路径,称之为多线程。一个进程当中至少有一个线程,开启多个线程是为了同时运行多部分代码;
二、线程与进程
1)进程
正在进行中的程序(直译)。几乎所有的操作系统都支持进程的概念,所有运行中的任务通常对应一个进程( Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。
特征:
① 独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
② 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的
③ 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
2)线程
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
3)多线程
多线程就是几乎同时执行多个线程(一个处理器在某一个时间点上永远都只能是一个线程!即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行。)。几乎同时是因为实际上多线程程序中的多个线程实际上是一个线程执行一会然后其他的线程再执行,并不是很多书籍所谓的同时执行。
● 优点:解决了多部分代码同时运行的问题;
① 进程之间不能共享内存,但线程之间共享内存非常容易。
② 系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高
③ Java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程
● 缺点:线程太多会导致效率降低
- JVM中的多线程解析:JVM启动时就启动了多个线程,至少有两个线程可以分析的出来
① 执行main函数的线程,该线程的任务都定义在main函数中
② 负责垃圾回收的线程 ;垃圾回收器System.gc()
4)并发和并行
● 并发:同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行
● 并行:同一时刻,有多条指令在多个处理器上同时执行
三、创建线程
1)继承Thread类
① 定义一个类继承Thread类,覆盖Thread类中的run()方法
② 直接创建Thread的子类创建线程
③ 调用start()方法开启线程,执行run()方法中的线程任务
class ThreadRuning extends Thread { private int i = 70; // 重写构造,可以对线程添加名字 public ThreadRuning(String name) { super(name); } @Override public void run() { while (i-- > 0) { // 在run方法里,this代表当前线程 System.out.println(this); } } } public class Test { public static void main(String[] args) { new ThreadRuning("线程1").start(); new ThreadRuning("线程2").start(); } }
2)实现Runnable - ★较为常用★
① 定义类实现Runnable接口,覆盖接口中的run()方法,将线程的任务代码封装到run()方法中
② 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递
③ 调用线程对象的start()方法开启线程
class RunableTest implements Runnable { private int i = 70; @Override public void run() { while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " - good time"); } } } public class Test { public static void main(String[] args) { RunableTest runableTest = new RunableTest(); new Thread(runableTest, "线程1").start(); new Thread(runableTest, "线程2").start(); } }
3)创建线程的两种方式的对比
● 继承Thread类:
优点:编写简单,如果需要访问当前线程,则无须使用 Thread.current Thread()方法,直接使用this即可获得当前线程;
缺点:因为线程已经继承了Thread类,所以不能再继承其他类;
优点:① 避免了Java单继承的局限性;② 将线程的任务从线程的子类中分离出来,进行单独封装;缺点:编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法;
四、线程状态
● 新建状态:使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。
它保持这个状态直到程序start()这个线程。
● 就绪状态:当线程对象调用了start()方法之后,该线程就进入就绪状态。
就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
● 运行状态:如果就绪状态的线程获得CPU资源,就可以执行run()语句,此时线程便处于运行状态。
处于运行状态的线程最为复杂,他可以变成阻塞状态、就绪状态、和死亡状态。
● 阻塞状态:如果一个线程执行了sleep(睡眠)、ssuspend(挂起)等方法后,失去所占资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
① 等待阻塞:运行状态中的线程执行wait()方法,线程便进入等待阻塞状态。
② 同步阻塞:线程在获取synchronized同步锁失败(因为同步锁被其它线程占用)
③ 其他阻塞:通过调用线程的sleep()或join()发出了I/O请求时,线程就会进入阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态
● 死亡状态:一个运行状态的线程完成任务或者其它终止条件发生时,该线程就切换到终止状态。
五、Thread方法
● Thread(Runnable target, String name):以Runnable创建线程,且指定线程名字
● Thread(ThreadGroup group, Runnable target):以Runnable创建新线程,并指定为group线程组的子线程
● Thread(ThreadGroup group, Runnable target, String name):以Runnable创建新线程,并指定为group线程组的子线程,且指定线程名字。
● Thread(ThreadGroup group, String name):创建线程,并指定为group线程组的子线程,且指定线程名字
● void start():开启线程,Java虚拟机调用线程的run方法。
● void run():封装线程任务,由start方法调用,否则只是一般函数。
● final void setName(String name):设置线程名称。
● final void setPriority(int priority):更改线程优先级。
● final void setDaemon(boolean b):设置守护线程,如果程序中只剩下守护线程在运行的话,那么该程序会停止,java虚拟机退出。该方法必须在启动线程前启动。
● final void join(long millisec):线程加入,等待该线程执行完毕才能执行其它线程;a线程中调用b.join(10),等待b线程执行10毫秒。
● void interrupt():中断线程,需要在线程中调用inderrupted方法进行判断;当调用该方法的时候线程中的inderrupted方法执行,可以中断线程、处理该线程。
● final boolean isAlive():测试线程是否处于执行状态。
● final ThreadGroup getThreadGroup():获取线程对象所属的线程组
● final String getName():获取线程对象的名字
● long getId():获得该线程的唯一标识
● static void yield():暂停当前正在执行的线程对象,并执行其它线程。
● static void sleep(long millisec):让线程休眠,必须要进行捕获或者声明抛出在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
● static boolean holdsLock(Object x):如果当前线程在指定的对象上保持监视器锁,则返回 true。
● static Thread currentThread():获得当前线程,返回对当前正在执行的线程对象引用。
● static void sumpStack():将当前线程的堆栈跟踪打印至标准错误流。该方法仅用于调试。
六、控制线程
● join线程
线程加入,等待该线程执行完毕才能执行其它线程;main线程 中调用 线程2.join(),等待b线程执行完毕再执行其他线程。
class JoinThread extends Thread { // 提供一个有参数的构造器,用于设置该线程的名字 public JoinThread(String name) { super(name); } // 重写run方法,定义线程体 public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + " - " + i); } } } public class Test { public static void main(String[] args) throws InterruptedException { // 启动子线程 new JoinThread("线程1").start(); for (int i = 0; i < 10; i++) { if (i == 5) { JoinThread jt = new JoinThread("线程2"); jt.start(); // main线程调用了jt线程的join方法,main线程必须等jt执行结束才会向下执行 jt.join(); } System.out.println(Thread.currentThread().getName() + " - " + i); } } }
● 守护线程 - setDaemon(boolean b)
有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为“后台线程( Daemon Thread )”,又称为“守护线程””。JVM的垃圾回收线程就是典型的后台线程。
后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。
当所有的前台线程死亡时,后台线程随之死亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。
class DaemonThread extends Thread { public DaemonThread(String name) { super(name); } // 定义后台线程的线程体与普通线程没有什么区别 public void run() { for (int i = 0; i < 1000; i++) { System.out.println(getName() + " - " + i); } } } public class Test { public static void main(String[] args) throws InterruptedException { DaemonThread t = new DaemonThread("后台线层"); // 将此线程设置为后台线程 t.setDaemon(true); t.start(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } // 程序到此执行结束,前台线程(main)结束,后台线程也随之结束 } }
● 线程睡眠 - sleep(long millisec)
让线程休眠,必须要进行捕获或者声明抛出在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
public class Test { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { System.out.println(i + 1 + " - " + new Date()); Thread.sleep(1000);// 线程休眠1秒(1000毫秒) } } }
● 改变线程优先级 - setPriority(int priority)
每个线程执行时都有一定的优先级,优先级高的线程获得较多的执行机会,优先级低的线程则获得较少的执行机会。
每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。
Thread类提供了 setPriority(int newPriority)、 getPriority()方法来设置和返回指定线程的优先级,其中 setPriority()方法的参数可以是一个整数,范围是1-10之间,也可以使用 Thread类的如下三个静态常量
① MAX_PRIORITY:其值是10 ,② MIN_PRIORITY:其值时1 ,③ NORM_PRIPRITY:其值是5
class PriorityTest extends Thread { // 定义一个构造器,用于创建线程时传入线程的名称 public PriorityTest(String name) { super(name); } public void run() { for (int i = 0; i < 20; i++) { System.out.println(getName() + " - 优先级:" + getPriority() + " - 循环变量的值:" + i); } } } public class Test { public static void main(String[] args) throws InterruptedException { // 改变主线程的优先级 Thread.currentThread().setPriority(6); for (int i = 0; i < 30; i++) { if (i == 10) { PriorityTest aThread = new PriorityTest("线程A"); aThread.start(); System.out.println("创建之初的优先级:" + aThread.getPriority()); aThread.setPriority(Thread.MIN_PRIORITY);// 设置该线程为最低优先级 } if (i == 20) { PriorityTest bThread = new PriorityTest("线程B"); bThread.start(); System.out.println("创建之初的优先级" + bThread.getPriority()); bThread.setPriority(Thread.MAX_PRIORITY);// 设置该线程为最高优先级 } } } }
以上是关于Java多线程的主要内容,如果未能解决你的问题,请参考以下文章