`
i学霸
  • 浏览: 12792 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

I学霸官方免费教程三十九 :Java基础教程之线程

阅读更多

线程

线程和进程

进程:系统运行程序的最小单位;一个进程最少有一个线程
线程:程序执行任务的最小单位;线程与线程之间并行
一个进程至少有一个线程,在java中这个线程称为主线程,由系统创建,运行main方法。这样只有一个线程的程序也被称为单线程程序。
主线程从程序入口main方法开始执行代码,执行任意方法中的代码都是按照自上而下的顺序执行的,如果只有一个主线程,又想实现在线听音乐这个功能的话,就很难实现。因为主线程必须先去下载音乐;下载完成后,在执行播放音乐;这显然不能满足当今人们对在线听音乐的需求。所以现在的程序都是多线程的,就在线听音乐这个功能而言,可以在main方法中创建一个线程负责下载音乐,在创建一个线程负责播放音乐。这样在听音乐的同时,用于还可以进行其他操作。


创建线程

创建线程的两种方式
1、实现Runnable接口:还可以继承其他类
2、继承Thread类:使用简单

方式一:<span style="font-size: 14px;">实现Runnable接口</span>
package thread;
/**
 * 创建MyRunnable类,并实现Runnable接口 
 * @author 学霸联盟 - 赵灿
 */
public class MyRunnable implements Runnable {
	// 必须重写Runnable接口中的方法,不重写会出现语法错误
	// Runnable接口中只声明了一个方法run()
	@Override
	public void run() {
		// 线程执行的代码
		System.out.println("MyRunnable-run");
	}
}
方式二:
package thread; 
/**
 * 创建MyThread类,并继承Thread接口 
 * @author 学霸联盟 - 赵灿
 */
public class MyThread extends Thread {
	/*
	 * Thread类也实现了Runnable接口,并重写了接口中的run方法
	 * 所以此处如果没有重写run方法,不会出现语法错误,但创建的这个线程也就没有意义了
	 */
	@Override
	public void run() {
		// 线程执行的代码
		System.out.println("MyThread-run");
	}
}
package thread;
/**
 * 创建测试类Test,测试以上两种方式创建的线程 
 * @author 学霸联盟 - 赵灿
 */
public class Test {
	public static void main(String[] args) {
		/*********** 方式一 ************/
		// 创建一个MyRunnable类型的对象mr
		MyRunnable mr = new MyRunnable();
		// 创建一个线程对象t;并将mr作为参数传入Thread对象
		Thread t = new Thread(mr);
		/*
		 * 注意:启动线程调用的是Thread类中的start方法,而不是调用run方法;
		 * 如果直接调用run方法,将只表示run方法的调用,不会启动线程;
		 * 调用start方法启动线程t,线程启动后,会自动调用对象mr中的run方法
		 */
		t.start();

		/*********** 方式二 ************/
		// 创建一个MyThread类型的对象mt
		MyThread mt = new MyThread();
		// 因为start方法在Thread类中声明,MyThread类继承了Thread类
		// 所以此处使用对象mt调用的start方法是从父类Thread中继承的方法
		mt.start();
	}
}
输出结果:
MyRunnable-run
MyThread-run
或
MyThread-run
MyRunnable-run
由于多个线程是并行执行的,即同时运行。
就以上代码而言,会先执行t.start();再执行mt.start();但是由于这里的代码非常简单,在非常非常短的时间内,两个现场就启动完成了;而线程启动后并不是立即执行的,要等待调度程序选择哪个线程执行,所以此处启动的两个线程谁先执行并不能确定。所以输出顺序也不确定。

线程的生命周期

新建(初始态):执行new操作后。此时在已经在内存中创建出了线程对象,但线程还没有启动运行
可运行:调用start()方法后。此时线程已经启动,但还没有运行(线程的执行是通过CPU执行完成的),另一种说法是:还没有获得CPU的使用权;正在和其他可运行状态的线程一起等待系统的调度程序选取,选中哪个线程,哪个现在使用CPU执行程序,执行一定的时间(CPU的时间片)后,退出对CPU的占用,转入可运行状态,和其他线程一起等待系统调度程序的选取。
运行:已获得CPU的使用权,正在运行
阻塞:执行sleep(int)、yield()、join()、wait()方法后,阻塞状态的线程不会被调度程序选中。只有从阻塞状态恢复至可运行态以后,才有机会获得CPU的使用权
死亡:运行结束或执行stop()、destroy()方法后;这两个方法均已过时,不推荐使用;此时线程已经彻底停止运行,释放了线程所占用的资源。


线程的调度

sleep方法

static void sleep(int)静态方法,建议使用类名Thread调用;
作用:线程休眠;哪个线程执行Thread.sleep();这条语句,哪个线程就会休眠(阻塞);int类型的参数,代表线程休眠的时间(毫秒),时间结束自动恢复至可运行态,和其他线程一起等待调度程序的选取
一句话:谁执行,谁休眠。
实例:
package thread.sleep;
/**
* 创建SleepDemo类
* 用于测试sleep方法
* @author 学霸联盟 - 赵灿
*/
public  class  SleepDemo{
	public  static  void  main(String[]  args){ 
		System.out.println("主线程正在执行");
		System.out.println("主线程休眠开始");
		// 执行Thread.sleep();可能产生异常,使用try-catch语句捕获异常
		try {
			// 这条语句会被主线程执行,执行后主线程休眠1000毫秒(1秒)
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// 如果try中的代码产生异常,程序执行这条输出语句
			System.out.println("线程休眠时出现异常");
		}
		System.out.println("主线程休眠结束");
		System.out.println("主线程恢复执行");
	}
}
输出结果:
主线程正在执行
主线程休眠开始【这里会休眠(停止)1秒,1秒后继续输出以下内容】
主线程休眠结束
主线程恢复执行

yield方法

static void yield()静态方法,建议使用类名Thread调用。
作用:线程让步;哪个线程执行Thread.yield();语句,哪个线程就会把已经获得的CPU使用权让出来,并进入可运行状态,和其他线程一起等待调度程序的选取;
一句话:谁执行,谁让步。
实例:
package thread.yield;
/**
 * 创建YieldDemo类
 * 用于测试线程的让步执行
 * @author 学霸联盟 - 赵灿
 */
public  class  YieldDemo {
	public  static  void  main(String[]  args) {
		//声明ThreadA和ThreadB的对象
		ThreadA  ta = new  ThreadA();
		ThreadB  tb = new  ThreadB();
		//启动线程ta
		ta.start();
		//启动线程tb
		tb.start();
	}
}


/**
 * 创建ThreadA类,并继承Thread类
 * @author 学霸联盟 - 赵灿
 */
class  ThreadA  extends  Thread{
	//重写父类中的run方法
	@Override
	public  void  run() {
		//循环30次
		for (int  i  = 1;  i  <=  30;  i ++) {
			/*
			 * 每循环一次ThreadA类的对象(本例是ta)就会让出一次CPU的使用权,
			 * 给其他线程执行(这个例子中是主线程和ThreadB的对象tb)
			 */
			Thread.yield();
			//输出一个字符串,为了在结果中可以看到线程ta的执行频率
			System.out.println("ThreadA-"  +  i);
		}
	}
}


/**
 * 创建ThreadB类,并继承Thread类
 * @author 学霸联盟 - 赵灿
 */
class  ThreadB  extends  Thread{
	//重写父类中的run方法
	@Override
	public  void  run() {
		//循环30次
		for (int  i  = 1;  i  <=  30;  i ++) {
			//输出一个字符串,为了在结果中可以看到线程tb的执行频率
			System.out.println("ThreadB--"  +  i);
		}
	}
}
输出结果:输出顺序是不确定的;多数情况下,线程tb会先执行完,线程ta后执行完,而且线程tb的执行频率要比线程ta高,但不是绝对的

join方法

void join([int] [,int]);非静态方法,只能使用对象调用。
作用:线程插队;假设现在有线程A和B,线程A中执行了B.join();语句,线程B会插队到线程A前执行,线程A会被阻塞
一句话:谁调用,谁插队;谁执行,谁阻塞。
其中可以传入一个int类型的参数,代表插队的毫秒数,如果传入两个int类型的参数,代表插队的毫秒数(左)和纳秒数(右);插队时间结束后,插队线程和被阻塞线程都回到可运行状态,等待调度程序的选取
实例:
package thread.join;
/**
 * 创建JoinDemo类
 * 用于测试线程的让步执行
 * @author 学霸联盟 - 赵灿
 */
public class JoinDemo {
	public  static  void  main(String[]  args) {
		//创建ThreadA的对象ta
		ThreadA  ta = new  ThreadA();
		//启动线程ta
		ta.start();
	}
}


/**
 * 创建ThreadA类,并继承Thread类
 * @author 学霸联盟 - 赵灿
 */
class  ThreadA  extends  Thread{
	//重写父类中的run方法
	@Override
	public  void  run() {
		//创建ThreadB的对象tb;线程ta运行后便会先执行此句
		ThreadB  tb = new  ThreadB();
		//启动线程tb;线程tb启动后,此时线程ta和tb共同等待调度程序的选取
		tb.start();
		//循环30次
		for (int  i  = 1;  i  <=  30;  i ++) {
			/*
			 * 当i等于10时,执行tb.join();
			 * 使线程tb插队执行到线程ta前面执行
			 * 线程ta被阻塞
			 */
			if ( i == 10 ) {
				try {
					/*
					 * 只需插队一次即可
					 * 在i小于10时,线程ta和tb共同等待调度程序的选取执行
					 * i==10时,执行tb.join();
					 * 执行后,线程ta被阻塞,只执行线程tb;
					 * 直至线程tb执行完,ta才会恢复至可运行态
					 * 其中ta是代码的执行者,tb是代码的调用者
					 */
					tb.join();
				} catch (InterruptedException e) {
					//输出栈内存中的异常信息
					e.printStackTrace();
				}
			}
			//输出一个字符串,为了在结果中可以看到线程ta的执行频率
			System.out.println("ThreadA-"  +  i);
		}
	}
}


/**
 * 创建ThreadB类,并继承Thread类
 * @author 学霸联盟 - 赵灿
 */
class  ThreadB  extends  Thread{
	//重写父类中的run方法
	@Override
	public  void  run() {
		//循环30次
		for (int  i  = 1;  i  <=  30;  i ++) {
			//输出一个字符串,为了在结果中可以看到线程tb的执行频率
			System.out.println("ThreadB--"  +  i);
		}
	}
}
由于输出结果的顺序完全不确定,这里同学们一定要自行测试

版权声明:本文为博主原创文章,未经博主允许不得转载。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics