一、程序、进程、线程:

程序:指令集、静态概念

进程:OS调度程序、动态概念。资源分配的单位。

线程:在进程内多条执行路径。线程又被称为轻量级的进程,一个进程可拥有多个并行的线程。容易造成并发的问题。main、gc和异常分别是不同的线程。调度和执行的单位。


二、Java实现多线程:

  1. 继承java.lang.Thread,重写run()方法:

    启动:创建子类对象+对象.start()。

    下面程序使用继承Thread类方式,模拟3个进程同时运行(包括一个主线程main)。

    常用工具:Thread.sleep(long millis)

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

例如:Thread.sleep(2000); 程序休息2秒。

package com.pmpa.thread;

/**
 * 
 * @author pmpa
 * @Date 2016-06-08
 * 模拟龟兔赛跑
 * 1、创建多线程  继承  Thread  +重写run(线程体)
 * 2、使用线程: 创建子类对象 + 对象.start()  线程启动
 *
 */
class Rabbit extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<20;i++)
		{
			System.out.println("Rabbit moves "+i+" steps!!");
		}
	}
}

class Tortoise extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<20;i++)
		{
			System.out.println("Tortoise moves "+i+" steps!!");
		}
	}
}


public class RabbitTortoiseRace {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Rabbit rabbit = new Rabbit();
		Tortoise tortoise = new Tortoise();
		
		rabbit.start();  
		tortoise.start();
		
		//不要调用run方法
		//rabbit.run();
		//tortoise.run();
		
		for(int i=0;i<20;i++)
		{
			System.out.println("main method moves "+i+" steps!!");
		}
		
	}

}

2. 实现Runnable接口:

启动:a.创建真实角色;b.创建代理角色+引用;c.代理角色.start()。

继承Thread类创建的方式,有弱点。因为java是单继承方式的。

Runnable使用了“静态代理”的设计模式。我们编写的实现了Runnable接口的类是真实类,Thread类是代理类。真实类和Thread类都实现了Runnable接口,在代理类Thread中声明了Runnable属性target。

(1)静态代理模式:

package com.pmpa.thread;

/**
 * 
 * @author pmap
 * @Date 2016-06-18
 * 
 * 静态代理:
 * 1. 真实角色
 * 2. 代理角色:持有真实角色的引用
 * 3. 二者 实现相同的接口
 *
 */
//实现的共同接口
interface RentHouse{
	  void renthouse();
	}

//真实类 真实租房人
class RealPerson implements RentHouse{
	@Override
	public void renthouse() {
		// TODO Auto-generated method stub
		System.out.println("小明租房");
	}
	
}

//代理类,租房中介公司
class LianJia implements RentHouse{
	
	private RealPerson realperson;
	public LianJia() {
		super();
	}
	public LianJia(RealPerson realperson) {
		super();
		this.realperson = realperson;
	}
	private void beforeRenting(){
		System.out.println("中介公司找房源");
	}
	private void afterRenting(){
		System.out.println("签订租房合同");
	}
	
	@Override
	public void renthouse() {
		// TODO Auto-generated method stub
		beforeRenting();
		realperson.renthouse();
		afterRenting();
	}
	
}

public class StaticProxy {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//创建真实角色
		RealPerson realperson = new RealPerson();
		LianJia lianjia =new LianJia(realperson);
		lianjia.renthouse();
	}

}

(2)使用Runnable创建线程:

创建真实角色(我们自己的类);

创建代理角色 + 真实角色引用;

调用start() 启动线程。


使用Runnable的好处:

a.避免单继承

b.方便共享资源


继承Runnable实现的多线程实例:

package com.pmpa.thread;

/**
 * 实现Runnable接口方式实现多线程。
 * @author Administrator
 *
 */

class Fog implements Runnable{

	@Override
	public void run() {
		for (int i=0;i<200;i++)
		{
			System.out.println("青蛙呱呱叫!!");
		}
		
	}
	
}

class Duck implements Runnable{
	@Override
	public void run() {
		for (int i=0;i<200;i++)
		{
			System.out.println("鸭子嘎嘎叫!!");
		}
		
	}
}

public class RunnableThread  {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//创建真实类
		Fog fog = new Fog();
		Duck duck = new Duck();
		//创建代理类: +真实角色引用
		Thread thred1 =  new Thread(fog);
		Thread thred2 =  new Thread(duck);
		//调用 .start() 启动线程
		thred1.start();
		thred2.start();
		
	}

}

使用实现Runnable接口的方式来实现线程,可以方便“资源共享”,下面是一个抢票的例子。

package com.pmpa.thread;
/**
 * 
 * @author Administrator
 * @Date 2016-06-18
 * 模拟买票动作
 */
public class OrderTicket implements Runnable {
	
	int TicketNum = 50;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true)
		{
			if (TicketNum > 0)
			{
				System.out.println(Thread.currentThread().getName() + "抢到了第"+(TicketNum--)+"张票!!");				
			}
			else
			{
				break;
			}
		}
	}
	
	public static void main(String[] args) {
		OrderTicket orders = new OrderTicket();
		
		Thread t1 = new Thread(orders,"黄牛1");
		Thread t2 = new Thread(orders,"黄牛2");
		Thread t3 = new Thread(orders,"黄牛3");
		
		t1.start();
		t2.start();
		t3.start();
	}

}

(3)实现Callable接口方式,实现多线程:

Callable接口与Runnable接口类似,但是该接口可以返回值或者抛出异常。


Callable 和 Future 接口 

Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。 

Callable和Runnable有几点不同:  

(1)Callable规定的方法是call(),而Runnable规定的方法是run(). 

(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。  

(3)call()方法可抛出异常,而run()方法是不能抛出异常的。 

(4)运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。 

它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。 

通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。涉及到ExecutorService、Executors、Future几个类和接口的使用:

下面例子是用实现Callable接口实现的线程:

package com.pmpa.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class Race implements Callable<Integer>
{
	
	private String name ; //名称
	private long time; //延时时间
	private boolean flag =true;
	private int step =0; //步
	
	

	public Race(String name, long time) {
		super();
		this.name = name;
		this.time = time;
	}



	public String getName() {
		return name;
	}



	public void setName(String name) {
		this.name = name;
	}



	public long getTime() {
		return time;
	}



	public void setTime(long time) {
		this.time = time;
	}



	public boolean isFlag() {
		return flag;
	}



	public void setFlag(boolean flag) {
		this.flag = flag;
	}



	public int getStep() {
		return step;
	}

	public void setStep(int step) {
		this.step = step;
	}

	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		while(true)
		{
			if(flag == true)
			{
				Thread.sleep(time);
				System.out.println(name + "走了第"+(step++)+"步");
			}
			else break;
		}
		return step;
	}
	
	
	
}

public class CallableThread {
	
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		Race rabbit = new Race("兔子", 500);  //间隔时间 0.5秒
		Race tortoise = new Race("乌龟", 2000);  //间隔时间2秒
		
		// 创建一个执行任务的服务  
		ExecutorService es = Executors.newFixedThreadPool(2);
		
		//提交task线程,获取值。获取线程的返回值 。
		Future<Integer> result1 =es.submit(tortoise) ;
		Future<Integer> result2 =es.submit(rabbit) ;
		
		Thread.sleep(20000); //2秒
		tortoise.setFlag(false); //停止线程体循环
		rabbit.setFlag(false);
		
		int num1 =result1.get();
		int num2 =result2.get();
		System.out.println("乌龟一共跑了-->"+num1+"步");
		System.out.println("兔子一共跑了-->"+num2+"步");
		
		//关闭服务
		es.shutdownNow();
		
	}

}


三、线程的状态:

  1. 线程的五大状态:

wKioL1dljb7CPIuQAAB1vie5hX4414.jpg

线程从创建、运行到结束总是处于下面五个状态之一:

创建状态、就绪状态、运行状态、阻塞状态及死亡状态(终止)。


