Java并发编程(二)-Thread类、Runnable接口与多线程原理
1、Thread线程类
Java使用 java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是 完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。
构造方法:
-
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 com.zhukun;
class MyThread extends Thread {
public void run()
{
for (int i = 0; i < 20; i++)
{
System.out.println("run:"+i);
}
}
}
class test {
public static void main(String[] args)
{
//创建自定义线程对象
MyThread mt = new MyThread();
//开启新线程
mt.start();
for (int i = 0; i < 20; i++)
{
System.out.println("main:"+i);
}
}
}
2、多线程实现与原理分析
Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
package com.zhukun;
class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/*** 重写run方法,完成该线程执行的逻辑 */
@Override
public void run()
{
for (int i = 0; i < 20; i++)
{
System.out.println(getName()+":正在执行!"+i);
}
}
}
class test {
public static void main(String[] args)
{
System.out.println("这里是main线程");
//创建自定义线程对象
MyThread mt = new MyThread("小强");
//开启新线程
mt.start();
for (int i = 0; i < 20; i++)
{
System.out.println("旺财:"+i);
}
}
}
这里是main线程
旺财:0
旺财:1
小强:正在执行!0
小强:正在执行!1
小强:正在执行!2
旺财:2
小强:正在执行!3
旺财:3
小强:正在执行!4
旺财:4
小强:正在执行!5
旺财:5
小强:正在执行!6
小强:正在执行!7
小强:正在执行!8
小强:正在执行!9
旺财:6
小强:正在执行!10
旺财:7
旺财:8
旺财:9
旺财:10
旺财:11
旺财:12
旺财:13
旺财:14
旺财:15
小强:正在执行!11
旺财:16
小强:正在执行!12
旺财:17
旺财:18
旺财:19
小强:正在执行!13
小强:正在执行!14
小强:正在执行!15
小强:正在执行!16
小强:正在执行!17
小强:正在执行!18
小强:正在执行!19
流程图:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。 通过这张图我们可以很清晰的看到多线程的执行流程,
那么为什么可以完成并发执行呢?
我们再来讲一讲原理。 多线程执行时,到底在内存中是如何运行的呢?以上个程序为例,进行图解说明:
多线程执行时,在栈内存中,其实每一个执行线程都会开辟一片自己所属的栈内存空间。进行方法的压栈和弹栈。
分析:先执行main()方法,将main()方法压栈执行,在main()里创建线程对象在堆中开辟对象地址,当线程对象执行start()方法时将start()方法压栈,执行时start()方法时弹栈并开辟新的栈空间,在新的栈空间中将线程的run()方法进行压栈执行。
此时CPU就有了选择的权利可以继续执行main()方法中的for循环,也可以执行新线程中的run()方法。也就是说:此时的两个线程:一个main()线程,一个新线程一起抢夺CPU的执行权(执行时间),谁抢到了谁就执行对应的代码。
(因为多个线程是在不同的栈内空间,所以彼多个线程之间并不产生影响,也就是说如果一个线程出现异常挂了,其他线程仍可继续执行)
3、实现Runnable接口创建线程
实现java.lang.Runnable
接口创建线程也是非常常见的一种方式,我们只需要重写run方法即可。
步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
- 调用线程对象的start()方法来启动线程
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
class test {
public static void main(String[] args)
{
//创建自定义线程对象
MyRunnable mr = new MyRunnable();
//创建线程对象,mr作为Thread的target来创建Thread对象
Thread t = new Thread(mr, "小强");
t.start(); for (int i = 0; i < 20; i++)
{
System.out.println("旺财 " + i);
}
}
}
通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程 代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。 在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread 对象的start()方法来运行多线程代码。 实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现 Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程 编程的基础。
(Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。 而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法)
4、 Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
(在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进 程。)
5、匿名内部类方式实现线程的创建
把子类继承父类,重写父类的方法,创建子类对象合一步完成;把实现类实现类接口,重写接口中的方法,创建实现类对象合一步完成
class test {
public static void main(String[] args)
{
//new MyThread().start();
new Thread(){
//重写run方法
public void run(){
for (int i = 0; i < 20; i++)
{
System.out.println(Thread.currentThread().getName()+"小强"+i);
}
}
}.start();;
//线程接口Runnable
Runnable r = new Runnable()
{
public void run()
{
for (int i = 0; i < 20; i++)
{
System.out.println(Thread.currentThread().getName()+"小张"+i);
}
}
};
new Thread(r).start();
}
}
上一篇: 1、Java面试常见
下一篇: java面试题<1>