何为进程:进程是一个应用程序(软件)
何为线程:线程是进程中的一个执行场景/执行单元。所以一个进程可以启动多个线程。两个进程的内存是不共享的,而两个或多个线程之间的堆区和方法区是共享的只有栈区是相互独立的。多线程之间,栈和栈之间各自运行,互不干扰,这就叫多线程并发。所以多线程并发可以提高程序的处理效率。Main也是jvm中的一个线程,所以main结束了改程序也不一定已经结束了。其中线程的开辟方式一共有三种,分别是:
第一种:自定义类去继承Thread
参考代码如下:
/**
* 测试创建线程的第一种方式
*/
public class test3 {
public static void main(String[] args) {
mytestThread myt=new mytestThread();//创建自定义类
Thread t=new Thread(my);//将自定义类封装成Thread类
t.start();//创建线程
}
}
//自定义一个可运行类去继承Thread
class mytestThread extends Thread{
public void run(){
System.out.println("分栈运算开始");
int a=10;
int b=20;
int c=a+b;
System.out.println("分栈运算完成其结果为---》》》"+c);
}
}
由图可看出,其大致思路为:自定义一个类去继承Thread类,因为Thread中含义run方法,所以必须对其run方法进行重写。后将这个自定义类给创建出来且把其封装成一个Thread类,后通过调用Thread类中的start()方法即可,该start方法是用来启动线程并在JVM中开辟新栈用的。在执行这方法时,JVM中会自动调用新栈中的run方法,该方法会和main方法同时运行,但输出的结果不会规律性,因为main也属于一个线程,线程之间要进行CPU时间片的抢夺,所以若main方法中也有什么内容需要进行输出的话,其控制台上的输出结果不会呈现规律性输出。其中这种方式也可以利用多态的方式直接将可运行类封装成Thread类即可,代码如下:
Thread t=new mytestThread();//利用多态机制
t.start();//开启线程
第二种:自定义类去实现Runnable接口
参考代码如下:
/**
* 测试开启线程的第二种方式
*/
public class test2 {
public static void main(String[] args) {
mytestRunnable myt=new mytestRunnable();//创建自定义类
Thread t=new Thread(myt);//将自定义类封装成Thread类
t.start();//创建线程
}
}
//自定义类去实现Runnable接口
class mytestRunnable implements Runnable{
@Override
public void run() {
System.out.println("分栈运算开始");
int a=10;
int b=20;
int c=a+b;
System.out.println("分栈运算完成其结果为---》》》"+c);
}
}
由图可看出,其大致思路为:自定义一个类去实现Runnable接口,且对其接口下的run方法进行重写。后将自定义类创建出来且后续成一个线程对象,而后调用其start方法即可创建线程。值得注意的是自定义类去实现Runnable接口的这种方式还可通过匿名内部类的方式去创建线程:
//利用匿名内部类方式去创建线程
Thread t=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("分栈运算开始");
int a=10;
int b=20;
int c=a+b;
System.out.println("分栈运算完成其结果为---》》》"+c);
}
});
t.start();
从上述两种线程的创建方式可得出:线程的创建都需要去调用Thread类中的start方法才可完成线程的创建,且在此之前必须去重写run方法。此时,若不调用start方法而直接去调用run方法,那么就相当于去实现了一个类中的名为run的普通方法而已。
比较以上两种方式:自定义类去实现Runnable方法会更加灵活,因为JAVA中不支持多继承,若采用了继承Thread类的方式就不可继承其他类,所以才用实现接口的方式还可去继承其他类。
第三种:自定义类去实现Callable接口
参考代码如下:
public class test4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
mytestCallable my=new mytestCallable();//先声明一个可运行类
FutureTask t=new FutureTask(my);//将这个可运行类包装成一个FutureTask类
Thread T=new Thread(t);//将FutureTask类最后包装成一个Thread类
T.start();//创建线程
Object o=t.get();//获取返回值
System.out.println("已运算成功返回值="+o);
}
}
//自定义类去实现Callable接口
class mytestCallable implements Callable {
public Object call() throws Exception{
System.out.println("分栈运算开始");
int a=10;
int b=20;
System.out.println("分栈运算完成");
return a+b;
}
}
从图上可看出,该方式的思路为:声明一个自定义类去实现Callable接口且去重写该接口下的call方法,值得注意的是call方法是带有返回值的,因为不确定数据的返回类型所以该方法返回类型为Object。后将该自定义类给创建出来且封装到FutureTask类中,在将封装完后的类封装到Thread类中,即后调用Thread下的start方法去进行线程的开辟,已经调用FutureTask下的get方法去获取返回值即可。在此过程中将自定义类去封装成一个FutureTask类的过程是必须的,因为get方法是FutureTask对象下的方法,而不是自定义类下的方法,且如若你即使不去获取该自定义类的返回值,所以不去将自定义类封装成为FutureTask,而将这自定义类直接去封装成一个线程对象,该过程会虽然在编译时不会报异常,但在运行时却是会报异常的,也就是不存在这样的方式。
该方式与前两种方式不同点在与:前两种方式均是重写run方法,而该方式去重写带有返回值的call方法,也就是说实现Callable这样创建线程的方式是带有返回值的。
总结:
1,如若创建的线程需要一个返回结果则可选择第三中实现Callable接口的方式。
2,去实现Runnable接口的方式比继承Thread类的方式更加灵活。
3,方式一,二都得必须去重写run方法,方式三得必须去去重写call方法
4,即使创建了自定义类最后封装为线程对象,而没有去调用start方法,该过程并不会创建出线程
5,采用方式三的方式去创建线程,即使不去获取该线程的返回值也得将自定义类封装成FutureTask类。
以上均由个人小白的理解,如有错误点,望各位大侠可指出
感谢