概述
------Java培训、Android培训、iOS培训、Net培训、期待与您交流! -------
一、线程的基本概念
一般来说,进程(Process)就是正在计算机中执行的程序。而线程(Thread)是进程中的某个单一顺序的控制流。线程是进程的细化,它是进程中的实体。进程是应用程序的运行实例,自己享有独立的地址空间。对于多任务操作系统,能够“同时”运行多个进程。但这些是CPU的分时机制在起作用,能够使每个进程都能循环获得自己的CPU时间片。进程(process)本质上是操作系统当前运行的执行程序。
简单地说,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而对于线程来说,多个线程共享内存,因此极大地提高了程序的运行效率。
使用多线程编程具有以下几点优点:
(1)进程之间不能共享内存,但线程之间共享内存非常容易;
(2)使用多线程来实现多任务并发比多进程的效率高;
(3)Java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程。
二、线程的创建和启动
在Jvav中主要提供两种方式实现线程,分别为继承Thread类与实现Runnable接口。
(1)通过继承Thread类创建和启动多线程
Thread类存放在java.lang类库里,但并不需要加载java.lang类库,因为它会自动加载。run()方法是定义在Thread类里的一个方法,因此把线程的程序代码编写在run()方法内,事实上就是覆盖的操作。
通过继承Thread类创建多线程的语法格式如下:
继承Thread类方式的线程启动非常简单,只要创建线程类示例后调用其start()方法即可。
启动线程的语法格式如下图:
示例:定义一个简单的线程类TextThread,在run()方法中利用for循环打印“@”。
//通过继承Thread类创建线程类ThreadDome
class ThreadDome extends Thread {
//覆写run()方法
public void run() {
for(int i=0;i< 10;i++)
{
System.out.print("@");
}
}
}
public class TextThread{
public static void main(String[] args) {
//创建线程类ThreadDome的示例m,并以此调用start方法
ThreadDome m = new ThreadDome();
m.start();
}
}
控制台输出的内容:
@@@@@@@@@@
(2)通过实现Runnable接口创建和启动多线程
如果自定义的线程类还要继承其他类,这时就不能采用第一种方式来创建。由于Java语言不支持类的多继承,却可以实现多个接口,所以这种情况可以采用实现Runnable接口的方式创建。
通过实现Runnable接口创建多线程的语法格式:
实现Runnable接口创建的线程首先转换为Thread类,然后调用Thread类的start()方法启动线程。
启动线程的语法格式如下图:
示例:运用Runnable接口创建线程。
//通过实现Runnable接口创建线程类ThreadDome
class ThreadDome implements Runnable {
//覆写run()方法
public void run() {
for(int i=0;i< 10;i++)
{
System.out.print("@");
}
}
}
public class TextThread{
public static void main(String[] args) {
//创建线程类ThreadDome的示例mt和线程类Thread的实例m,调用start()方法启动线程
ThreadDome mt = new ThreadDome();
Thread m = new Thread(mt);
m.start();
}
}
控制台输出的内容:
@@@@@@@@@@
(3)继承Thread类与实现Runnable接口的区别
继承Thread类:线程代码存放在Thread子类run()方法中;
实现Runnable接口:线程代码存放在接口的子类的run()方法中。
三、线程的生命周期
线程从其创建到死亡具有一个完整的生命周期,在整个生命周期中处于各种状态。线程的状态表明线程当前可以进行的活动。一个生命周期内的线程主要包括创建、就绪、运行、阻塞、死亡状态,如图所示。
(1)创建状态
在线程类使用new关键字实例化之后且在调用start方法之前,线程处于创建状态。处于创建状态的线程仅仅分配了内存空间,属于生命周期的初始状态。
(2)就绪状态
在线程调用了start方法后即处于就绪状态。处于就绪状态的线程具备了除CPU之外所有运行所需资源。就绪状态线程排队等待CPU,由系统调度为其分配。
使线程处于就绪状态有以下几种可能:
①调用sleep()方法;
②调用wait()方法;
③等待输入/输出完成。
(3)运行状态
处于就绪状态的线程获得CPU之后即处于运行状态。处于运行状态的线程才开始真正执行线程run方法的内容。
使线程再次进入运行状态:
①线程调用notify()方法;
②线程调用notifyAll()方法;
③线程调用interrupt()方法;
④线程的休眠时间结束;
⑤输入/输出结束。
(4)阻塞/等待状态
处于运行状态的线程如果因为某种原因不能继续执行,则进入阻塞状态。阻塞状态与就绪状态的不同是:就绪状态只是因为缺少CPU而不能执行,而阻塞状态是由于各种原因引起线程不能执行,不仅仅是缺少CPU。引起阻塞的原因解除以后,线程再次转为就绪状态,等待分配CPU运行。
(5)死亡状态
线程会以以下3种方式结束,结束后就处于死亡状态:
①run()或call()方法完成,线程正常结束;
②线程抛出一个未捕获的Exception或Error;
③直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁。
四、线程的调度
处于生命周期中的线程,可以通过调度实现各种状态间的转换。线程的调度是使用各种线程调度方法,如设置优先级方法、睡眠方法、让步方法和等待方法等,对线程进行不同的操作。对于各种线程调度方法,下面分别进行介绍。
(1)join线程
Thread提供了让一个线程等待另一个线程完成的方法——join()方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。
示例:
public class JoinThread extends Thread{
//提供了一个有参数的构造器,用于设置改线程的名字
public JoinThread (String name) {
super(name);
}
//重写run()方法,定义了线程的执行体
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+""+i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动子线程
new JoinThread("新线程").start();
for (int i = 0; i < 100; i++) {
if (i==20) {
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
//main线程调用了jt线程的join()方法,main线程
//需要等jt执行结束才会向下执行
jt.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
运行结果:
上面程序一共有3个线程,主方法开始时就启动了名为“新线程”的子线程,该子线程将会和main线程并发执行。当主线程的循环变量i=20时,启动了名为“被join的线程”的线程,该线程不会和mian线程并发执行,main线程必须等该线程执行结束后才可以向下执行。
(2)线程的睡眠:sleep
Thread类中提供了sleep()方法,可以让当前线程休眠(停止执行)一定的时间,线程由运行中状态进入阻塞状态 ,等待停止执行时间到后,线程再进入可运行状态。
示例:以下程序的实际效果是打印0~9的数字,其中,每隔1秒打印一个数字,在程序运行时可以清楚地看出sleep()方法的作用。
public class SleepText extends Thread {
//重写run()方法,定义了线程的执行体
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print(i);
try {
//设定线程睡眠的时间
//由于线程休眠时可能被中断,所以需要捕获异常
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SleepText st = new SleepText();
//启动线程
st.start();
}
}
运行结果:
0 1 2 3 4 5 6 7 8 9
(3)线程的让步:yield
yield()方法是一个和sleep()方法有点相似的方法,它是Thread类提供的一个静态方法。对于正在执行的线程,可以调用yield()方法使其重新排队,将CPU让给排在后面的线程,此线程转为就绪态。另外,yield()方法只让步给高优先级或同等优先级的线程,如果后面是低优先级线程,则继续执行此线程。yield没有声明抛出任何异常。
示例:两个线程交替打印0~9的数字
public class ThreamDome extends Thread {
//重写run()方法,定义了线程的执行体
public void run() {
for (int i = 0; i < 10; i++) {
System.out.print(i);
//调用yield()方法让当前线程让步
yield();
}
}
public static void main(String[] args) {
ThreamDome m1 = new ThreamDome();
ThreamDome m2 = new ThreamDome();
//启动线程
m1.start();
m2.start();
}
}
运行结果:
01023456789123456789
(4)线程的优先级
线程优先级是指线程在被系统调度执行时的优先执行级别。在多线程程序中,往往是多个线程同时等待被调度执行。然而,每个线程的重要程度通常是不一样的,在同等条件下,有些重要线程需要优先执行。在Java语言中,Thread类提供了setPriority(int newPriority)、其中setPriority()方法的参数可以是一个整数,范围是1~10之间,也可以使用Thread类的如下3个静态常亮。
①MAX_PRIORITY:其值是10
②MIN_PRIORITY:其值是1
③NORM_PRIORITY:其值是5
如果没有为线程设置优先级,则线程的优先级默认为5。
示例:
public class PriorityTest extends Thread{
//定义一个有参数的构造器,用于创建线程时指定name
public PriorityTest(String name) {
super(name);
}
//重写run()方法,定义了线程的执行体
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(getName() + ",其优先级是:"
+ getPriority() + ",循环变量的值为:" + i);
}
}
public static void main(String[] args) {
//改变主线程的优先级
Thread.currentThread().setPriority(6);
for (int i = 0; i < 30; i++) {
if (i == 10) {
PriorityTest low = new PriorityTest("低级");
low.start();
System.out.println("创建之初的优先级:" + low.getPriority());
//设置了该线程为最低优先级
low.setPriority(Thread.MIN_PRIORITY);
}
if (i == 20) {
PriorityTest high = new PriorityTest("高级");
high.start();
System.out.println("创建之初的优先级:" + high.getPriority());
//设置了该线程为最高优先级
high.setPriority(Thread.MAX_PRIORITY);
}
}
}
}
运行结果 :
创建之初的优先级:6
创建之初的优先级:6
高级,其优先级是:10,循环变量的值为:0
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:2
高级,其优先级是:10,循环变量的值为:3
高级,其优先级是:10,循环变量的值为:4
高级,其优先级是:10,循环变量的值为:5
高级,其优先级是:10,循环变量的值为:6
高级,其优先级是:10,循环变量的值为:7
高级,其优先级是:10,循环变量的值为:8
高级,其优先级是:10,循环变量的值为:9
高级,其优先级是:10,循环变量的值为:10
高级,其优先级是:10,循环变量的值为:11
高级,其优先级是:10,循环变量的值为:12
高级,其优先级是:10,循环变量的值为:13
高级,其优先级是:10,循环变量的值为:14
高级,其优先级是:10,循环变量的值为:15
高级,其优先级是:10,循环变量的值为:16
高级,其优先级是:10,循环变量的值为:17
高级,其优先级是:10,循环变量的值为:18
高级,其优先级是:10,循环变量的值为:19
高级,其优先级是:10,循环变量的值为:20
高级,其优先级是:10,循环变量的值为:21
高级,其优先级是:10,循环变量的值为:22
高级,其优先级是:10,循环变量的值为:23
高级,其优先级是:10,循环变量的值为:24
高级,其优先级是:10,循环变量的值为:25
高级,其优先级是:10,循环变量的值为:26
高级,其优先级是:10,循环变量的值为:27
高级,其优先级是:10,循环变量的值为:28
高级,其优先级是:10,循环变量的值为:29
高级,其优先级是:10,循环变量的值为:30
高级,其优先级是:10,循环变量的值为:31
高级,其优先级是:10,循环变量的值为:32
高级,其优先级是:10,循环变量的值为:33
高级,其优先级是:10,循环变量的值为:34
高级,其优先级是:10,循环变量的值为:35
高级,其优先级是:10,循环变量的值为:36
高级,其优先级是:10,循环变量的值为:37
高级,其优先级是:10,循环变量的值为:38
高级,其优先级是:10,循环变量的值为:39
高级,其优先级是:10,循环变量的值为:40
高级,其优先级是:10,循环变量的值为:41
高级,其优先级是:10,循环变量的值为:42
高级,其优先级是:10,循环变量的值为:43
高级,其优先级是:10,循环变量的值为:44
高级,其优先级是:10,循环变量的值为:45
高级,其优先级是:10,循环变量的值为:46
高级,其优先级是:10,循环变量的值为:47
高级,其优先级是:10,循环变量的值为:48
高级,其优先级是:10,循环变量的值为:49
低级,其优先级是:1,循环变量的值为:0
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:2
低级,其优先级是:1,循环变量的值为:3
低级,其优先级是:1,循环变量的值为:4
低级,其优先级是:1,循环变量的值为:5
低级,其优先级是:1,循环变量的值为:6
低级,其优先级是:1,循环变量的值为:7
低级,其优先级是:1,循环变量的值为:8
低级,其优先级是:1,循环变量的值为:9
低级,其优先级是:1,循环变量的值为:10
低级,其优先级是:1,循环变量的值为:11
低级,其优先级是:1,循环变量的值为:12
低级,其优先级是:1,循环变量的值为:13
低级,其优先级是:1,循环变量的值为:14
低级,其优先级是:1,循环变量的值为:15
低级,其优先级是:1,循环变量的值为:16
低级,其优先级是:1,循环变量的值为:17
低级,其优先级是:1,循环变量的值为:18
低级,其优先级是:1,循环变量的值为:19
低级,其优先级是:1,循环变量的值为:20
低级,其优先级是:1,循环变量的值为:21
低级,其优先级是:1,循环变量的值为:22
低级,其优先级是:1,循环变量的值为:23
低级,其优先级是:1,循环变量的值为:24
低级,其优先级是:1,循环变量的值为:25
低级,其优先级是:1,循环变量的值为:26
低级,其优先级是:1,循环变量的值为:27
低级,其优先级是:1,循环变量的值为:28
低级,其优先级是:1,循环变量的值为:29
低级,其优先级是:1,循环变量的值为:30
低级,其优先级是:1,循环变量的值为:31
低级,其优先级是:1,循环变量的值为:32
低级,其优先级是:1,循环变量的值为:33
低级,其优先级是:1,循环变量的值为:34
低级,其优先级是:1,循环变量的值为:35
低级,其优先级是:1,循环变量的值为:36
低级,其优先级是:1,循环变量的值为:37
低级,其优先级是:1,循环变量的值为:38
低级,其优先级是:1,循环变量的值为:39
低级,其优先级是:1,循环变量的值为:40
低级,其优先级是:1,循环变量的值为:41
低级,其优先级是:1,循环变量的值为:42
低级,其优先级是:1,循环变量的值为:43
低级,其优先级是:1,循环变量的值为:44
低级,其优先级是:1,循环变量的值为:45
低级,其优先级是:1,循环变量的值为:46
低级,其优先级是:1,循环变量的值为:47
低级,其优先级是:1,循环变量的值为:48
低级,其优先级是:1,循环变量的值为:49
最后
以上就是感性冰棍为你收集整理的黑马程序员——Java之多线程(1)的全部内容,希望文章能够帮你解决黑马程序员——Java之多线程(1)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复