Java多线程,主要有三种创建模式:通过继承Thread类;通过实现Runnable接口;通过实现Callable接口。
由于Java的单继承、多实现性,推荐多使用实现接口的方式。
继承Thread类
通过继承Thread类,并重写run()方法,将线程中需要执行的代码内容写在run()方法中。
在通过实例化对象的start()方法,使线程进入就绪状态。注意,不是调用run()方法,直接调用run()方法则是在单个线程中执行,并没有启用新线程,新线程的启用是在start()中实现的。
实例:
public class Test extends Thread{//继承Thread
@Override
public void run() {
for(int i=0;i<5;i++) {
try {
Thread.sleep(100); //run()方法中,不能throws异常,只能进行捕获处理
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("听歌");
}
}
public static void main(String[] args) throws InterruptedException {
//创建子类对象,新建状态
Test t =new Test();
//启动,就绪状态
t.start(); //父类Thread的方法,等待cpu调度器调用
for(int i=0;i<5;i++) {
Thread.sleep(100);
System.out.println("在CSND写笔记"); //在main线程中进行
}
}
}
--------------------------------------------------
输出结果为:
听歌
在CSND写笔记
在CSND写笔记
听歌
听歌
...
实现Runnable接口
通过继承Runnable接口,并重写run()方法,将线程中需要执行的代码内容写在run()方法中。
此种创建模式,多线程类并没有获得Thread类的start()方法。这时需要通过新建Thread类,将多线程对象丢入构造器种,并且可以同时为线程命名(可查看Thread构造器源码了解),然后再使用新建Thread类的start()方法来启用新线程。
实例:
public class Test implements Runnable{//实现Runnable
@Override
public void run() {
for(int i=0;i<5;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {//run()方法中,不能throws异常,只能进行捕获处理
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName()+" 在听歌");
}
}
public static void main(String[] args) throws InterruptedException {
// Test t =new Test(); 实例化测试类
// Thread thread = new Thread(t); 新建Thread类
// thread.start();//start()方法,线程就绪
// new Thread(new Test()).start();//匿名处理
new Thread(new Test(),"耳朵").start();//start()方法,启用新线程并就绪
Thread.currentThread().setName("手");//为线程命名,此处为main线程
for(int i=0;i<5;i++) {
Thread.sleep(100);
System.out.println("线程:"+Thread.currentThread().getName()+" 在CSND写笔记");//获取当前线程名
}
}
}
---------------------------------
输出结果为:
线程:手 在CSND写笔记
线程:耳朵 在听歌
线程:耳朵 在听歌
线程:手 在CSND写笔记
线程:耳朵 在听歌
...
实现Callable接口
通过继承Callable接口,并重写call()方法,将线程中需要执行的代码内容写在call()方法中。与run()方法不同,call()方法是有返回值,并可以向上抛出异常的。
此种创建模式,启用多线程时,需要借助Executors类创建线程池(pool的概念),再通过submit()方法提交执行。
实例:
public class Test implements Callable<Boolean>{//实现Callable接口,有泛型,指定call()返回值类型
@Override
public Boolean call() throws Exception { //有返回值
Thread.currentThread().setName("耳朵");
for(int i=0;i<5;i++) {
Thread.sleep(100);//call()方法中,可以用throws抛出异常
System.out.println("线程:"+Thread.currentThread().getName()+" 在听歌");
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行:
Future<Boolean> result =ser.submit(new Test()) ;
Thread.currentThread().setName("手");
for(int i=0;i<5;i++) {
Thread.sleep(100);
System.out.println("线程:"+Thread.currentThread().getName()+" 在CSND写笔记");
}
boolean res = result.get(); //线程执行结束后才能获得结果
System.out.println(res);
}
}
-------------------------------------------------------
输出结果为:
线程:手 在CSND写笔记
线程:耳朵 在听歌
线程:手 在CSND写笔记
线程:耳朵 在听歌
线程:耳朵 在听歌
...
true
线程属性设置
可以查看Thread类种,有许多get/set方法,这些方法就是用来操作线程的属性,如:设置线程名、设置优先级、置为守护线程、设置线程组等。
线程的属性一般尽量在线程启动start()前设置。
线程名
在上面的实例中以及使用到了线程名,通过setName()方法命名,通过getName()方法获取名字。
上面实例中已有,这里不再累赘写实例。
线程优先级
线程的优先级概念,并不代表线程的执行顺序,不是排队的概念。而是优先级高的,下次调度器选线程时被选中的概率大,反之概率小,是一种概率问题。
线程优先级为1至10,默认优先级为5。越大优先级越高,若输入参数不再1至10,回抛异常。
通过setPriority()方法设置优先级,通过getPriority()方法获取优先级信息。实例:
Thread thread = new Thread(new Test(),"耳朵");
thread.setPriority(10);//设置优先级为10
thread.start();//start()方法,启用新线程并就绪
守护线程
守护线程,与普通线程(默认未非守护线程)相比,主要区别就是执行完毕的时机问题:
-
当程序中还有普通线程未执行完时,java虚拟机会持续继续执行。
-
当程序中仅有守护线程未执行完时,java虚拟机不会理会守护线程,会直接停止。
守护线程通过setDaemon()方法设置,通过isDaemon()方法获取信息。实例:
public class Test implements Runnable{//实现Runnable
@Override
public void run() {
for(;true;) { //死循环,永久执行
try {
Thread.sleep(100);
} catch (InterruptedException e) {//run()方法中,不能throws异常,只能进行捕获处理
e.printStackTrace();
}
System.out.println("线程:"+Thread.currentThread().getName()+" 在听歌");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Test(),"耳朵");
thread.setDaemon(true);//设为守护线程,可以改为false再执行对比一下
thread.start();//start()方法,启用新线程并就绪
Thread.currentThread().setName("手");//为线程命名,此处为main线程
for(int i=0;i<5;i++) {
Thread.sleep(100);
System.out.println("线程:"+Thread.currentThread().getName()+" 在CSND写笔记");//获取当前线程名
}
}
}
虽然线程中写了死循环,但设置线程为守护线程,当其他线程都执行结束后,守护线程也会被终止,而不是永远执行下去。