话说,线程是为了java程序能够获得更高的执行效率,而引入的一个概念。所谓的线程,就是一个可以独立、并发执行的程序单元。而,多线程是指,程序中同时存在多个执行体,各线程按照自己的执行路线并发的工作,独立完成各自的功能,互不干扰。就好像,我们能够在计算机上,同时听音乐和上网浏览信息一样。这种执行效果是不是很好。
线程的生命周期,就像我们学习android中的activity类也有自己的生命周期,以及操作系统中的一些执行程序也有自己的生命周期类似。每个线程要经历:新生状态、就绪状态、运行状态、阻塞状态、死亡状态,这样一个从新生到死亡的过程称为一个生命周期。
下面再来说说多线程的实现方法,为什么要直接讲多线程呢?因为一个线程的话,就可以直接执行了,没有交集的作用而言,其执行过程就是按照他的一个生命周期来运行的。所以应该搞明白的是多线程的运行机制,况且在实际应用中,很少(或是几乎没有)有单线程执行任务的,就像linux系统在用户不工作的情况下,他的后台都有近一百左右的程序在运行。所以搞清楚多线程的运行机制很重。
直接看程序:
1、线程的子类继承:
多线程的继承,当两个子线程同时继承Thread的时候,会是一个什么情况呢?他们是怎么样的运行的呢?先看看下面的一个程序
package moreThread;
public class ThreadZiLei extends Thread{
String s ;
int m, count = 0 ;
public ThreadZiLei(String s, int m) { //构造函数
super();
this.s = s;
this.m = m;
}
public void run() {
try
{
while(true)
{
System.out.print(s);
sleep(m);
count++;
if(count>=20) {
break;
}
}
System.out.print(s+"finished!");
}
catch(InterruptedException e) {
return;
}
}
public static void main (String args[]) {
ThreadZiLei threadZiLeiA = new ThreadZiLei("A ",50);
ThreadZiLei threadZiLeiB = new ThreadZiLei("B ",100);
threadZiLeiA.start();
threadZiLeiB.start();
System.out.print("main is finished !"); //说明thread A、B和main函数是并发的线程。
}
}
结果:
A main is finished !B A A B A A B A A B A A B A A B A A B A A B A A B A A B A A finished!B B B B B B B B B B B finished!
说明:从输出的结果我们可以看到线程A、B和main函数是并行进行的,但是为什么main函数是在A、B之后呢,这是因为当程序走到threadZiLeiA.start();时,会跳到 ThreadZiLei方法中去执行相关的程序,这个“跳”的过程会利用一段时间,但是并不是说执行完了这个方法才来执执行main的输出,只是说,在这里线程A的输出稍微比main函数中的System.out.print("main is finished !");执行快乐一点。那么我们做个测试,如果把线程A的间隔设值的大一点呢?当我把线程A的输出设置为500的时候,运行的结果如下:
main is finished !B A B B B B B A B B B B B A B B B B B A B B B B A B finished!A A A A A A A A A A A A A A A A finished!
这就很明白了,这说明在“跳”的执行那两个语句的时候用的时间在50到100毫秒之间,所以才会有上面的一个输出结果。不知道这样讲大家能不能明白?下面的关于这个main函数中的输出问题就不再赘述了,大家看到有不同此处的地方,还是自己验证一下,改变一下变量等,或许会有不一样的收获的。
2、通过runnable接口实现多线程
下面来讲一下如何通过runnable接口来实现多线程?run()函数又是怎么回事?
package moreThread;
public class RunnableInterfece implements Runnable{
String s ;
int m, count = 0 ;
public RunnableInterfece(String s, int m) {
super();
this.s = s;
this.m = m;
}
/**
* public void run()使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。
*/
public void run() {
try
{
while(true)
{
System.out.print(s);
Thread.sleep(m); //这是在接口调用,注意与thread子类调用的方法有什么不同之处
count++;
if(count>=20) {
break;
}
}
System.out.print(s+"has finished!");
}
catch(InterruptedException e) {
return;
}
}
结果:
A B main is finished !A A B A A B A A B A A B A A B A A B A A B A A B A A B A A has finished!B B B B B B B B B B B has finished!
注意比较和上面的 main is finished !输出有什么不同?
说明:看到上面的run()函数的注释,应该是知道run()函数的意义了吧,其实他就是和线程组合配套使用的,且方法run
的常规协定是,它可能执行任何所需的动作。
再来看下Runnable
接口,该接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run
的无参数方法。
设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。例如,Thread
类实现了 Runnable
。激活的意思是说某个线程已启动并且尚未停止。此外,Runnable
为非Thread
子类的类提供了一种激活方式。通过实例化某个 Thread
实例并将自身作为运行目标,就可以运行实现Runnable
的类而无需创建 Thread
的子类。大多数情况下,如果只想重写 run()
方法,而不重写其他Thread
方法,那么应使用 Runnable
接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。
3、线程的等待
本例说明线程等待的特性,即在给出等待随机过程的情况下会发生什么样的情况?
3.1 各线程并发执行
即没有给出限制条件的情况下,让两个或多个线程随机的进行运行,这种情况也是大多说情况下的实例
package moreThread;
public class ThreadWaiting extends Thread{
String s ;
int m, i = 0 ;
public ThreadWaiting(String s) {
super();
this.s = s;
}
public void run() {
try
{
for(i = 0 ;i<6;i++)
{
Thread.sleep((int)(500*Math.random())); //线程每次睡眠的时间为一个随机值
System.out.print(s);
}
System.out.print(s+"has finished!");
}
catch(InterruptedException e) {
return;
}
}
public static void main (String args[]) {
ThreadWaiting threadA = new ThreadWaiting("A "); //调用构造函数来构造两个对象
ThreadWaiting threadB = new ThreadWaiting("B ");
threadA.start();
threadB.start();
System.out.print("main is finished !"); //测试main函数里的执行顺序,说明thread A、B和main函数是并发的线程。thread有个睡眠等待的过程,所以main函数就先执行了
}
}
结果
main is finished !B B A A B A B A B B B has finished!A A A has finished!
通过前两个例子的理解,再来看这个例子其实是很好理解的,就是两个程序的随机并发的执行任务,各自执行的各自的,互不干涉!强调这点很重要,因为下面要讲的与这个有关。
接下来要讲的是在上面的例子的基础上,稍微作了一点点的小的改动,而其结果却发生了很大的变化。
package moreThread;
public class ThreadWaitingJion extends Thread{
String s ;
int m, i = 0 ;
/**
* 构造函数
* @param s
*/
public ThreadWaitingJion(String s) {
super();
this.s = s;
}
/**
* 功能实现函数:连续输出一组字符,并间隔睡眠一段随机时间
*/
public void run() {
try
{
for(i = 0 ;i<6;i++)
{
Thread.sleep((int)(500*Math.random())); //线程每次睡眠的时间为一个随机值500*Math.random()
System.out.print(s);
}
System.out.print(s+"has finished!");
}
catch(InterruptedException e) {
return;
}
}
public static void main (String args[]) {
ThreadWaitingJion threadA = new ThreadWaitingJion("A "); //调用构造函数来构造两个对象
ThreadWaitingJion threadB = new ThreadWaitingJion("B ");
threadA.start();
try
{
threadA.join(); //jion()方法的功能是先执行完一个线程,然后再去执行另一个线程,且会抛出异常。 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。
}
catch(InterruptedException e) {}
threadB.start();
System.out.print("main is finished !"); //测试main函数里的执行顺序,说明thread A、B和main函数是并发的线程。thread有个睡眠等待的过程,所以main函数就先执行了
}
}
结果:
A A A A A A A has finished!main is finished !B B B B B B B has finished!
说明:看到没有,结果,有什么不同的变化?再对比一下这个程序与上面的一个程序有什么不同之处(绿体code)?就像注释所言:jion()方法的功能是先执行完一个线程,然后再去执行另一个线程,且会抛出异常。 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。这就意味着,只能等待这个程序运行完以后才能运行下一个程序,就如结果所运行的一样的。不知能接受不?
模拟银行取款系统:
本例是模拟银行的一个简单地取款系统,本来还有依程序的,没有在这里贴出来,那个程序的大体和这个一样的,就是本程序的sub方法中加了一个synchronized修饰词。且其作用已在sub方法的前面注释中写的很明白的了。没有synchronized的修饰词的例子的执行结果是1900 1900 1800 1800 ……说这个主要是想说明本例的特点:就是存取共享内存变量的问题:
package moreThread;
class Cbank{
private static int s = 2000;
/**
* 实现同步操作的方式是在共享内存变量的方法前加synchronized修饰符。在程序运行过程中,如果某一线程调用
* 经synchronized修饰的方法,在该线程结束此方法之前,其他所有线程都不能运行该方法,只有等待太线程完成此方法的
* 运行后,其他线程才能运行该方法。
* 相当于,系统暂时为该方法中的变量加锁,使得其他的线程无法存取该变量。
* @param m //所取款数目
*/
public synchronized static void sub (int m) {
int temp = s;
temp-=m;
try {
Thread.sleep((int)(1000*Math.random())); //随机睡眠1000*Math.random()时间
}
catch(InterruptedException e) { }
s=temp;
System.out.print(s+" || ");
}
}
class Customer extends Thread {
public void run() {
for(int i= 1; i <= 4; i++) {
Cbank.sub(100);
}
}
}
public class ThreadCogradient {
public static void main (String srgs[]) {
Customer customer1 = new Customer();
Customer customer2 = new Customer();
customer1.start();
customer2.start();
}
}
结果:
1900 || 1800 || 1700 || 1600 || 1500 || 1400 || 1300 || 1200 ||
说明:好了,上面说了,本例的特点是要说明内存共享问题,那么,是怎么个一回事呢?原来,在线程的内存中,并发的程序可以同时存取同一内存变量(共享内存变量)。这样就导致了,一个线程去过变量后,在等待的时间内,即变量还没有改变的情况下,另一个线程也对该内存进行了内存的变量存取操作,这样就导致了重复操作,但有可能只记录了一次。所以我们要解决这样的问题,就要想办法让他们分别对内存进行操作。即在当一个线程存取共享变量的是时候,系统为该变量加锁,是其他的线程无法存取该变量。这就出现了上面的synchronized的修饰方法,以及他的作用。
PS:由于之前时间比较紧,没有附上说明,现在特别附上说明,希望对你有用。欢迎提出宝贵意见。谢谢!