标签:may 处理 ati 出现 创建线程 接口 col 阻塞 cancel
几乎所有的操作系统都只支持同时运行多个任务,一个任务就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。
1.1 进程与线程
进程是运行过程中的程序,具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。一般而言,进程包括如下特征
并发性和并行性是两个概念。并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,是的宏观上具有多个进程同时执行的效果。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量。但不拥有系统资源。他和父进程的其他子线程共享进程所拥有的全部资源。线程和其他线程共同协作完成进程的任务。
1.2 线程的创建和启动
1.21 继承Thread类创建线程类。
步骤如下:
① 定义Thread的子类,重写该类的run()方法
② 创建Thread子类的实例,创建子线程对象
③ 调用线程对象的start()方法
package com.gdut.thread; public class FirstThread extends Thread{ private int i; public void run(){ for (; i < 100; i++) { System.out.println(getName()+" "+i); } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()); if(i == 20){ new FirstThread().start(); new FirstThread().start(); } } } }
1.2.2 实现Runnable接口创建线程类
① 定义Runnable接口的实现类,并重写该类的run()方法
② 创建Runnable接口实现类的实例,并以此实例作为Thread的target来创建Thread对象
③ 调用线程对象的start()方法
1.2.3 使用Callable和Future创建线程
Java 5开始,Java提供了Callable接口,该接口提供了一个call()方法可以作为线程执行体,但call()方法更加强大
Java 5提供了Future接口来代表Callable接口里call()方法的返回值,并未Future接口提供了FutureTask实现类,该类实现了,Future接口和Runnable接口,可以作为Thread类的target。
在Future接口定义了如下公共方法来控制关联的Callable任务
创建启动有返回值的线程步骤如下:
① 创建Callable接口的实现类,并实现Call()方法,该Call()方法将作为线程执行体,且该Call方法有返回值,在创建Callable实现类的实例
② 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法
③ 使用FutureTask对象作为Thread对象的Target创建并启动线程
④ 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
package com.gdut.thread; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class ThirdThread { public static void main(String[] args) { ThirdThread rt = new ThirdThread(); FutureTask task = new FutureTask((Callable<Integer>)() ->{ int i =0; for (; i <100 ; i++) { System.out.println(Thread.currentThread().getName()+"循环变量的值"+i); } return i; }); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"循环变量的值"+i); if(i == 20){ new Thread(task,"有返回值的线程").start(); } } try{ System.out.println("子线程的返回值:"+task.get()); }catch(Exception ex){ ex.printStackTrace(); } } }
1.3 创建线程的三种方式对比
采用实现Runnable、Callable接口的方式创建多线程的优缺点
所以一般推荐使用实现Runnable、Callable接口的方式创建多线程。
1.4 线程的生命周期
线程的生命周期经过:新建,就绪,运行,阻塞和死亡5种状态。使用new关键字创建线程,该线程就处于新建状态。当线程对象调用了start()方法之后,该程序就处于就绪状态,Java虚拟机回为其创建方法调用栈和程序计数器,处于这个状态的程序还没有开始运行,只是表示该线程可以运行了,至于何时开始运行,取决于JVM里线程调度器的调度。
处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。
当一个线程开始运行后,他不可能一直处于运行状态(除非它运行执行体非常短,一下子就执行完),线程在执行过程中需要被中断,目的使其他线程获得执行的机会,在选择下一个线程,系统会考虑线程的优先级。
当一个线程调用了它的sleep()和yield()方法后主动放弃所占用的资源。
当发生如下情况,线程会进入阻塞状态:
被阻塞线程在合适的时候重新进入就绪状态。重新等待线程调度器调度它。
当发生特定的情况时可以解除上面的阻塞
线程会以如下三种方式结束,进入死亡状态
Thread提供了让一个线程等待另一个线程完成的方法——join()方法。
当程序执行流中调用了其他线程的join()方法时,调用线程被阻塞,知道被join()方法加入的join线程执行完为止
join()方法通常由使用的线程的程序调用,将大问题划分许多小问题,每个小问题分配一个线程。当所有的小问题处理后,在调用主线程进一步处理。
package com.gdut.thread; public class JoinThread extends Thread{ public JoinThread(String name) { super(name); } @Override public void run() { for (int i = 0; i <100 ; i++) { System.out.println(getName()+" "+i); } } public static void main(String[] args) throws Exception { new JoinThread("新线程").start(); for (int i = 0; i <100 ; i++) { if(i == 20){ JoinThread jt = new JoinThread("被join的线程"); jt.start(); jt.join(); } System.out.println(Thread.currentThread().getName()+" "+i); } } }
有一种线程,后台执行的,它的任务是为其他线程提供服务。这种线程被称为后台线程,又称守护线程或精灵线程。JVM的垃圾回收线程就是典型的后台线程
后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。
如果需要让正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过Thread类的静态方法sleep()来实现。
在调用了sleep()方法之后,线程进入阻塞状态,在其睡眠时间内,该线程将不会获得执行的机会,即使系统中没有其他可执行的线程。
yield()方法是一个和sleep()方法有也是点相似的方法,它Thread类的提供的一个静态方法,它可以让当前正在执行的线程暂停,但它不会阻塞该线程,只是将线程转入就绪状态。yield()方法只是让当前的线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行
关于sleep()方法和yield()方法的区别
每个线程执行时都具有一定的优先级,优先级高的线程得到更多执行的机会,而优先级低的线程则得到较少的执行机会。
每个线程默认的优先级都和创建它的父线程的优先级相同,默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。
Thread类提供了setPriority(int new Priority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority(int new Priority)参数可以是一个整数,范围1~10,也可以使用Thread类的如下三个常量:
3.1 线程安全
脏数据问题,例如多个线程同时操作公共数据引发的数据出错问题,这里不再叙述
3.2 同步代码块
为了解决上面的问题,Java多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。同步代码块的语法格式如下:
synchronized(obj){ //此处代码就是同步代码块 }
上面代码的含义是:线程开始执行同步代码块之前,必须先获得同步监视器的锁定。
3.3 同步方法
与同步代码块对应,Java的多线程安全还支持同步方法,同步方法就是用synchronized关键字来修饰某个方法,则该方法称为同步方法。对于synchronized修饰的方法(非static方法)而言,无须显式指定同步监视器,同步方法的监视器是this,也就是调用该方法的对象
当执行了同步监视器的wait()方法,则当前线程暂停,并释放同步监视器。
3.4 同步锁
从Java 5开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象实现同步,在这种机制下,同步锁由lock充当。
某些锁可能允许对贡献资源的访问。如ReadWriteLock,Lock,ReentrantRock(可重入锁)。为ReadWriteRock提供了ReentrantReadWriteLock实现类。
通常建议在Finally块确保在必要时释放锁。
3.5 死锁
当两个线程相互等待对方释放锁是就会发生死锁。一旦出现死锁,整个程序将不会发生任何异常,也不会给出任何提示。只是所有的线程都处于阻塞状态,无法继续。
标签:may 处理 ati 出现 创建线程 接口 col 阻塞 cancel
原文地址:https://www.cnblogs.com/yumiaoxia/p/9048827.html