多线程的原理
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着Thread的子对象的调用start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。多线程的执行流程,那么为什么可以完成并发执行呢?
多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。多个线程之间互不影响(在不同的栈空间)
主线程
执行主方法(main)的线程就是主线程。单线程程序:Java程序中只有一个线程,执行从main方法开始,从上到下依次执行。main方法通向CPU的路径,这个路径叫主线程。
创建多线程的第一种方式:继承Thread类
Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是 完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。
创建并启动多线程的步骤如下:
- 创建一个类,继承Thread类
- 重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线
注意事项:
- 线程对象调用start方法,多个线程会并发执行
- 多次启动一个线程是非法的,特别是当线程已经结束执行后,不能在重新启动
Thread类
构造方法:
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
常用方法:
- public String getName() :获取当前线程名称。
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
- public void run() :此线程要执行的任务在此处定义代码。
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
代码举例
package demo01; public class Demo01Thread extends Thread {//创建一个类,继承Thread类 @Override //重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。 public void run() { //线程执行体。线程任务 for (int i = 0; i < 5; i++) { System.out.println("run" + i); } } }
package demo01; public class Demo01ThreadTest { public static void main(String[] args) { //创建Thread子类的实例,即创建了线程对象 Demo01Thread demo01Thread = new Demo01Thread(); //调用线程对象的start()方法来启动该线 demo01Thread.start(); //设置主线程任务 for (int i = 0; i < 5; i++) { System.out.println("main"+i); } } }
执行结果
run0
main0
run1
main1
run2
main2
run3
main3
run4
main4
创建多线程的第二种方式:实现Runnable接口
采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动线程。
代码举例
package demo03; //定义Runnable接口的实现类 public class Demo01Runnable implements Runnable { @Override //重写该接口的run()方法 public void run() { //获取线程名称 System.out.println(Thread.currentThread().getName()); //该run()方法的方法体同样是该线程的线程执行体。 for (int i = 0; i < 3; i++) { System.out.println("run" + i); } } }
package demo03; public class Demo01RunnableTest { public static void main(String[] args) { //创建Runnable实现类的实例 Demo01Runnable d = new Demo01Runnable(); //并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 Thread thread = new Thread(d); //调用线程对象的start()方法来启动线程。 thread.start(); //获取线程名称 System.out.println(Thread.currentThread().getName()); for (int i = 0; i < 3; i++) { System.out.println("main" + i); } } }
执行结果
main Thread-0 main0 main1 main2 run0 run1 run2
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
- 实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
匿名内部类方式实现线程的创建
package demo03; public class Demo02RunnableTest { public static void main(String[] args) { /*使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。 使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法: * */ new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println("run" + i); } } }).start(); for (int i = 0; i < 3; i++) { System.out.println("main" + i); } } }