在上一篇文章中,回顾了Java的集合。而在本篇文章中主要介绍多线程的相关知识。主要介绍的知识点为线程的介绍、多线程的使用、以及在多线程中使用的一些方法。java
表示进程中负责程序执行的执行单元,依靠程序进行运行。线程是程序中的顺序控制流,只能使用分配给程序的资源和环境。编程
表示资源的分配和调度的一个独立单元,一般表示为执行中的程序。一个进程至少包含一个线程。bash
线程和进程同样分为五个阶段:建立、就绪、运行、阻塞和终止。多线程
能够用下述图来进行理解线程的生命周期: 并发
在了解了线程和进程以后,咱们再来简单的了解下单线程和多线程。 单线程 程序中只存在一个线程,实际上主方法就是一个主线程。dom
多线程 多线程是指在同一程序中有多个顺序流在执行。 简单的说就是在一个程序中有多个任务运行。ide
那么在什么状况下用多线程呢?ui
通常来讲,程序中有两个以上的子系统须要并发执行的,这时候就须要利用多线程编程。经过对多线程的使用,能够编写出高效的程序。this
那么是否是使用不少线程就能提升效率呢?spa
不必定的。由于程序中上下文的切换开销也很重要,若是建立了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!这时是会下降程序执行效率的。
因此有效利用多线程的关键是理解程序是并发执行而不是串行执行的。
通常来讲,咱们在对线程进行建立的时候,通常是继承Thread 类或实现Runnable 接口。其实还有一种方式是实现 Callable接口,而后与Future 或线程池结合使用, 相似于Runnable接口,可是就功能上来讲更为强大一些,也就是被执行以后,能够拿到返回值。
这里咱们分别一个例子使用继承Thread 类、实现Runnable 接口和实现Callable接口与Future结合来进行建立线程。 代码示例: 注:线程启动的方法是start而不是run。由于使用start方法整个线程处于就绪状态,等待虚拟机来进行调度。而使用run,也就是看成了一个普通的方法进行启动,这样虚拟机不会进行线程调度,虚拟机会执行这个方法直到结束后自动退出。
代码示例:
public class Test {
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
threadTest.start();
RunalbeTest runalbeTest=new RunalbeTest();
Thread thread=new Thread(runalbeTest);
thread.start();
CallableTest callableTest=new CallableTest();
FutureTask<Integer> ft = new FutureTask<Integer>(callableTest);
Thread thread2=new Thread(ft);
thread2.start();
try {
System.out.println("返回值:"+ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("这是一个Thread的线程!");
}
}
class RunalbeTest implements Runnable{
@Override
public void run() {
System.out.println("这是一个Runnable的线程!");
}
}
class CallableTest implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("这是一个Callable的线程!");
return 2;
}
}
复制代码
运行结果:
这是一个Thread的线程!
这是一个Runnable的线程!
这是一个Callable的线程!
返回值:2
复制代码
经过上述示例代码中,咱们发现使用继承 Thread 类的方式建立线程时,编写最为简单。而使用Runnable、Callable 接口的方式建立线程的时候,须要经过Thread类的构造方法Thread(Runnable target) 构造出对象,而后调用start方法来运行线程代码。顺便说下,其实Thread类实际上也是实现了Runnable接口的一个类。
可是在这里,我推荐你们建立单线程的时候使用继承 Thread 类方式建立,多线线程的时候使用Runnable、Callable 接口的方式来建立建立线程。 至于为何呢?在下面中的描述已给出理由。
总的来讲就是,单线程建议用继承 Thread 类建立,多线程建议- 使用Runnable、Callable 接口的方式建立。
使用yield方法表示暂停当前正在执行的线程对象,并执行其余线程。
代码示例:
public class YieldTest {
public static void main(String[] args) {
Test1 t1 = new Test1("张三");
Test1 t2 = new Test1("李四");
new Thread(t1).start();
new Thread(t2).start();
}
}
class Test1 implements Runnable {
private String name;
public Test1(String name) {
this.name=name;
}
@Override
public void run() {
System.out.println(this.name + " 线程运行开始!");
for (int i = 1; i <= 5; i++) {
System.out.println(""+this.name + "-----" + i);
// 当为3的时候,让出资源
if (i == 3) {
Thread.yield();
}
}
System.out.println(this.name + " 线程运行结束!");
}
}
复制代码
执行结果一:
张三 线程运行开始!
张三-----1
张三-----2
张三-----3
李四 线程运行开始!
李四-----1
李四-----2
李四-----3
张三-----4
张三-----5
张三 线程运行结束!
李四-----4
李四-----5
李四 线程运行结束!
复制代码
执行结果二:
张三 线程运行开始!
李四 线程运行开始!
李四-----1
李四-----2
李四-----3
张三-----1
张三-----2
张三-----3
李四-----4
李四-----5
李四 线程运行结束!
张三-----4
张三-----5
张三 线程运行结束!
复制代码
上述中的例子咱们能够看到,启动两个线程以后,哪一个线程先执行到3,就会让出资源,让另外一个线程执行。 在这里顺便说下,yield和sleep的区别。
使用join方法指等待某个线程终止。也就是说当子线程调用了join方法以后,后面的代码只有等待该线程执行完毕以后才会执行。
若是很差理解,这里依旧使用一段代码来进行说明。 这里咱们建立两个线程,并使用main方法执行。顺便提一下,其实main方法也是个线程。若是直接执行的话,可能main方法执行完毕了,子线程还没执行完毕,这里咱们就让子线程使用join方法使main方法最后执行。
代码示例:
public class JoinTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+ "主线程开始运行!");
Test2 t1=new Test2("A");
Test2 t2=new Test2("B");
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
class Test2 extends Thread{
public Test2(String name) {
super(name);
}
public void run() {
System.out.println(this.getName() + " 线程运行开始!");
for (int i = 0; i < 5; i++) {
System.out.println("子线程"+this.getName() + "运行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 线程运行结束!");
}
}
复制代码
执行结果:
main主线程开始运行!
B 线程运行开始!
子线程B运行 : 0
A 线程运行开始!
子线程A运行 : 0
子线程A运行 : 1
子线程B运行 : 1
子线程B运行 : 2
子线程B运行 : 3
子线程B运行 : 4
B 线程运行结束!
子线程A运行 : 2
子线程A运行 : 3
子线程A运行 : 4
A 线程运行结束!
main主线程运行结束!
复制代码
上述示例中的结果显然符合咱们的预期。
使用setPriority表示设置线程的优先级。 每一个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。 线程的优先级有继承关系,好比A线程中建立了B线程,那么B将和A具备相同的优先级。 JVM提供了10个线程优先级,但与常见的操做系统都不能很好的映射。若是但愿程序能移植到各个操做系统中,应该仅仅使用Thread类有如下三个静态常量做为优先级,这样能保证一样的优先级采用了一样的调度方式
可是设置优先级并不能保证线程必定先执行。咱们能够经过一下代码来验证。
代码示例:
public class PriorityTest {
public static void main(String[] args) {
Test3 t1 = new Test3("张三");
Test3 t2 = new Test3("李四");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
class Test3 extends Thread {
public Test3(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.getName() + " 线程运行开始!");
for (int i = 1; i <= 5; i++) {
System.out.println("子线程"+this.getName() + "运行 : " + i);
try {
sleep(new Random().nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + " 线程运行结束!");
}
}
复制代码
执行结果一:
李四 线程运行开始!
子线程李四运行 : 1
张三 线程运行开始!
子线程张三运行 : 1
子线程张三运行 : 2
子线程李四运行 : 2
子线程李四运行 : 3
子线程李四运行 : 4
子线程张三运行 : 3
子线程李四运行 : 5
李四 线程运行结束!
子线程张三运行 : 4
子线程张三运行 : 5
张三 线程运行结束!
复制代码
执行结果二:
张三 线程运行开始!
子线程张三运行 : 1
李四 线程运行开始!
子线程李四运行 : 1
子线程张三运行 : 2
子线程张三运行 : 3
子线程李四运行 : 2
子线程张三运行 : 4
子线程李四运行 : 3
子线程张三运行 : 5
子线程李四运行 : 4
张三 线程运行结束!
子线程李四运行 : 5
李四 线程运行结束!
复制代码
执行结果三:
李四 线程运行开始!
子线程李四运行 : 1
张三 线程运行开始!
子线程张三运行 : 1
子线程李四运行 : 2
子线程李四运行 : 3
子线程李四运行 : 4
子线程张三运行 : 2
子线程张三运行 : 3
子线程张三运行 : 4
子线程李四运行 : 5
子线程张三运行 : 5
李四 线程运行结束!
张三 线程运行结束!
复制代码
线程中还有许多方法,可是这里并不会所有细说。只简单的列举了几个方法使用。更多的方法使用能够查看相关的API文档。这里我也顺便总结了一些关于这些方法的描述。
其实这篇文章好久以前都已经打好草稿了,可是因为各类缘由,只到今天才写完。虽然也只是简单的介绍了一下多线程的相关知识,也只能算个入门级的教程吧。不过写完以后,感受本身又从新复习了一遍多线程,对多线程的理解又加深了一些。 话已尽此,不在多说。 原创不易,若是感受不错,但愿给个推荐!您的支持是我写做的最大动力!
参考:https://blog.csdn.net/evankaka/article/details/44153709#t1