一、创建线程的三种方式
1. 继承Thread类,重写run()方法
- 代码展示
public class myThread extends Thread {
//成员变量
private String title;
//构造方法
public myThread(String title){
this.title=title;
}
//业务实现
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(title+"运行,x="+ i);
}
}
public static void main(String[] args) {
myThread t1=new myThread("线程1");
myThread t2=new myThread("线程2");
myThread t3=new myThread("线程3");
t1.start();
t2.start();
t3.start();
}
}
注意:
- 启动线程需要调用start()方法
- 同一线程对象只能调用一次start()方法,否则会抛出
IllegalThreadStateException
- 如果想调用对象的run()方法来启动线程,则代码只会按序输出并不会启动多线程
- 如果获取当前线程无需调用
Thread.currentThread()
,直接使用this获得即可。
- 为什么是start()方法
贴一下源代码:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
分析:
这里主要介绍以下start0()方法,源码中可以看到该方法使用native修饰,这意味着它可以调用我们所使用机器的操作系统的底层函数,这项技术实际上是JNI(Java native interface)。
由于不同操作系统对于资源分配的算法不同,所以为了满足不同层次的开发者,Java提供了start0()方法,当编译好的字节码文件在jvm中运行时,jvm使用JNI技术调用当前操作系统的底层函数用来匹配start0()方法,进而实现了线程在机器上的调度。
从编程的角度来看,Java中的多线程必须使用Thread类中的start()方法启动。
2.继承Runnable接口,实现run()方法
在Java中受限于单继承的限制,所以在有些时候使用继承Thread是不合适的。这时我们就可以通过实现Runnable接口的方式进行多线程编程。代码如下:
public class MThread implements Runnable{
private String title;
public MThread(String title){
this.title=title;
}
//实现run()
public void run() {
for(int i=0;i<5;i++){
System.out.println(title+"运行,x="+ i);
}
}
//启动多线程
public static void main(String[] args) {
Thread t1=new Thread(new MThread("线程1"));
t1.start();
}
}
注意:
- 前面我们提到过启动多线程必须使用Thread类中的start()方法,而Runnable接口中并没有提供start()方法,这时候我们就必须构造一个Thread类。下面看一段源代码:
//Thread的构造方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
//Thread的run方法
public void run() {
if (target != null) {
target.run();
}
}
可以看到,Thread中的run()方法实际上是调用的Runnable对象中的run()方法。当我们把自己创建的实现Runnable接口的对象传入构造方法时,我们可以得到一个等价于复写父类中run()方法的效果。
- 实际上,Java多线程的设计使用了代理模式的设计。Thread类是一个代理类,开发者只需要关注如何实现run()方法。
- Thread类描述线程,实现Runnable接口的子类用于描述资源。下面代码展示了一个售票程序的简单代码。即多个线程访问同一资源。
//实现runnable的子类,描述资源
public class MThread implements Runnable{
private int tickets=5;
private String saler;
public MThread(String saler){
this.saler=saler;
}
//实现run()
public void run() {
for(int i=0;i<100;i++){
if(tickets>0) {
System.out.println(saler + " 卖票,票还剩余" + tickets--);
}
}
}
}
public class myThread {
//启动多线程,Thread描述线程
public static void main(String[] args) {
MThread mt=new MThread("多线程");
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
上述程序的内存结构图如下:
##### 3.继承Callable接口
//实现callable的子类
public class MThread implements Callable<String> {
public String call() throws Exception {
for(int i=0;i<5;i++){
System.out.println("************售票************");
}
return "售罄";
}
}
//启动多线程
public class myThread {
public static void main(String[] args) throws Exception{
FutureTask<String> task=new FutureTask<String>(new MThread());
Thread t=new Thread(task);
t.start();
System.out.println("多线程执行完毕:"+task.get());
}
}
注意:Runnable和Callable的区别
- Runable是JDK1.0提出的多线程接口,Callable是JDK1.5之后
- Callable接口中提供call()方法,并且提供返回值
- Runnable中只提供run()方法,没有返回值
二、多线程的操作
Java中对于多线程的操作主要封装在Thread类中。
1.线程的命名和获取
Java对于线程的操作是基于线程名的。如果不设置名字系统会自动设置不重复的名字。
- 构造方法设置名字:
public Thread(Runnable target, String name)
- 设置名字:
public final synchronized void setName(String name)
- 获取名字:
public final String getName()
2.线程休眠
线程休眠是指是线程暂缓一段时间执行,过了规定的时间,线程被自动唤醒。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException
注意:
这两个方法都会抛出中断异常(InterruptedException),这个异常是Exception的子类,不是RuntimeException的子类,所以需要手动处理。
3.线程中断
在之前线程休眠可以抛出中断异常,这就提示我们线程的休眠是可以被中断的。
- 判断线程是否被中断:
public boolean isInterrupted()
- 中断线程:
public void interrupt()
4.线程的强制执行
一直占用资源直至线程任务执行完毕。
public final void join() throws InterruptedException
5.线程的礼让
调用该方法的线程会自动让出当前占用的资源。
public static native void yield()
6.线程的优先级
- 设置优先级:
public final void setPriority(int newPriority)
- 获取优先级:
public final int getPriority()
- 静态常量:
Thread.MAX_PRIORITY
10、Thread.NORM_PRIORITY
5、Thread.MIN_PRIORITY
1 - 线程的默认优先级是
Thread.NORM_PRIORITY
5
关于三种方式创建多线程得小结:
采用实现Runnable、Callable接口方式创建多线程的优缺点:
- 线程类只是实现了接口,还可以继承其他类。
- 这种方式下多个线程可以共享同一个target对象,所以非常适合多个相同线程处理同一份资源的情况。
- 缺点是编程稍复杂,访问当前线程需要调用调用
Thread.currentThread()
采用继承Thread类创建多线程的优缺点:- 优点是编写简单,访问当前线程直接使用this即可
- 劣势是无法继承其他父类
关于sleep()和yield()方法的区别:
- sleep()方法暂停当前线程后会个其他线程(无论优先级高低)执行机会,而yield()方法暂停当前线程后,只有优先级大于或等于当前线程的就绪线程才能执行。
- sleep()方法会使当前线程进入阻塞状态,只有在过了规定时间后才能进入就绪状态。yield()方法会使当前线程进入就绪态。
- sleep()方法声明抛出了异常,yield()方法没有。
- sleep()方法有更好的可移植性,推荐用sleep()方法控制并发线程的执行。