Java Thread 入门

Java的Thread机制可以类比进程,可让几个操作同时执行,详情googl:time sharing。

 

线程架构图:代表一个线程持有CPU资源,代码资源和数据资源

Java Thread 入门_第1张图片
 

Java中想让某种操作具有线程能力有两种方式:

extends Thread和implements Runnable,重载run 方法,在里面实现想要的操作:

请看码:

public class TestThread {

	public static void main(String[] args) {
		new TestThread().testThreadRun();
	}
	
	
	/**
	 *  extends Thread 方式
	 */
	class T1 extends Thread{
		private String name;
		
		public T1(String name) {
			this.name = name;
		}
		
		@Override
		public void run() {
			for (int i =0; i<10; i++) {
				System.out.println(this.name + " print " + i);
			}
			
		}
	}
	
	/**
	 *  implements Runnable 方式
	 */
	class T2 implements Runnable {
		private String name;
		
		
		public T2(String name) {
			this.name = name;
		}
		
		@Override
		public void run() {
			for (int i =0; i<10; i++) {
				System.out.println(this.name + " print " + i);
			}
		}
		
	}
	
	public void testThreadRun() {
		T1 t1 = new T1("Oham");
		T2 t2 = new T2("Lulu");
		
		// 继承Thread的直接调用start, 线程进入Runnable状态
		t1.start();  
		
		// 实现Runnable的对象需要作为new出的Thread对象的构造,由Thread对象start, 线程进入Runnable状态
		new Thread(t2).start(); 
		
	}

 

调用start后线程状态如下:

Java Thread 入门_第2张图片

 

调用start后线程并不是立刻进入Running状态,而是可执行状态,等待CPU的调度,当得到CPU分配的时间片线程才能进入Running状态,这个过程是系统自身的过程,我们无法干涉(控制CPU分配时间片到某个线程)。
 
 进入Running状态后线程执行run中的操作,当run执行完毕后线程进入死亡状态,无法再回到其他状态了,线程在非死亡状态时只能调用一次start方法。


Java Thread 入门_第3张图片
 

-------------------------------------------------------------------------------------------------------------------------------------
 暂停线程执行

1.sleep方法

正如其名,就是让Thread对象睡睡觉,此方法需要传入一个时长参数,单位为毫秒,表示过一段时间再恢复可执行状态,注意是可执行状态(Runnable),若指定时长为1秒,是指暂停时长为1秒,暂停完毕后进入可执行状态,等待CPU的调度,所以到执行状态(Running)其实已经超过一秒。

class TestSleep implements Runnable {

