1、进程与线程
进程:一个应用程序的运行就是一个进程
多进程:表示一个时间段上多个程序依次进行,而在一个时间点上只会有一个进程执行
线程:在进程的基础之上划分的更小的程序单元,线程式在进程基础上创建并使用的,所以线程依赖于进程。但现成的启用速度要比进程快,所以当使用多线程进行并发处理时,其执行的性能要高于进程。
注意:进程运行在windows系统之上,线程运行于进程基础之上
在Java中实现多线程,需要一个专门的线程主体类进行线程的执行任务的定义,而这个主体类需要继承特定的父类或者实现特定的接口才可以完成
2、第一种多线程实现方式:Thread类实现多线程
若有一个类继承了Thread类,且覆写Thread类中提供的一个run()方法,就表示此类为线程的主体类,子类覆写的这个方法就叫线程的主方法,所有要执行的功能都应该写在此方法中。
1)范例:多线程主体类
//定义一个线程主体类
class MyThread extends Thread{
//定义类的属性
private String title;
//实现有参构造
public MyThread(String title) {
super();
this.title = title;
}
//覆写线程的run方法
@Override
public void run(){//线程的主体方法
for (int x = 0;x < 10;x++){
System.out.println(this.title + "运行,x=" + x);
}
}
}
注意:在启动多线程时,run()方法并不能通过实例化对象进行调用,只能使用start()方法进行调度。
若使用对象实例化调用,代码如下:
package cn.demos;
//定义一个线程主体类
class MyThread extends Thread{
//定义类的属性
private String title;
//实现有参构造
public MyThread(String title) {
super();
this.title = title;
}
//覆写线程的run方法
@Override
public void run(){//线程的主体方法
for (int x = 0;x < 10;x++){
System.out.println(this.title + "运行,x=" + x);
}
}
}
public class Demo1 {
public static void main(String[] args) {
new MyThread("线程A").run();
new MyThread("线程B").run();
new MyThread("线程C").run();
}
}
结果 | 线程A运行,x=0 线程A运行,x=1 线程A运行,x=2 线程A运行,x=3 线程A运行,x=4 线程A运行,x=5 线程A运行,x=6 线程A运行,x=7 线程A运行,x=8 线程A运行,x=9 线程B运行,x=0 线程B运行,x=1 线程B运行,x=2 线程B运行,x=3 线程B运行,x=4 线程B运行,x=5 线程B运行,x=6 线程B运行,x=7 线程B运行,x=8 线程B运行,x=9 线程C运行,x=0 线程C运行,x=1 线程C运行,x=2 线程C运行,x=3 线程C运行,x=4 线程C运行,x=5 线程C运行,x=6 线程C运行,x=7 线程C运行,x=8 线程C运行,x=9 |
可以看到程序结果是按顺序进行,A线程计算完成才进行B线程的计算,随后是C线程,并没有实现交替进行的结果。所以,启动多线程时,需要使用start()方法来进行调用。
多线程启动:使用start()方法,代码如下
package cn.demos;
//定义一个线程主体类
class MyThread extends Thread{
//定义类的属性
private String title;
//实现有参构造
public MyThread(String title) {
super();
this.title = title;
}
//覆写线程的run方法
@Override
public void run(){//线程的主体方法
for (int x = 0;x < 10;x++){
System.out.println(this.title + "运行,x=" + x);
}
}
}
public class Demo1 {
public static void main(String[] args) {
new MyThread("线程A").start();
new MyThread("线程B").start();
new MyThread("线程C").start();
}
}
结果 | 线程A运行,x=0 线程A运行,x=1 线程A运行,x=2 线程C运行,x=0 线程B运行,x=0 线程B运行,x=1 线程B运行,x=2 线程B运行,x=3 线程B运行,x=4 线程B运行,x=5 线程B运行,x=6 线程B运行,x=7 线程B运行,x=8 线程B运行,x=9 线程C运行,x=1 线程A运行,x=3 线程C运行,x=2 线程A运行,x=4 线程A运行,x=5 线程A运行,x=6 线程A运行,x=7 线程A运行,x=8 线程A运行,x=9 线程C运行,x=3 线程C运行,x=4 线程C运行,x=5 线程C运行,x=6 线程C运行,x=7 线程C运行,x=8 线程C运行,x=9 |
如上结果交替显示,表示线程启动成功,执行顺序不可控。
每一个线程对象只能启动一次,若重复启动会报以下错误:
问题 | 主要代码 | 信息 |
重复启动 | public static void main(String[] args) { } | Exception in thread "main" java.lang.IllegalThreadStateException |
3、第二种多线程实现方式:Runnable接口实现多线程
1)范例:定义并启动多线程(通常方法)
package cn.demos;
//定义一个线程主体类
class MyThread implements Runnable {
// 定义类的属性
private String title;
// 实现有参构造
public MyThread(String title) {
super();
this.title = title;
}
// 覆写线程的run方法
@Override
public void run() {// 线程的主体方法
for (int x = 0; x < 10; x++) {
System.out.println(this.title + "运行,x=" + x);
}
}
}
public class Demo1 {
public static void main(String[] args) {
// 启动多线程
Thread threadA = new Thread(new MyThread("线程AAAA"));
Thread threadB = new Thread(new MyThread("线程BBBB"));
Thread threadC = new Thread(new MyThread("线程CCCC"));
// 启动多线程
threadA.start();
threadB.start();
threadC.start();
}
}
结果 | 线程AAAA运行,x=0 线程BBBB运行,x=0 线程CCCC运行,x=0 线程BBBB运行,x=1 线程BBBB运行,x=2 线程BBBB运行,x=3 线程BBBB运行,x=4 线程AAAA运行,x=1 线程CCCC运行,x=1 线程AAAA运行,x=2 线程CCCC运行,x=2 线程CCCC运行,x=3 线程CCCC运行,x=4 线程AAAA运行,x=3 线程AAAA运行,x=4 |
通过文档,观察Runnable接口定义,从之前的学习中可以得知@FunctionalInterface表示函数编程,即Lamda表达式编程,下面的例子将使用Lamda表达式进行启动线程
Runnable接口定义 | @FunctionalInterface //表示函数式接口 } |
2)范例:定义并启动多线程(Lamda表达式启动)
代码 | 结果 |
public class Demo1 { public static void main(String[] args) { | 线程对象-0运行,y=0 线程对象-2运行,y=0 线程对象-2运行,y=1 线程对象-2运行,y=2 线程对象-2运行,y=3 线程对象-2运行,y=4 线程对象-1运行,y=0 线程对象-0运行,y=1 线程对象-1运行,y=1 线程对象-0运行,y=2 线程对象-0运行,y=3 线程对象-0运行,y=4 线程对象-1运行,y=2 线程对象-1运行,y=3 线程对象-1运行,y=4 |
4、Thread与Runnable关系
Thread类 | Runnable接口 | |
定义 | | public interface Runnable{ } |
代码 | package cn.demos; //定义一个线程主体类 public static void main(String[] args) { } | public class Demo1 { public static void main(String[] args) { |
联系 | 1、从定义中可以看出,Thread类实现了Runnable接口,即Thread类属于Runnable的子类 2、Runnable接口里只定义了一个run()方法,所以,Thread类中的run()方法是覆写的Runnable里的 3、线程对象先调用Thread类中的start()方法,然后start()方法里调用run()方法,具体可看源码 |
1)范例:利用卖票程序来实现多个线程的资源并发访问
代码 | 结果 |
package cn.demos; //定义一个线程主体类 private int ticket = 5; @Override } public class Demo1 { public static void main(String[] args) { MyThread mt = new MyThread(); } | 卖票,ticket=5 卖票,ticket=3 卖票,ticket=1 卖票,ticket=4 卖票,ticket=2 |
5、Callable接口实现多线程
在JDK1.5之后,新增了一个新的线程实现接口,解决了Runnable接口没有返回值的问题
Callable | FutureTask | RunnableFuture | Runnable | Future | |
定义 | | public class FutureTask<V> extends Object implements RunnableFuture<V> | public interface RunnableFuture<V> extends Runnable, Future<V> | public interface Runnable{ } | public interface Future<V>{ //get实现返回值接收 |
FutureTask |
public class FutureTask<V> extends Object implements RunnableFuture<V>{ public FutureTask(Callable<V> callable); } |
由定义可以看出,Callable定义的时候可以设置一个泛型,此泛型的类型就是返回的数据类型,如此可以避免向下转型带来的安全隐患。
关系图:
2)范例:使用Callable实现多线程处理
代码 | 结果 |
package cn.demos; import java.util.concurrent.Callable; class MyThread implements Callable<String> { } | 线程执行,x=0 线程执行,x=1 线程执行,x=2 线程执行,x=3 线程执行,x=4 线程返回数据!!!!线程执行完毕 |
面试题:Runnable与Callable的区别
区别 | Runnable | Callable |
1 | 提出时间是JDK1.0 | 提出时间是JDK1.5 |
2 | java.lang.Runnable接口之中只提供有一个run()方法,且没有返回值 | java.util.concurrent.Callable接口中提供有call()方法,可以有返回值 |
总结:不论使用何种方法实现多线程,线程的启动只有一个方法,即Thread类的对象.start()方法。
6、多线程运行状态
a.任何一个线程的对象都会使用Thread类进行封装,而线程的启动时调用start(),所以,当对象调用start()方法后,线程其实并没有执行,而是进入了一种就绪状态;
b.进入就绪状态之后需要等待进行资源调度,当某一个线程调度成功之后则进入运行状态(run()方法);
c.线程并不会一直运行,中间需要产生一些暂停状态,例如:某个线程执行一段时间之后需要让出资源(或理解为资源被其他线程抢占),此时就会进入堵塞状态,随后会重新回归到就绪状态;
d.当run()方法执行完毕之后,实际上该线程的主要任务也结束了,那么此时直接进入停止状态。