`
oham_一1一
  • 浏览: 51434 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Java Thread 入门

阅读更多

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

 

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


 

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后线程状态如下:

 

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



 

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

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();
	}

}

 

 状态图:


 ------------------------------------------------------------------------------------------------------------------------------------

同步处理

 

不同的线程之间,线程支配的资源: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好像失去了实质意义。

状态图:

 

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

 

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

 

 预防死锁状态,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条件同时成立。如此才能预防死锁状态和保证共用数据的一致状态。

 

状态图:


 


 

 

  • 大小: 10.6 KB
  • 大小: 13.1 KB
  • 大小: 21.6 KB
  • 大小: 31.9 KB
  • 大小: 41.3 KB
  • 大小: 44.3 KB
  • 大小: 3.5 KB
  • 大小: 46.1 KB
分享到:
评论

相关推荐

    JAVA基础入门到精通pdf

    10. **多线程**:Java内置对多线程的支持,通过Thread类或Runnable接口实现并发执行。同步机制如synchronized关键字、wait/notify机制防止并发问题。 11. **网络编程**:Java提供了丰富的网络编程API,如Socket和...

    Java基础入门.rar

    "Java基础入门"这个压缩包提供了学习Java编程的基础资源,包括经典的入门书籍源码和JDK11的中文版API文档。 首先,让我们来了解一下Java API。API(Application Programming Interface)是一系列预先定义的函数,...

    java学习从入门到精通教程(pdf)

    9. **多线程**:Java支持多线程编程,讲解Thread类、Runnable接口以及线程同步机制,如synchronized关键字和wait/notify机制。 10. **异常处理**:了解Java的异常处理机制,包括try-catch-finally语句块和自定义...

    Java基础入门源代码

    7. **多线程**:Java内置对多线程的支持,通过Thread类和Runnable接口可以创建并运行多个线程。多线程在处理并发任务和提高程序效率方面非常重要。 8. **Java Swing和JavaFX**:这两者是Java的图形用户界面(GUI)...

    《Java 基础入门(传智播客)》_高清中文版pdf 网盘链接

    根据提供的文件信息,我们可以推断出这是一本关于Java编程语言的基础入门教程,由传智播客出版。虽然具体的PDF内容未给出,但从标题、描述和部分可见内容来看,本书主要面向初学者,旨在帮助他们掌握Java编程的基本...

    JAVA轻松入门 源代码

    【JAVA轻松入门 源代码】是一个针对初学者的编程学习资源,主要涵盖了Java语言的基础概念和实践操作。这个压缩包包含的是与课程配套的实验源代码,通过这些代码,学习者可以直观地理解Java编程的基本语法和常用结构...

    Java编程入门.pdf

    - **多线程**:Java支持并发编程,通过Thread类和Runnable接口实现多线程。 - **网络编程**:利用Socket和ServerSocket类进行网络通信。 - **JDBC**:Java Database Connectivity,用于Java应用程序与各种数据库之间...

    java基础 Java开发入门到精通PPT课件 共353页.ppt

    Java的基础知识包括语法基础、面向对象编程以及高级编程接口,这些内容构成了Java开发入门到精通的核心。 **Java语法基础**是学习Java的第一步,涵盖了变量、数据类型、运算符、控制流(如if语句、for循环、while...

    Java基础入门教程

    Java基础入门教程是一份非常适合初学者的教育资源,它引导学习者逐步掌握Java编程语言的核心概念。这份教程可能包含了以下几个关键的知识点: 1. **认识Java**:这部分通常会介绍Java的历史,由Sun Microsystems...

    JAVA语言入门 电子书

    Java提供了丰富的线程API,如Thread类、Runnable接口以及synchronized关键字,帮助开发者实现高效的并发程序。 《JAVA语言入门》.chm文件将涵盖以上所有内容,并可能包含更多的实例、练习和解答,帮助读者从零开始...

    java线程入门级书籍

    ### Java线程入门知识点详解 #### 一、Java线程基础知识概述 **1.1 什么是线程?** 线程是程序执行流的最小单元,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。在Java中...

    java 入门入门入门入门入门入门入门入门

    "Java 入门入门入门入门入门入门入门入门"这个标题暗示了我们将要探讨的是针对初学者的基础知识,包括如何开始学习Java,理解其基本概念,以及如何编写简单的程序。 Java的基础知识点主要包括以下几个部分: 1. **...

    java从入门到精通第四版课本实例源程序

    5. **多线程**:Java内置了对多线程的支持,"sl"中的代码可能包含线程的创建与同步控制,如Thread类的使用、Runnable接口的实现、synchronized关键字的应用,以及线程间的通信(wait、notify、notifyAll)。...

    java2入门经典源代码

    7. **多线程编程**:Java支持并发编程,Chap17可能包含了关于线程的创建、同步和互斥的知识,如Thread类的使用、synchronized关键字、wait()、notify()和notifyAll()方法。 通过这个压缩包中的源代码,读者不仅可以...

    Java从入门到项目实战【配套资源】自测题目.rar

    本资源"Java从入门到项目实战【配套资源】自测题目.rar"为Java初学者和开发者提供了一套完整的自测题目,旨在巩固基础,提升实践能力,并为面试准备提供便利。以下是基于该资源涵盖的知识点的详细讲解: 1. **基础...

    Java2入门经典8

    根据给定的信息,“Java2入门经典8”似乎是一本关于Java 2编程语言的基础学习资料。虽然提供的具体内容没有实质性的技术细节,但从标题和描述中我们可以推断出这本书旨在为初学者提供Java 2的基本概念和技术指导。...

    java基础课件 java入门课件

    这份"java基础课件"是专为初学者设计的,旨在帮助那些对Java编程不太熟悉的人快速入门并掌握基本概念。 首先,Java的基础知识包括语法结构。在Java中,程序由类(class)组成,类是具有特定属性和行为的对象模板。...

    java从入门到精通(第三版)光盘实例

    《Java从入门到精通》(第三版)是一本旨在帮助初学者和有一定基础的程序员深入理解Java编程语言的书籍。光盘实例作为该书的重要补充,提供了丰富的代码示例和实际应用,帮助读者巩固理论知识并提升实战技能。在这些...

    Java基础入门编程详解

    14. **多线程**:Java内置对多线程的支持,可以创建Thread类的实例或实现Runnable接口来并发执行任务,提高程序效率。 15. **Java标准库**:Java API包含了大量的预定义类和接口,如Math、String、Date等,熟练使用...

    JAVA语言入门

    本“JAVA语言入门”教程是为初学者精心编写的,旨在帮助他们快速掌握Java编程的基础知识。CHM(Compiled HTML Help)格式的文件是一种常见的电子文档形式,易于阅读和检索,适合学习和参考。 教程可能涵盖以下几个...

Global site tag (gtag.js) - Google Analytics