说起Java的多线程机制,可能很多程序猿和笔者一样,对其是又爱又恨的矛盾心理。一方面,多线程的强大功能可以简化编程模型,很方便的达到我们的目的;另一方面,也正是由于多线程的强大功能,我们很容易在编程过程中考虑不周,从而出现一些难以发现的bug。要写好Java多线程,笔者以为需要的是长期不断的练习和实践,而今天和大家分享的便是笔者在学习Java多线程中的一些感想和心得,也希望广大读者朋友批评指正。
一、初识多线程
1. Java对线程的支持
Java对线程的支持主要由java.lang包中封装的Thread类和Runnable接口来实现。
2. 线程的创建和启动
创建使用Thread()方法及其重载方法,启动使用void start()方法。
3. 线程中其他常用的方法
(1)使线程休眠:static void sleep()及其重载函数,最低可精确到纳秒。
(2)使其他线程等待当前线程终止:void join()及其重载方法,最低等待时间可精确到纳秒。
(3)释放资源:static void yield()方法用于当前运行的线程释放占用的处理器资源。
(4)获取线程引用:static Thread currentThread() 方法用于返回当前运行线程的引用。
关于Thread类和Runnable接口在使用上的区别,读者可以参考追寻—专栏的文章《 java线程系列—Runnable和Thread的区别》。另外,我们将在下面的实际例子中说明上述线程常用方法。
二、举例说明
我们考虑一个学生在教室上课的实际例子:
- 学生们是独立的个体,他们在课上的学习活动互不干扰
- 老师是推动整堂课有序进行的关键人物
- 教室是这堂课进行的必不可少的因素,它提供了场地
其实上述的三句话便代表了这个实例中的三个类,分别是:学生StudentRunnable、老师TeacherThread、教室Classroom。我们通过这样的三个类便可以模拟一个简单的上课情景(以两个学生为例)。
显然,主方法在Classroom类中定义,下面给出实现示例:
(1)StudentRunnable.java
package com.bebdong.concurrent;
//学生线程,模拟上课行为
public class StudentRunnable implements Runnable
{
//volatile可以保证正确读取其他线程写入的值
volatile boolean keepRunning=true; //回答问题直至老师叫停
@Override
public void run()
{
while(keepRunning)
{
//连续回答5个问题
for(int i=0;i<5;i++)
{
//getName()方法用于获取当前线程的名称
System.out.println(Thread.currentThread().getName()+"回答第"+i+"个问题");
Thread.yield(); //释放资源,使得其他学生也可以抢答问题
}
}
System.out.println(Thread.currentThread().getName()+"回答问题结束!");
}
}
(2)TeacherThread.java
package com.bebdong.concurrent;
//老师线程
public class TeacherThread extends Thread
{
public void run()
{
System.out.println(Thread.currentThread().getName()+"开始评判学生的回答!");
for(int i=0;i<5;i++)
System.out.println(Thread.currentThread().getName()+"正在评判学生的回答……");
System.out.println(Thread.currentThread().getName()+"评判结束!");
}
}
(3)Classroom.java
package com.bebdong.concurrent;
//教室提供上课的地点
public class Classroom extends Thread
{
public void run()
{
System.out.println("老师提问,找学生回答!");
//老师等待学生举手回答
try
{
Thread.sleep(5000);
}
catch (InterruptedException e1)
{
e1.printStackTrace();
}
System.out.println("两个学生开始回答问题!");
//创建两个学生
StudentRunnable studentATask=new StudentRunnable();
StudentRunnable studentBTask=new StudentRunnable();
//使用Runnable接口创建线程
Thread studentA=new Thread(studentATask, "学生A");
Thread studentB=new Thread(studentBTask, "学生B");
//启动线程,学生开始回答问题
studentA.start();
studentB.start();
//休眠教室线程,学生专心回答问题
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
//创建老师线程
Thread teacher=new TeacherThread();
teacher.setName("老师");
System.out.println("老师要对学生回答做出正确及时的评判");
//学生停止回答
studentATask.keepRunning=false;
studentBTask.keepRunning=false;
//教室线程休眠,把资源交给老师
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
//教师线程运行
teacher.start();
//学生线程和教室线程等待老师评判
try
{
teacher.join();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("老师评判结束,继续上课……");
}
public static void main(String[] args)
{
new Classroom().start();
}
}
运行上面的程序,可以得到如下的结果:
对结果的分析和说明:
- 由于图片是静态的,没有办法展现程序中sleep()方法的作用,读者可以试着自己运行来体会sleep()方法的作用。
- 可以发现学生A和学生B的回答顺序是毫无规律的,这是因为调用了yield()方法释放了资源,而调度程序下次选择哪一个线程运行是不确定的。
- 在老师评判学生的回答之前,调用了join()方法,目的是使其他线程等待老师的评判。
- 当然,对于以上的多线程程序,每一次运行的结果可能存在些许差异,这取决于设置keepRunning属性为false时学生线程的运行状态。
以上用多线程来模拟上课的情景或许存在不当之处,还请各位读者指正。当然读者也可以自己建立相关场景来模拟。
(未完待续……)