多线程基本概念
线程是在进程的基础上划分的更小的程序单元,线程是在进程基础上创建并使用的,所以线程依赖于进程的支持,但是线程的启动速度要比进程快许多。所以当使用多线程进行并发处理的时候,其执行的性能要高于进程。
Java是多线程的编程语言,所以Java在进行并发访问处理的时候可以得到更高的处理性能。
多线程基础
Java中实现多线程的定义,需要使用一个专门的线程类,进行线程任务的定义。这个定义是有要求的, 必须实现特定的接口或者继承特定的父类才可以进行。
继承Thread类实现多线程
public class Thread implements Runnable {
Java里提供了一个java.lang.Thread的程序类,只要继承了此类就表示为这个类为线程的主体类。但是并不是继承了此类就实现了多线程处理了,还需要覆写 Thread类的run() 方法,这个方法是线程的主方法。多线程要执行的功能都应该在run()方法中定义。需要说明的是:run()方法是不能直接调用的,因为这里面牵扯到一个操作系统的资源调度问题,所以要想启动多线程,我们要调用start()方法。
为什么执行Thread类的start()方法就启动了多线程
这个模块对这一部分知识进行一个延申。首先来查看Threa的start()方法源码
public synchronized void start() {
// 判断线程的状态
if (threadStatus != 0)
// 下边的 这个异常,在这里既没有抛出也没有处理,因此推断是RuntimeException的子类
// 这个异常是在当前线程启动两次引发的!
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
// 这里调用了这个方法
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
// 这个方法只定义了方法名称,但是没有实现。但是发现它是“native”修饰的方法!
private native void start0();
在Java程序执行的过程之中考虑到对于不同层次开发者的需求,所以其支持有本地的操作系统函数调用。而这项技术被称为JNI(java native interface)技术,但是Java开发过程之中并不推荐这样使用。利用这项技术可以使用一些操作系统提供的底层函数进行一些特殊的处理。而在Thread类里面提供的start0()就表示需要将此方法交给不同的操作系统去实现。
任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Thread类的start()方法
实现Runnable接口实现多线程
@FunctionalInterface // 函数式编程的支持
public interface Runnable {
Runnable接口只给我们提供了一个编写多线程执行任务的run()方法,要想启动多线程,还需要通过Thread的start()方法类启动。即Runnable侧中的是业务逻辑。
通过Runnable接口实现多线程的方式,使用了代理设计模式。
Thread与Runnable的关系
通过查看Thread类,可以看到Thread类是Runnable接口的实现类,实现了run()方法。多线程开发的本质实质上是在于多个线程可以进行同一资源的抢占,其中Thread类主要描述的是线程,Runabble主要描述的是资源。
Callable接口实现多线程
传统开发来讲如果要实现多线程肯定是要依靠Runnable,但是Runnable接口有一个缺点:当线程执行完毕后无法获取到一个返回值。因此自JDK1.5之后就提出了一个新的线程实现接口:java.util.concurrent.Callable接口。
Callable接口定义的时候可以设置一个泛型,此泛型的类型就是返回的数据类型。这样的好处是可以避免向下转型带来的安全隐患。
主要的资源接口:
@FunctionalInterface
public interface Callable<V> {
有了资源类,怎么让它和Thread类关联上呢?通过FutureTask类!通过下面可以看到FutureTask实现了Runnable接口,这样就可以通过使用Runnable方式使用此类了。在FutureTask类中提供了一个get()的方法,这个方法就是获取返回值的。
public class FutureTask implements RunnableFuture {
public interface RunnableFuture extends Runnable, Future {
Callable案例
package chenfu.thread;
import java.util.concurrent.Callable;
/**
* @Author: romantic_ke@163.com
* @Description:
* @Date: 2019/6/9 15:20
*/
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "..." + i);
}
return "function over!";
}
}
package chenfu.thread;
import org.junit.Test;
import java.util.concurrent.FutureTask;
/**
* @Author: romantic_ke@163.com
* @Description:
* @Date: 2019/6/9 15:27
*/
public class ThreadTest {
/**
* 测试Callable接口
*/
@Test
public void run1() throws Exception {
MyCallable callable = new MyCallable();
FutureTask<String> task = new FutureTask<String>(callable);
new Thread(task).start();
System.out.println(task.get());
}
/**
* 函数式编程
*/
@Test
public void run2() throws Exception {
FutureTask<String> task = new FutureTask<>(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "..." + i);
}
return "程序结束";
});
new Thread(task).start();
System.out.println(task.get());
}
}
Callable和Runnable的区别
多线程运行状态
对于多线程的开发而言,编写程序的过程中总是按照:定义线程的主体类,而后通过Thread类进行线程的启动,但是并不意味着你调用了start()方法,线程就已经开始运行了,因为整体的线程处理有自己的一套运行状态。
- 任何一个线程的对象都应该使用Thread类来进行封装,所以线程的启动使用的是start(),但是线程启动的时候实际上若干个线程都将进入到一终就绪状态,此时并没有执行。
- 进入到就绪状态之后就需要等待进行资源调度,当某一个线程调度成功之后则进入到运行状态(run()方法)。但是所有线程不可能一直持续运行下去,中间需要产生一些暂停的状态,例如:某个线程执行一段时间,就需要让出资源。而后这个线程就将进入到阻塞状态。随后重新回归到就绪状态。
- 当run()方法执行完毕之后,实际上线程的主要任务也就结束了,那么此时就可以直接进入到停止状态。