		@Override
		public void run() {
			while (true) {
				try {
					
					System.out.print("**");
					
					//调用sleep方法将使Thread进入“暂停”(blocked)状态
					Thread.sleep(3000);
					
					System.out.println("暂停3秒");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		
	}

 

2.yield方法,让某一执行当中的线程交出CPU时间片,进入可执行状态,不过可能又立刻被分配到CPU时间片。。。

修改前面的T1run方法:

class T1 extends Thread{
		private String name;
		
		public T1(String name) {
			this.name = name;
		}
		
		@Override
		public void run() {
			for (int i =0; i<10; i++) {
				System.out.println(this.name + " print " + i);
				
				//让出当前执行线程的时间片,进入Runnable状态
				Thread.yield();
			}
			
		}
	}

 试重新运行结果。。。不好评价。。。,可以把for的循环设置为10000试试,效果更佳。

 

3.join  有甲乙两个线程,要求甲线程必须等待乙线程执行玩后才能执行,这时我们用到join。

借书上一个例子:时间不早妈妈开始煮饭,于是进厨房开始准备,去发现米酒用完了,所以叫儿子去巷口士多买瓶米酒回来,等到买回来了妈妈才又开始煮饭,直至饭煮好。

 这里的例子用到两个线程,mother和son,中间儿子去买酒,mother线程必须等待son线程执行完毕后才往下执行,这里就用到join,从方法字面理解,join——参与,也就是说son线程参与到mother线程当中,相当于把son线程的run操作放到mother线程的run操作中去,合并为一个线程操作(是相当于,其实里头还是两个线程,只是效果如是而已)。

package test;

public class MotherThread implements Runnable {

	class SonThread implements Runnable {

		@Override
		public void run() {
			System.out.println("儿子出门买米酒");
			System.out.println("儿子出门需要5分钟");
			try {
				for(int i=1; i<=5; i++) {
					Thread.sleep(1000);
					System.out.print(i + "分钟  ");
				}
			} catch (InterruptedException e) {
				
				System.err.println("儿子粗大事了");
			}
			System.out.println("\n儿子买酒回来了");
		}
		
	}
	
	@Override
	public void run() {
		System.out.println("妈妈准备煮饭");
		System.out.println("妈妈发现米酒用完");
		System.out.println("妈妈叫儿子去买米酒");
		
		Thread son = new Thread(new SonThread());
		son.start();
		
		System.out.println("妈妈等待儿子把米酒买回来");
		
		try {
			//son线程调用join是mother线程挂起进入Blocked状态,等待son线程执行run操作完毕
			son.join();
		} catch (InterruptedException e) {
			System.err.println("发生异常");
			System.err.println("妈妈中断煮饭");
			System.exit(1);
		}
		
		System.out.println("妈妈开始煮饭");
		System.out.println("饭煮好了");
		
	}
	
	public static void main(String[] args) {
		new Thread(new MotherThread()).start();
	}

}

 

 状态图:

Java Thread 入门_第4张图片
 ------------------------------------------------------------------------------------------------------------------------------------

同步处理

 

不同的线程之间,线程支配的资源:CPU资源,代码资源和数据资源,除了CPU外其他的都可以共用,代码是写好的,共用没问题,但数据共用就有问题了,因为不同的线程获得CPU时间片不可预计,当其中涉及到对数据操作时,对单个线程而言其数据已经造成混乱。

首先给共享代码,但没有共享数据的两个线程:

package test;

public class TestShare {
	
	class ShareData  implements Runnable {
		
		int i;
		
		@Override
		public void run() {
			while(i < 10) {
				i++;
				for(int j=0;j<10000000; j++);
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
	
	public void goTest() {
		
		//共享代码,没有共享数据
		ShareData s1 = new ShareData();
		ShareData s2 = new ShareData();
		
		Thread t1 = new Thread(s1);
		Thread t2 = new Thread(s2);
		
		t1.setName("Oham");
		t2.setName("Lulu");
		
		t1.start();
		t2.start();
		
		
		
	}
	
	public static void main(String[] args) {
		 new TestShare().goTest();
	}
	
	

}

 

运行结果是对于单个线程而言依次打印出i 1至10。若将goTest改为:

public void goTest() {
		
		//共享代码,共享数据
		ShareData s = new ShareData();
		
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		
		t1.setName("Oham");
		t2.setName("Lulu");
		
		t1.start();
		t2.start();
		
	}

 

结果是对整个结果而言,i没有依次输出1至10。原因是两个线程争夺CPU时间片的结果把i在打印出之前或之时就已i++。就是因竞争CPU时间片说代码段被分割执行了。

为了防止多线程情形下代码被分割执行,这里需要锁机制——synchronized:

语法:

sychronized(要取得锁的对象)——问谁取得锁 

{要锁定保护的代码}

 

修改上述run方法:

public void run() {
			while(i < 10) {
                               //未取得锁的线程进入锁定池等待
				synchronized (this) {
					i++;
					for(int j=0;j<10000000; j++);
					System.out.println(Thread.currentThread().getName() + ":" + i);
				}
			}
		}

 从结果可以看出对于整体结果而言,i是依次输出1至10了,只是两个线程交互执行。

一点细节:受synchronized保护的代码段中,要访问的对象应该设置为private,否则可以通过其他的方式访问该对象了,这样synchronized好像失去了实质意义。

状态图:

 
Java Thread 入门_第5张图片
 如果一个线程要执行某段锁定的程序代码,但它没有取得指定的锁,那么该线程就会从执行状态进入锁定池中等待,当获取到锁后,它会转移到可执行状态(Runnable)。

 

Java的锁定机制若是滥用,很可能会造成系统进入死锁状态,如下图情况是死锁的一种:

 
Java Thread 入门_第6张图片
 预防死锁状态,Java有种Monitor Model的机制,除了预防死锁,还保证共用数据的一致状态。

 看一个经典的生产者与消费者问题:

生产者生产东西,不可能无节制地生产下去,因为库存量的限制,所以未到达一定的库存量时,生产者会继续生产;当达到生产量时库存量时,生产者就等待消费者用掉库存,而消费者看到有库存的时候才会进行消费;如果库存量没有了,消费者就等待生产者生产出东西来。

 

Storage.java

package test;

public class Storage {

	private int count;   //记录库存量
	private int size;    //库存量上限
	
	public Storage(int s) {
		size = s;
	}
	
	/**
	 *供生产者调用,生产被执行时累加库存量 
	 */
	public synchronized void addData(String producer) {
		
		//检查库存量是否达到上限,如果是则此对象调用wait方法,
		//持有该对象锁的Running状态的线程就进入等待状态(Wait pool)
		
		while(count == size) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		//通知其他正在等待的消费者,在wait pool中挑选任一线程,使其进入Lock Pool当中
		this.notify();
		count++;
		System.out.println(producer + " make data count: " + count);
	}
	
	public synchronized void delData(String producer) {
		
		//与addData相对,检查库存量是否为零,如果是则
		//持有该对象锁的Running状态的线程就进入等待状态,
		//顺便一提,此处用while是因为调用notify唤醒wait pool中的线程
		//时是任意挑选的,且或是生产者,或是消费者被调醒,醒后又
		//不会立刻执行。有可能别的线程先执行了,所以有必要再检查一次。
		//(某一线程被wait,它的执行流程停在wait方法处,当被唤醒重新进入
		//Running状态时,执行流程从wait调用之后开始,而不是重新来过一遍)
		while(count == 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		this.notify();
		count--;
		System.out.println(producer + " use data count: " + count);
	}
	

}

 

 

 

Producer.java

package test;

public class Producer extends Thread {
	private String name;
	private Storage storage;
	
	public Producer(String name, Storage storage) {
		this.name = name;
		this.storage = storage;
	}
	
	@Override
	public void run() {
		while(true) {
			storage.addData(name);
			
			try {
				Thread.sleep((int)Math.random()*3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

 

Consumer.java

package test;

public class Consumer extends Thread {
	private String name;
	private Storage storage;
	
	public Consumer(String name, Storage storage) {
		this.name = name;
		this.storage = storage;
	}
	
	@Override
	public void run() {
		while(true) {
			storage.delData(name);
			
			try {
				Thread.sleep((int)Math.random()*3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

执行测试代码:

Storage storage = new Storage(5);
		Producer p1 = new Producer("Oham", storage);
		Producer p2 = new Producer("Lulu", storage);
		Consumer c = new Consumer("Cancan", storage);
		
		p1.start();
		p2.start();
		c.start();

注意:wait与notify必须在sychronized的代码块内调用,否则运行时抛java.lang.IllegalMonitorStateException。
           wait是使得持有特定锁A的当前Running的线程进入wait pool,而notify是针对该特定锁A去notify,若是针对另外的锁B去notify,那么将不会去唤醒针对特定锁A的在wait pool当中的线程,而是唤醒另外的锁B对应的wait pool。

           wait 与 notify 要用于相对而言的业务逻辑当中才能发挥Monitor Model的机制的作用,即一方wait的条件成立,另一方的notify条件必须成立,一方notify条件成立,另一方wait条件同时成立。如此才能预防死锁状态和保证共用数据的一致状态。

 

状态图:

Java Thread 入门_第7张图片
 


 

 

你可能感兴趣的:(java,线程,基础)