欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java并发编程(二)-Thread类、Runnable接口与多线程原理

程序员文章站 2022-05-05 22:48:14
...

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); 
		}
	} 
}

Java并发编程(二)-Thread类、Runnable接口与多线程原理

2、多线程实现与原理分析

Java中通过继承Thread类来创建并启动多线程的步骤如下

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的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

流程图:
Java并发编程(二)-Thread类、Runnable接口与多线程原理
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。 通过这张图我们可以很清晰的看到多线程的执行流程,
那么为什么可以完成并发执行呢?
我们再来讲一讲原理。 多线程执行时,到底在内存中是如何运行的呢?以上个程序为例,进行图解说明:
多线程执行时,在栈内存中,其实每一个执行线程都会开辟一片自己所属的栈内存空间。进行方法的压栈和弹栈。
Java并发编程(二)-Thread类、Runnable接口与多线程原理
分析:先执行main()方法,将main()方法压栈执行,在main()里创建线程对象在堆中开辟对象地址,当线程对象执行start()方法时将start()方法压栈,执行时start()方法时弹栈并开辟新的栈空间,在新的栈空间中将线程的run()方法进行压栈执行。
此时CPU就有了选择的权利可以继续执行main()方法中的for循环,也可以执行新线程中的run()方法。也就是说:此时的两个线程:一个main()线程,一个新线程一起抢夺CPU的执行权(执行时间),谁抢到了谁就执行对应的代码。
因为多个线程是在不同的栈内空间,所以彼多个线程之间并不产生影响,也就是说如果一个线程出现异常挂了,其他线程仍可继续执行

3、实现Runnable接口创建线程

实现java.lang.Runnable 接口创建线程也是非常常见的一种方式,我们只需要重写run方法即可。
步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
  3. 调用线程对象的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类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现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();	
	}
}