2.停止状态:

a.自然终止:线程体正常执行完毕

b.外部干涉:

(1)线程类中 线程体使用的标识

(2)线程体内使用该标识

(3)提供对外的方法改变该标识

(4)外部根据条件调用该方法

Thread的stop和destry (已经过时),不安全,建议不要使用。

下面程序演示了线程的外部干涉终止的实例:

package com.pmpa.thread.status;

/**
 * 外部干涉线程停止
 * @author Administrator
 *
 */

class StopOuter implements Runnable
{
	//1.线程类中 定义 线程体使用的标识	 
	private boolean flag = true;

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	//3、对外提供方法改变标识
	public void stop(){
		this.flag = false;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		int i = 1;
		//2、线程体使用该标识
		while(flag)
		{
			
			System.out.println("Class StopOuter is Running..."+i);
			try {
				Thread.sleep(2000);
				i++;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

public class StopDemo01 {

	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		StopOuter s1 = new StopOuter();
		Thread t1 = new Thread(s1,"TestingStop");
		
		t1.start();
		Thread.sleep(20000);
		//4、外部干涉
		s1.setFlag(false);
		System.out.println("Thread is stopped!!");

	}

}

3. 阻塞状态:

a.join:合并线程:

被join进来的线程会先执行完。


b.yield:暂停进程:

yield方法并没有完全意义上的暂停,只是暂时释放资源给CPU,让CPU执行其他未执行完的线程。也有可能在下一个时钟周期,CPU又开始执行这个线程了。

yield()是一个静态方法,使用Thread.yield()。 这个方法写在哪个线程体里,就暂停哪个线程。

API上的说明:

It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions. It may also be useful when designing concurrency control constructs such as the ones in the java.util.concurrent.locks package.

JoinYieldDemo01.java

package com.pmpa.thread.status;

/**
 * yield()是一个静态方法,使用Thread.yield()。 这个方法写在哪个线程体里,就暂停哪个线程。
 * @author Administrator
 *
 */

class Rabbit extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<100;i++)
		{
			System.out.println("Rabbit moves "+i+" steps!!");
			
		}
	}
}

class Tortoise extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<100;i++)
		{
			System.out.println("Tortoise moves "+i+" steps!!");
		}
	}
}

public class JoinYieldDemo01 {

	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		Tortoise t1 = new Tortoise();
		t1.start();
		for (int i = 1 ; i<100 ;i++){
			System.out.println("main ...." + i + "Steps!");
			if(i==50)
			{
				t1.join(); //t1线程join进来,main进程会等待t1执行完。所以说main线程阻塞了。
			}
		}
		
		System.out.println("------------------------Tortoise Finished!!!------------------------");

		Rabbit t2 = new Rabbit();
		t2.start();
		
		for (int i = 1 ; i<100 ;i++){
			System.out.println("main ...." + i + "Steps!");
			if(i==50)
			{
				//yield并没有严格意义上的暂停,
				Thread.yield(); //写在了main线程中,那么会暂停main线程,先执行Rabbit线程。
			}
		}


		
		
		
	}

}

c.sleep方法:

休眠,不释放锁。 每一个对象都会生成一个锁,占用一定的资源。在sleep时,不释放锁。

sleep方法的应用:1.与时间相关的,倒计时  2.模拟网络延时。

下例子展示了时间倒计时的程序:

SleepDemo01.java:

package com.pmpa.thread.status;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 1.倒计时,1秒打印一个
 * @author Administrator
 *
 */
