Java是一门为数不多的多线程支持的编程语言。
如果要想解释多线程之前,首先需要知道什么是进程?在操作系统中,进程指的是一次程序的完整执行,在这个运行的过程之中内存、处理器、IO等资源操作都要为这个进程进行服务。线程是在进程上进一步划分的结果,即:一个进程可以同时创建多个线程。线程是比进程更快的处理单元,而且所占的资源也小。那么多线程的应用也就是性能最高的应用。
· 继承Thread 类
· 实现Runnable接口(Callable接口)
class MyThread extends Thread{ //这就是一个多线程的操作类
}
所有程序的起点是main()方法,但是所有的线程也一定要有自己的一个起点,那么这个起点就是run()方法,在多线程的每个主体类之中都必须覆写Thread类中所提供的run()方法 public void run() {}
这个方法上没有返回值,那么也就表示了线程一旦开始就要一直执行,不能够返回内容。
范例:多线程启动
package cn.mldn.demo;
class MyThread extends Thread { //这就是一个多线程的操作类
private String name;
public MyThread(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public void run() { //覆写run方法,作为线程的主体操作方法
for (int x = 0; x < 200; x ++) {
System.out.println(this.name+"--->"+ x);
}
}
}
public class TestDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
MyThread mt3 = new MyThread("线程3");
mt1.start();
mt2.start();
mt3.start();
}
}
此时每一个线程对象交替执行。
疑问?为什么多线程启动不是调用run()而必须调用start().
start()方法里面要调用一个start0()方法,而且此方法的结构与抽象方法类似,使用了native声明,在Java开发里面有一门技术JNI(Java Native Interface),这门技术的特点,是使用Java调用本机操作系统提供的函数。但是这样的技术有一个缺点,不能够离开特定的操作系统。 如果要想线程能够执行,需要操作系统来进行资源分配,所以此操作严格来讲主要是由JVM负责根据不同的操作系统而实现的。
即:使用Thread类的start()方法,不仅仅是要启动多线程的执行代码,还要去根据不同的操作系统进行资源的分配。
@FunctionalInterface
public interface Runnable{
public void run();
}
在接口里面任何方法都是public定义的权限,不存在默认的权限。
那么只需要让一个类实现Runnable接口即可,并且也需要覆写run()方法。
与继承Thread类相比,此时的MyThread类在结构上与之前是没有区别的,但是继承了Thread类
,那么可以直接继承start()方法,但是如果实现Runnable接口,并没有start()方法可以继承。 不管何种情况下,如果想要启动多线程一定要考Thread类完成,在Thread类里面定义有以下的定义方法。
·构造方法:public Thread(Runnable target),接收的是Runnable接口的子类
范例:启动多线程
package cn.mldn.demo;
class MyThread implements Runnable { //这就是一个多线程的操作类
private String name;
public MyThread(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public void run() { //覆写run方法,作为线程的主体操作方法
for (int x = 0; x < 200; x ++) {
System.out.println(this.name+"--->"+ x);
}
}
}
public class TestDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
new Thread(mt1).start();
new Thread(mt2).start();
new Thread(mt3).start();
}
}
此时就避免了单继承局限,那么也就是说在实际工作中继承Runnable接口是最合适的。
首先观察一下Thread类的定义,
public class Thread extends Object implements Runnable
发现Thread类实现了Runnable接口。此时整个的定义结构看起来非常像代理设计模式,如果是代理设计模式,客户端调用的代理类的方法也应该是接口里的run()才对。
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> { //这就是一个多线程的操作类
private int ticket = 10;
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
for(int x= 0; x< 100; x++) {
if(this.ticket > 0) {
System.out.println("卖票,ticket = "+ this.ticket --);
}
}
return "票已卖光!";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
FutureTask<String> t1 = new FutureTask<String>(mt1);//目的是为了取得call()方法的返回结果
FutureTask<String> t2 = new FutureTask<String>(mt2);
//FutureTask是Runnable接口的子类,所以能够使用Thread类的构造来接收task对象
new Thread(t1).start();//启动多线程
System.out.println("task1的返回结果:"+t1.get());
System.out.println("task2的返回结果:"+t2.get());
}
}
第三种方式,最麻烦的问题在于需要接收返回值信息,又要与原始的多线程的实现靠拢。
如果要想解释多线程之前,首先需要知道什么是进程?在操作系统中,进程指的是一次程序的完整执行,在这个运行的过程之中内存、处理器、IO等资源操作都要为这个进程进行服务。线程是在进程上进一步划分的结果,即:一个进程可以同时创建多个线程。线程是比进程更快的处理单元,而且所占的资源也小。那么多线程的应用也就是性能最高的应用。
1. 多线程的实现
如果想在Java之中实现多线程有两种途径:· 继承Thread 类
· 实现Runnable接口(Callable接口)
1.1 继承Thread类
Thread类是一个支持多线程的功能类,只要有一个子类它就可以实现多线程的支持。class MyThread extends Thread{ //这就是一个多线程的操作类
}
所有程序的起点是main()方法,但是所有的线程也一定要有自己的一个起点,那么这个起点就是run()方法,在多线程的每个主体类之中都必须覆写Thread类中所提供的run()方法 public void run() {}
这个方法上没有返回值,那么也就表示了线程一旦开始就要一直执行,不能够返回内容。
范例:多线程启动
package cn.mldn.demo;
class MyThread extends Thread { //这就是一个多线程的操作类
private String name;
public MyThread(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public void run() { //覆写run方法,作为线程的主体操作方法
for (int x = 0; x < 200; x ++) {
System.out.println(this.name+"--->"+ x);
}
}
}
public class TestDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
MyThread mt3 = new MyThread("线程3");
mt1.start();
mt2.start();
mt3.start();
}
}
此时每一个线程对象交替执行。
疑问?为什么多线程启动不是调用run()而必须调用start().
start()方法里面要调用一个start0()方法,而且此方法的结构与抽象方法类似,使用了native声明,在Java开发里面有一门技术JNI(Java Native Interface),这门技术的特点,是使用Java调用本机操作系统提供的函数。但是这样的技术有一个缺点,不能够离开特定的操作系统。 如果要想线程能够执行,需要操作系统来进行资源分配,所以此操作严格来讲主要是由JVM负责根据不同的操作系统而实现的。
即:使用Thread类的start()方法,不仅仅是要启动多线程的执行代码,还要去根据不同的操作系统进行资源的分配。
1.2 实现Runnable接口
虽然Thread类可以实现多线程的主体类定义,但是它有一个问题,Java具有单继承局限,正因为如此,在任何情况下,针对于类的继承都应该是回避的问题,那么多线程也一样,为了解决单继承的限制,在Java里面专门提供了Runnable接口。此接口定义如下:@FunctionalInterface
public interface Runnable{
public void run();
}
在接口里面任何方法都是public定义的权限,不存在默认的权限。
那么只需要让一个类实现Runnable接口即可,并且也需要覆写run()方法。
与继承Thread类相比,此时的MyThread类在结构上与之前是没有区别的,但是继承了Thread类
,那么可以直接继承start()方法,但是如果实现Runnable接口,并没有start()方法可以继承。 不管何种情况下,如果想要启动多线程一定要考Thread类完成,在Thread类里面定义有以下的定义方法。
·构造方法:public Thread(Runnable target),接收的是Runnable接口的子类
范例:启动多线程
package cn.mldn.demo;
class MyThread implements Runnable { //这就是一个多线程的操作类
private String name;
public MyThread(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public void run() { //覆写run方法,作为线程的主体操作方法
for (int x = 0; x < 200; x ++) {
System.out.println(this.name+"--->"+ x);
}
}
}
public class TestDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
new Thread(mt1).start();
new Thread(mt2).start();
new Thread(mt3).start();
}
}
此时就避免了单继承局限,那么也就是说在实际工作中继承Runnable接口是最合适的。
2. 多线程两种方式的区别
首先一定要明确的是,使用Runnable接口与Thread类相比,解决了单继承的定义局限,所以不管后面的区别与联系是什么,至少这一点上就已经下了死定义--如果要使用多线程一定要继承Runnable 接口。首先观察一下Thread类的定义,
public class Thread extends Object implements Runnable
发现Thread类实现了Runnable接口。此时整个的定义结构看起来非常像代理设计模式,如果是代理设计模式,客户端调用的代理类的方法也应该是接口里的run()才对。
除了以上的联系之外,还有一点:使用Runnable接口可以比Thread类能够更好的描述出数据共享的概念。此时的数据共享指的是多个线程访问同一资源的操作。
3. 第三种实现方式(理解)
使用Runnable接口实现的多线程可以避免单继承局限,的确很好,但是有一个问题,Runnable接口里面的run()方法,并不能返回操作结果。为了解决这样的矛盾,提供了一个新的接口 java.util.concurrent.Callable接口。@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
call()方法执行完线程的主题功能之后可以返回一个结果,而返回结果的类型由Callable接口的泛型决定。
范例:多线程实现
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> { //这就是一个多线程的操作类
private int ticket = 10;
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
for(int x= 0; x< 100; x++) {
if(this.ticket > 0) {
System.out.println("卖票,ticket = "+ this.ticket --);
}
}
return "票已卖光!";
}
}
public class TestDemo {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
FutureTask<String> t1 = new FutureTask<String>(mt1);//目的是为了取得call()方法的返回结果
FutureTask<String> t2 = new FutureTask<String>(mt2);
//FutureTask是Runnable接口的子类,所以能够使用Thread类的构造来接收task对象
new Thread(t1).start();//启动多线程
new Thread(t2).start();
//多线程执行完毕后可以取得内容,依靠FutureTask的父接口Future中的get()方法完成System.out.println("task1的返回结果:"+t1.get());
System.out.println("task2的返回结果:"+t2.get());
}
}
第三种方式,最麻烦的问题在于需要接收返回值信息,又要与原始的多线程的实现靠拢。