Java多线程
Java多线程
现代操作系统是多任务操作系统。多线程是实现多任务的一种方式。
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
“同时”执行是人的感觉,在线程之间实际上轮换执行。
Java中的线程
在java中要想实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runable接口。
继承Thread类
对于直接继承Thread的类来说,代码大致框架是:
class 类名 extends Thread{
属性1;
属性2;
...
public void run(){
}
方法1;
方法2;
...
}
Demo:
ThreadTest.java
package org.hibiscus.thread;
public class ThreadTest extends Thread {
private String names;
public String getNames() {
return names;
}
public void setNames(String names) {
this.names = names;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.names+":\t"+i);
}
}
}
main.Java
package org.hibiscus.main;
public class Main {
public static void main(String[] args) {
ThreadTest testA = new ThreadTest();
testA.setNames("A");
ThreadTest testB = new ThreadTest();
testB.setNames("B");
testA.start();
testB.start();
}
}
执行Main,会看到A与B的交替打印
有时因为系统分配CPU时间的问题。可能出现先打印A再打印B,或者先打印B后打印A的情况,可以将循环次数改大一点,或者多运行几次可以看到效果。
这里使用的是start()方法而不是run(),因为多线程需要系统支持,所以start()方法调用系统底层函数来运行run()方法以实现多线程。如果start方法重复调用,会出现java.lang.IllegalThreadStateException异常。
实现Runnable接口
对于实现Runnable接口来说,代码大致框架是:
class 类名 implements Runnable{
属性1;
属性2;
...
public void run(){
}
方法1;
方法2;
...
}
Demo
RunnableTest.java
package org.hibiscus.thread;
public class RunnableTest implements Runnable {
private String names;
public String getNames() {
return names;
}
public void setNames(String names) {
this.names = names;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.names+":\t"+i);
}
}
}
Main.java
package org.hibiscus.main;
import org.hibiscus.thread.RunnableTest;
public class Main {
public static void main(String[] args) {
RunnableTest testA = new RunnableTest();
testA.setNames("A");
RunnableTest testB = new RunnableTest();
testB.setNames("B");
Thread threadA = new Thread(testA);
Thread threadB = new Thread(testB);
threadA.start();
threadB.start();
}
}
关于选择继承Thread还是实现Runnable接口?
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
Demo:
class Saler implements Runnable{
private int ticket = 5; //5张票
public void run() {
for (int i=0; i<=20; i++) {
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName()+ "正在卖票"+this.ticket--);
}
}
}
}
public class Main {
public static void main(String [] args) {
Saler my = new Saler();
new Thread(my, "1号窗口").start();
new Thread(my, "2号窗口").start();
new Thread(my, "3号窗口").start();
}
}
实现Runnable接口比继承Thread类所具有的优势:
适合多个相同的程序代码的线程去处理同一个资源
可以避免java中的单继承的限制
增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
注意: main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。
因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM就是在操作系统中启动了一个进程。
线程的生命周期
线程的生命周期是指一个线程从创建到消亡的过程。如下图示:
线程的生命周期可以分为四个状态:
创建状态
当用new操作符创建一个新的线程对象时,该线程处于创建状态。
可运行状态
执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体——run()方法,这样就使得该线程处于可运行状态(Runnable)。
这一状态并不是运行中状态(Running),因为线程也许实际上并未真正运行。
不可运行状态
当发生下列事件时,处于运行状态的线程会转入到不可运行状态:
调用了sleep()方法;
线程调用wait()方法等待特定条件的满足;
线程输入/输出阻塞。
返回可运行状态
处于睡眠状态的线程在指定的时间过去后;
如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变;
如果线程是因为输入输出阻塞,等待输入输出完成。
- 消亡状态
当线程的run()方法执行结束后,该线程自然消亡。
Thread的常用方法
- isAlive()
public final boolean isAlive()
测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
- isDaemon()
public final boolean isDaemon()
测试该线程是否为守护线程。
- isInterrupted()
public boolean isInterrupted()
测试线程是否已经中断。线程的中断状态 不受该方法的影响。
线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来。
- join()
public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException
public final void join(long millis,int nanos) throws InterruptedException
等待该线程终止。带参数的方法等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
- sleep()
public static void sleep(long millis) throws InterruptedException
public static void sleep(long millis,int nanos) throws InterruptedException
在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。
- interrupt()
public void interrupt()
中断线程。
如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。
如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。
如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。
如果以前的条件都没有保存,则该线程的中断状态将被设置。
中断一个不处于活动状态的线程不需要任何作用。
- setPriority()
public final void setPriority(int newPriority)
更改线程的优先级。
不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源。
线程的默认优先级是5
Thread提供了三个常量
Thread.MAX_PRIORITY 线程可以具有的最高优先级。
Thread.MIN_PRIORITY 线程可以具有的最低优先级。
Thread.NORM_PRIORITY 分配给线程的默认优先级。
- yield()
public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。