public class SleepDemo01 {
	
	
   /**
    * 模拟10以内的倒计时,每隔1秒打印一个数字:
    * 	
    */
   public static void Daojishi(){
		for (int i=10 ; i>0 ; i--){
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
   }
   
   /**
    * 
    * 模拟时间倒计时,给出一个
    * @param args
    * @throws InterruptedException
    */
	
   public static void TimerDao(){
	   Date d1 = new Date(System.currentTimeMillis()+10*1000); //当前时间往前推10秒
	   long time_next =  d1.getTime();
	   long time_current = System.currentTimeMillis();
	   System.out.println("当前时间:"+ (new Date(time_current)) );
	   while(true)
	   {
		   System.out.println(new SimpleDateFormat("hh:mm:ss").format(new Date(time_next)));
		   try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		   if (time_next < time_current)
		   {
			   break;
		   }
		   time_next = time_next -1000;
	   }
	   
   }
	

	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub	
		System.out.println("-------------按秒倒计时程序!!!-------------");
		Daojishi();
		System.out.println("-------------时间倒计时程序!!!-------------");
		TimerDao();
	}

}

网络阻塞模拟程序SleepDemo2.java

下边程序,由于三个线程共享“余票”资源,所以会发生并发的错误。产生了0张票,-1张票的错误,因为在判断第1张票时,由于之前的2个线程都进行了sleep,所以他们都会通过条件“if (TicketNum > 0)”,所以在第一个线程-1后,后2个线程也都-1,就出现了错误(0和-1张票)。

package com.pmpa.thread.status;

import com.pmpa.thread.OrderTicket;

public class SleepDemo2 implements Runnable {

	int TicketNum = 50;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true)
		{
			if (TicketNum > 0)
			{
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "抢到了第"+(TicketNum--)+"张票!!");				
			}
			else
			{
				break;
			}
		}
	}	
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SleepDemo2 orders = new SleepDemo2();
		
		Thread t1 = new Thread(orders,"黄牛1");
		Thread t2 = new Thread(orders,"黄牛2");
		Thread t3 = new Thread(orders,"黄牛3");
		
		t1.start();
		t2.start();
		t3.start();
	}

}

四、线程的基本信息:

Thread.currentThread():当前线程。

setName():设置名称;

getName():获取名称:

isAlive():判断状态。


getPriority():获取优先级

setPriority():设置优先级

MIN_PRIORITY:1

MAX_PRIORITY:10

NORM_PRIORITY:5

优先级:代表的是概率,不是绝对的优先级,不代表绝对的先后顺序。例如线程t1和t2,如果t1的优先级比t2的优先级高,不代表执行t1不执行t2,而是t1执行的概率比t2的高。

MyThread.java

package com.pmpa.thread.info;

public class MyThread implements Runnable {
	private boolean flag =true;
	private int num =0;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(flag)
		{
			System.out.println(Thread.currentThread().getName()+"-->"+num++);
		}
		
	}
	public void stop(){
		this.flag=false;
	}
}

Info1.java:

package com.pmpa.thread.info;

public class Info1 {

	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stu
		
		MyThread it =new MyThread();
		Thread proxy =new Thread(it,"挨踢");
		proxy.setName("test");
		System.out.println(proxy.getName());
		System.out.println(Thread.currentThread().getName()); //返回当前所在线程的名称:是main,该代码在main线程体中。
		
		proxy.start();
		System.out.println("启动后的状态:"+proxy.isAlive());
		Thread.sleep(200);
		it.stop();
		Thread.sleep(100);
		System.out.println("停止后的状态:"+proxy.isAlive());
	}

}

Info2.java:

package com.pmpa.thread.info;

/**
 * 优先级:概率,不是绝对的先后顺序
 * @author Administrator
 *
 */

public class Info2 {

	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		System.out.println(Thread.MIN_PRIORITY);  //1
		System.out.println(Thread.MAX_PRIORITY);  //10
		System.out.println(Thread.NORM_PRIORITY); //5
		MyThread it =new MyThread();
		Thread p1 =new Thread(it,"挨踢1");
		MyThread it2 =new MyThread();
		Thread p2 =new Thread(it2,"挨踢2");
		
		p1.setPriority(Thread.MIN_PRIORITY); //设置优先级
		p2.setPriority(Thread.MAX_PRIORITY);//设置优先级
		p1.start();
		p2.start();
			
		
		Thread.sleep(100);
		it.stop();
		it2.stop();
	}

}