Java线程详解
程序、进程、线程的概念
程序、进程、线程的概念
程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个程序可同一时间执行多个线程,就是支持多线程的
Java中多线程的创建和使用
一、定义线程
1、继承java.lang.Thread类。
1 ) 定义子类继承Thread类。
2) 子类中重写Thread类中的run方法。
3) 创建Thread子类对象,即创建了线程对象。
4) 调用线程对象start方法:启动线程,调用run方法。
2、实现java.lang.Runnable接口。
1)定义子类,实现Runnable接口。
2)子类中重写Runnable接口中的run方法。
3)通过Thread类含参构造器创建线程对象。
4)将Runnable接口的子类对象作为实际参数传递给
Thread类的构造方法中。
5)调用Thread类的start方法:开启线程,调用
Runnable子类接口的run方法。
2.1、继承方式和实现方式的联系与区别
区别
继承Thread: 线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
实现方法的好处
避免了单继承的局限性
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
3.通过Callable和Future创建线程
通过Callable和Future创建线程的具体步骤和具体代码如下:
• 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
• 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
• 使用FutureTask对象作为Thread对象的target创建并启动新线程。
• 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值其中,Callable接口(也只有一个方法)
4. 通过线程池来创建线程
① new ThreadPoolExecutor(…);
② 创建任务Task implements Callable,重写run()方法;
③ 通过线程池的execute()或submit()将任务command传入线程池;
④ 获取返回值:
实现Callable接口,重写call()方法
class CallableImpl implements Callable
定义线程池
ThreadPoolExecutor executor
利用submit()方法提交任务
Future<?> future = executor.submit(new CallableImpl());
利用FutureTask类get()方法获取返回值
res = task.get();
这里future申明为Future对象,但是它是由FutureTask实现的,也可以直接申明为FutureTask future:
6)关闭线程池 .shutdown();
二、实例化线程
1、如果是扩展java.lang.Thread类的线程,则直接new即可。
2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:
Thread(Runnable target) Thread(Runnable target, String name) Thread(ThreadGroup group, Runnable target) Thread(ThreadGroup group, Runnable target, String name) Thread(ThreadGroup group, Runnable target, String name, long stackSize)
三、Thread类的有关方法
1.start():启动线程并执行相应的run()方法
2.run():子线程要执行的代码放入run()方法中
3.currentThread():静态的,调取当前的线程
4.getName():获取此线程的名字
5.setName():设置此线程的名字
6.yield():调用此方法的线程释放当前CPU的执行权
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法
7.join():在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕,
当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
低优先级的线程也可以获得执行
8.isAlive():判断当前线程是否还存活
9.sleep(long l):显式的让当前线程睡眠l毫秒
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
10.线程通信:wait() notify() notifyAll()
设置线程的优先级
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
四、代码实现
继承于Thread类
//模拟火车站售票窗口,开启三个窗口售票,总票数为100张 //存在线程的安全问题(之后例子用线程同步解决) class Window extends Thread { //静态变量,保证票数统一 static int ticket = 100; public void run() { while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--); } else { break; } } } } public class TestWindow { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } }
实现Runnable接口
//使用实现Runnable接口的方式,售票 /* * 此程序存在线程的安全问题:打印车票时,会出现重票、错票 */ class Window1 implements Runnable { int ticket = 100; public void run() { while (true) { if (ticket > 0) { //线程睡眠10秒,暴露重票、错票问题 try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket--); } else { break; } } } } public class TestWindow1 { public static void main(String[] args) { Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
线程的调度
调度策略
时间片:
抢占式:高优先级的线程抢占CPU
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
线程的优先级
线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:
static int MAX_PRIORITY
线程可以具有的最高优先级。
static int MIN_PRIORITY
线程可以具有的最低优先级。
static int NORM_PRIORITY
分配给线程的默认优先级。
涉及的方法:
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
线程创建时继承父线程的优先级
1.线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。
在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。
注意:线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。
2.当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:
一是选择一个线程运行,直到它阻塞或者运行完成为止。
二是时间分片,为池内的每个线程提供均等的运行机会。
设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
3.线程优先级为110之间的正整数,JVM从不会改变一个线程的优先级。然而,110之间的值是没有保证的。一些JVM可能不能识别10个不同的值,
而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。
代码实现:
//创建多线程的方式一:继承于Thread类 class PrintNum extends Thread{ public void run(){ //子线程执行的代码 for(int i = 1;i <= 100;i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } public PrintNum(String name){ super(name); } } public class TestThread { public static void main(String[] args) { PrintNum p1 = new PrintNum("线程1"); PrintNum p2 = new PrintNum("线程2"); //优先级高的获取CPU执行几率高 p1.setPriority(Thread.MAX_PRIORITY);//10 p2.setPriority(Thread.MIN_PRIORITY);//1 p1.start(); p2.start(); } }
线程的生命周期
一、线程状态
线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是生、死、可运行、运行、等待/阻塞。用一个图来描述如下:
1、新建状态:线程对象已经创建,还没有在其上调用start()方法。
2、就绪状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。
5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
二、阻止线程执行
1、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。当线程睡眠时,它入睡在某个地方,在苏醒之前不会返回到可运行状态。当睡眠时间到期,则返回到可运行状态。
注意:
1、线程睡眠是帮助所有线程获得运行机会的最好方法。
2、线程睡眠到期自动苏醒,并返回到就绪状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
3、sleep()是静态方法,只能控制当前正在运行的线程。
下面给个代码实现:
**
//完成1-100之间自然数的输出。 class PrintNum extends Thread { public void run() { // 子线程执行的代码 for (int i = 0; i <= 100; i++) { if ((i) % 10 == 0) { System.out.println(Thread.currentThread().getName() + ":-------------" + i); } System.out.print(Thread.currentThread().getName() + ":" + i); try { Thread.sleep(1); System.out.print(" 线程睡眠1毫秒!\n"); }catch (InterruptedException e) { e.printStackTrace(); } } } public PrintNum(String name) { super(name); } } public class TestThread { public static void main(String[] args) { PrintNum p1 = new PrintNum("线程"); p1.start(); } }
**
结果:
线程:-------------0 线程:0 线程睡眠1毫秒! 线程:1 线程睡眠1毫秒! 线程:2 线程睡眠1毫秒! 线程:3 线程睡眠1毫秒! 线程:4 线程睡眠1毫秒! 线程:5 线程睡眠1毫秒! 线程:6 线程睡眠1毫秒! 线程:7 线程睡眠1毫秒! 线程:8 线程睡眠1毫秒! 线程:9 线程睡眠1毫秒! 线程:-------------10 线程:10 线程睡眠1毫秒! 线程:11 线程睡眠1毫秒! 线程:12 线程睡眠1毫秒! 线程:13 线程睡眠1毫秒! 线程:14 线程睡眠1毫秒! 线程:15 线程睡眠1毫秒! 线程:16 线程睡眠1毫秒! 线程:17 线程睡眠1毫秒! 线程:18 线程睡眠1毫秒! 线程:19 线程睡眠1毫秒! 线程:-------------20 线程:20 线程睡眠1毫秒! 线程:21 线程睡眠1毫秒! 线程:22 线程睡眠1毫秒! 线程:23 线程睡眠1毫秒! 线程:24 线程睡眠1毫秒! 线程:25 线程睡眠1毫秒! 线程:26 线程睡眠1毫秒! 线程:27 线程睡眠1毫秒! 线程:28 线程睡眠1毫秒! 线程:29 线程睡眠1毫秒! 线程:-------------30 线程:30 线程睡眠1毫秒! 线程:31 线程睡眠1毫秒! 线程:32 线程睡眠1毫秒! 线程:33 线程睡眠1毫秒! 线程:34 线程睡眠1毫秒! 线程:35 线程睡眠1毫秒! 线程:36 线程睡眠1毫秒! 线程:37 线程睡眠1毫秒! 线程:38 线程睡眠1毫秒! 线程:39 线程睡眠1毫秒! 线程:-------------40 线程:40 线程睡眠1毫秒! 线程:41 线程睡眠1毫秒! 线程:42 线程睡眠1毫秒! 线程:43 线程睡眠1毫秒! 线程:44 线程睡眠1毫秒! 线程:45 线程睡眠1毫秒! 线程:46 线程睡眠1毫秒! 线程:47 线程睡眠1毫秒! 线程:48 线程睡眠1毫秒! 线程:49 线程睡眠1毫秒! 线程:-------------50 线程:50 线程睡眠1毫秒! 线程:51 线程睡眠1毫秒! 线程:52 线程睡眠1毫秒! 线程:53 线程睡眠1毫秒! 线程:54 线程睡眠1毫秒! 线程:55 线程睡眠1毫秒! 线程:56 线程睡眠1毫秒! 线程:57 线程睡眠1毫秒! 线程:58 线程睡眠1毫秒! 线程:59 线程睡眠1毫秒! 线程:-------------60 线程:60 线程睡眠1毫秒! 线程:61 线程睡眠1毫秒! 线程:62 线程睡眠1毫秒! 线程:63 线程睡眠1毫秒! 线程:64 线程睡眠1毫秒! 线程:65 线程睡眠1毫秒! 线程:66 线程睡眠1毫秒! 线程:67 线程睡眠1毫秒! 线程:68 线程睡眠1毫秒! 线程:69 线程睡眠1毫秒! 线程:-------------70 线程:70 线程睡眠1毫秒! 线程:71 线程睡眠1毫秒! 线程:72 线程睡眠1毫秒! 线程:73 线程睡眠1毫秒! 线程:74 线程睡眠1毫秒! 线程:75 线程睡眠1毫秒! 线程:76 线程睡眠1毫秒! 线程:77 线程睡眠1毫秒! 线程:78 线程睡眠1毫秒! 线程:79 线程睡眠1毫秒! 线程:-------------80 线程:80 线程睡眠1毫秒! 线程:81 线程睡眠1毫秒! 线程:82 线程睡眠1毫秒! 线程:83 线程睡眠1毫秒! 线程:84 线程睡眠1毫秒! 线程:85 线程睡眠1毫秒! 线程:86 线程睡眠1毫秒! 线程:87 线程睡眠1毫秒! 线程:88 线程睡眠1毫秒! 线程:89 线程睡眠1毫秒! 线程:-------------90 线程:90 线程睡眠1毫秒! 线程:91 线程睡眠1毫秒! 线程:92 线程睡眠1毫秒! 线程:93 线程睡眠1毫秒! 线程:94 线程睡眠1毫秒! 线程:95 线程睡眠1毫秒! 线程:96 线程睡眠1毫秒! 线程:97 线程睡眠1毫秒! 线程:98 线程睡眠1毫秒! 线程:99 线程睡眠1毫秒! 线程:-------------100 线程:100 线程睡眠1毫秒!
2、线程让步yield()
Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。
因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()
达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态
转到就绪状态,但有可能没有效果。
3、join()方法
Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。例如:
Thread t = new MyThread(); t.start(); t.join();
另外,join()方法还有带超时限制的重载版本。例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态。
线程的加入join()对线程栈导致的结果是线程栈发生了变化,当然这些变化都是瞬时的。