线程概念和两种实现方法
一、线程的概念
现代的操作系统都是多用户多进程分时操作系统,所以我们在使用操作系统时,可以一边听歌,一边下载,还可以聊天等等,事实上我们的操作系统同时还运行着很多后台进程,你可以打开window系统的任务管理器就可以看到很多进程在运行。
在往下学习之前,让我们先弄清楚什么是程序,进程和线程这几个概念。
1、程序:
利用编程语言开发的一个工具软件, 静态的,在没有启动运行之前只是磁盘中的一个普通文件。
2、进程:
程序启动之后就变成了进程,进程是在内存中运行的,具有一定的生命周期,如果程序运行解析,进程在内存中就会回收,生命周期也就结束了,当然程序是可以再次运行,重写变成进程。
3、线程:
进程在运行过程中的执行走向,线索。线程是比进程更小的一个单位,一个进程可以有一个或多个线程的。
java程序启动运行时,就自动产生了一个线程,主函数main就是在这个线程上运行的,当不再产生新的线程时,程序就是单线程。到目前为止我们编写的所有的java程序都是单线程的,接下来我们要学习如何实现多线程的java程序。
二、线程的实现方法
1、单线程。
我们新建一个类Stu1实现一个简单的循环。
public class Stu1 {
public static void main(String[] args) {
//主线程
for(int i=0;i<10;i++){
System.out.println("主线程:"+i);
}
System.out.println("主线程:死亡");
}
}
运行上面代码,程序只有一个线程,运行完毕程序也就结束了。
2、通过继承Thread类实现第一个线程。
新建一个MyThread类继承Thread类,并重写run方法,run方法就是线程的执行内容。实现代码如下:
package lisy;
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i <10;i++) {
System.out.println(this.name +", i = "+i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("thread1");
MyThread mt2 = new MyThread("thread2");
MyThread mt3 = new MyThread("thread3");
mt1.run();
mt2.run();
mt3.run();
}
}
以上代码运行结果如图
以上的代码只是进行了一个顺序打印和多线没有关系。
正确启动多线程的方式不是调用run()方法,而是使用start()方法启动线程,线程启动后会自动执行线程的run()方法。请看一下代码
package lisy;
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i <10;i++) {
System.out.println(this.name +", i = "+i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("thread1");
MyThread mt2 = new MyThread("thread2");
MyThread mt3 = new MyThread("thread3");
mt1.start();
mt2.start();
mt3.start();
}
}
现在你可以再次运行这个段代码,查看后台的输出。你可以多运行几次可以看出来都是交替执行的。
由输出可见,主线程和子线程之间是独立运行的,它们将会轮流的占用CPU,而那个线程会占有CPU是由操作系统决定的。所以我们看到多次运行这个程序时,每一次的输出可能都不一样。
每一个线程的对象只能够启动一次
3、通过实现Runnable接口实现线程。
(1)观察Runnable接口
@FunctionalInterface
public interface Runnable{
public abstract void run();
}
注意:接口中定义的抽象方法是没有default权限的。
如果要想启动多线程依靠的只能够是Thread类中的start()方法,在之前继承Thread类的时候可以直接将start()方法继承下来继续使用,但是现在实现的是Runnable接口,没有此方法。
那么此时就需要关注Thread类提供的构造方法。
public Thread (Runnable target)
使用Runnable实现多线程代码如下:
package lisy;
class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i <10;i++) {
System.out.println(this.name +", i = "+i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("thread1");
MyThread mt2 = new MyThread("thread2");
MyThread mt3 = new MyThread("thread3");
new Thread(mt1).start();
new Thread(mt2).start();
new Thread(mt3).start();
}
}
打印结果如图:
Runnable接口对象还可以使用匿名内部类或者Lambda表达式来实现。
采用匿名内部类实现代码如下:
package lisy;
class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i <10;i++) {
System.out.println(this.name +", i = "+i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
String name = "线程对象";
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i <10;i++) {
System.out.println(name +", i = "+i);
}
}
}).start();
}
}
打印结果如图:
采用Lambda表达式实现代码如下:
package lisy;
class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i <10;i++) {
System.out.println(this.name +", i = "+i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
String name = "线程对象";
new Thread(()->{
for(int i = 0;i <10;i++) {
System.out.println(name +", i = "+i);
}
}
).start();
}
}
打印结果如图:
4、通过实现Callable接口实现线程。
Runnable中的run()方法没有返回值,如果某些线程执行完成后可能带来一些返回结果,这种情况下我们就只能利用Callable接口来实现多线程。此接口定义在java.uti.concurrent这个包中。jdk1.5之后有的。
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
这个泛型表示的是返回值的类型。call()方法就相当于run()方法。但是Thread类中没有提供接受Callable接口的对象。
类图:实现为实线,虚线为使用
实现代码:
package lisy;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String>{
private int ticket = 5;
@Override
public String call() {
while(this.ticket > 0 ) {
System.out.println("剩余票数:"+this.ticket--);
}
return "飘卖完了!";
}
}
public class TestDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<String> task = new FutureTask<>(new MyThread());//取得实现结果
new Thread(task).start();
new Thread(task).start();
System.out.println(task.get());
}
}
代码实现结果图:
三、继承Thread类、实现Runnable接口这两种实现方式的区别(面试题)
对于多线程的两种实现模式:继承Thread类、实现Runnable接口,这两种模式本质上来讲,一定使用Runnable接口实现,这样可以避免单继承局限。这两种实现方式之间的关系。
- Thread类的定义结构:
Thread类实现了Runnable接口。
- Runnable接口实现的多线程要比Thread类实现的多线程更方便的表示出数据共享的概念。
(1)请看继承Thread类实现共享数据的代码:
package lisy;
class MyThread extends Thread{
private int ticket = 5;
@Override
public void run() {
while(this.ticket > 0 ) {
System.out.println("剩余票数:"+this.ticket--);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
mt1.start();
mt2.start();
mt3.start();
}
}
打印结果如图:
分析打印结果的图:
(2)使用Runnable接口来实现数据共享代码:
package lisy;
class MyThread implements Runnable{
private int ticket = 5;
@Override
public void run() {
while(this.ticket > 0 ) {
System.out.println("剩余票数:"+this.ticket--);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
打印结果如图:
分析打印结果的图: