多线程编程1-定义理解与三种实现方式
多线程编程
进程与线程的理解:
进程: 是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程就是进程产生、发展到最终消亡的过程;
多进程: 操作系统能同时运行多个进程(程序),由于CPU
具备分时机制,在每个进程都能循环获得自己的CPU时间片;由于CPU执行的速度非常快,使得所有的程序好像是在同时运行一样。
进程与线程的区别与联系:
- 进程是资源调度的基本单位,运行一个可执行程序会创建一个或多个线程,进程就是运行起来的可执行程序;
- 线程是程序执行的基本单位,是轻量级的进程,每个进程中都有唯一的主线程,且只能有一个,主线程和进程是相互依存的关系,主线程结束,进程也会结束。
具体实例(word):
每次启动Word对于操作系统而言就相当于启动了一个系统的进程,而在这个进程之上又有许多其他程序在运行(拼写检查等),那么对于这些程序就是一个个多线程。如果Word关闭了,则这些拼写检查的线程也肯定会消失,但是如果拼写检查的线程消失了,并不一定会让Word的进程消失;
多插一句:如果打开两个word文档,则表示当前操作系统创建了两个进程。
多线程实现:
实现多线程需要一个线程的主体类,这个类可以继承Thread
、实现Runnable
以及Callable
接口完成定义;
Thread实现多线程:
继承结构如下:
public class Thread extends Object implements Runnable
实现接口Runnable
,所以必须实现接口中的抽象方法:
Modifier and Type | Method | Description |
---|---|---|
void |
run() |
当一个实现接口Runnable的对象被用来创建线程时,启动线程会导致对象的run方法在单独执行的线程中被调用。 |
void |
start() |
使线程开始执行;Java虚拟机调用这个线程的run方法。 |
当产生多个对象时,这些对象就会并发的执行run()方法中的代码;
虽然多线程的执行方法都在run()方法中定义,但是在实际进行多线程启动时并不能直接调用此方法,由于多线程时需要并发执行的,所以需要通过操作系统的资源调度才能执行,这样多线程的启动就必须利用Thread类中的start()方法完成,调用此方法会间接的调用run()方法。
实例:
package Java从入门到项目实战.多线程编程.Java多线程实现;
class MyThread extends Thread{ //单继承
private String title;
public MyThread(String title){
this.title = title;
}
//覆写线程的run方法
@Override
public void run() {
for (int i = 0 ; i < 10; i++){
System.out.println(this.title+"运行,i =" +i);
}
}
}
public class Main{
public static void main(String[] args){
new MyThread("线程A").start(); //实例化线程对象并启动
new MyThread("线程B").start();
new MyThread("线程C").start();
//对照
/*没有开启多线程*/
new MyThread("线程A").run();
new MyThread("线程B").run();
new MyThread("线程C").run();
}
}
由效果图可以看出,三个线程在交替执行:
假如面试题:
为什么线程启动的时候必须调用start()方法而不是直接调用run()方法?
在本程序中,程序调用了Thread类继承而来的start()方法后,实际上他执行的还是覆写后的run()方法,那为什么不直接调用run()?
简单的说下:是因为多线程需要调用操作系统的资源,在start()下有一个关键的部分start0()方法,并且在start0()方法上使用了navite关键字定义;
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(); //navite
什么是navite?
navite是指:Java本机接口(Java Native Interface)简称:JNI;使用Java调用本机操作系统的函数功能完成一些特殊操作;
在Java中将start0()方法体交给JVM进行实现,所以这样就会出现在windows或者在Linux中实现的start0()的是不同,不关系过程,只关心结果(是否调用了本机的操作系统的函数);
start0()作用:交由JVM进行匹配不同的操作系统,实现start0()方法体,功能:实现本机函数的调用;
具体百度、Google吧。
Runnable接口实现多线程:
出现的原因:为了解决Thread实现多线程出现的单继承问题;并且增加了函数式接口;
Modifier and Type | Method | Description |
---|---|---|
void |
run() |
当一个实现接口Runnable的对象被用来创建线程时,启动线程会导致对象的run方法在单独执行的线程中被调用。 |
实现代码:
class MyThread implements Runnable{
private String title;
public MyThread(String title){
this.title = title;
}
@Override
public void run() { //线程方法覆写
for (int i = 0; i< 10;i++){
System.out.println(this.title+"运行,i"+i);
}
}
}
启动方式一:
Thread threadA = new Thread(new MyThread("线程A"));
Thread threadB = new Thread(new MyThread("线程B"));
Thread threadC = new Thread(new MyThread("线程C"));
Thread threadD = new Thread(new MyThread("线程D"));
Thread threadE = new Thread(new MyThread("线程E"));
threadA.start();
threadB.start();
threadC.start();
threadD.start();
threadE.start();
启动方式二:
//通过Lambal表达式定义线程主体
for(int x = 0; x < 3;x++){
String title = "线程对象-"+x;
//实际上Thread传入的类型是Runnable
new Thread(()->{ //Lambda实现线程主体
for(int y = 0; y < 20; y++){
System.out.println(title+"运行,y"+y);
}
}).start();
}
Thread与Runnable的联系:
继承结构:
public class Thread extends Object implements Runnable
在之前继承Thread类的时候实际上覆写的还是Runnable接口的run()方法。
实现并发访问资源:
package Java从入门到项目实战.多线程编程.Java多线程实现;
class MyThreadConcurrent implements Runnable {
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//同步操作--》从5-1票数
/*synchronized(this){
if(this.ticket > 0){
System.out.println("卖票,ticket = "+this.ticket--);
}
}*/
//票数乱数
if(this.ticket > 0){
System.out.println("卖票,ticket = "+this.ticket--);
}
}
}
}
public class 并发资源访问 {
public static void main(String[] args) {
MyThreadConcurrent thread = new MyThreadConcurrent();
new Thread(thread).start(); //第一个线程
new Thread(thread).start(); //第二个线程
new Thread(thread).start(); //第三个线程
}
}
总结一句话:Thread有单继承的局限性以及在有些情况下结构的不合理性;所以后面多线程的实现使用的都是Runnable接口。
Callable接口实现多线程:
为什么要使用Callable接口来实现多线程?
因为使用Callable接口实现弥补了Runnable实现多线程没有返回值的问题。
继承结构如下:
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception{
}
}
定义的时候可以设置一个泛型,此泛型的类型就是call()方法的返回的数据类型,好处:可以避免向下转型的安全隐患。
线程类主体完成后,需要启动多线程的话还是需要通过Thread类实现的,又因为我们的Callable接口与Thread没有联系,所以我们需要FutureTask类实现两者之间的联系;如图所示:
通过FutureTask类继承结构可以发现它是Runnable接口的子类;
代码实现如下:
package Java从入门到项目实战.多线程编程.Java多线程实现;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class CallableThread implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("线程执行 x = "+i);
}
return "xbhog";
}
}
public class Callable接口实现多线程 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//将Callable实例化包装在FutureTask类中,这样就可以与Runnable接口关联
FutureTask<String> task = new FutureTask<String>(new CallableThread());
//线程启动
new Thread(task).start();
//获取call()的返回值
System.out.println("【线程返回数据】:"+task.get());
}
}