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