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. **多线程**:Java内置对多线程的支持,通过Thread类或Runnable接口实现并发执行。同步机制如synchronized关键字、wait/notify机制防止并发问题。 11. **网络编程**:Java提供了丰富的网络编程API,如Socket和...
"Java基础入门"这个压缩包提供了学习Java编程的基础资源,包括经典的入门书籍源码和JDK11的中文版API文档。 首先,让我们来了解一下Java API。API(Application Programming Interface)是一系列预先定义的函数,...
9. **多线程**:Java支持多线程编程,讲解Thread类、Runnable接口以及线程同步机制,如synchronized关键字和wait/notify机制。 10. **异常处理**:了解Java的异常处理机制,包括try-catch-finally语句块和自定义...
7. **多线程**:Java内置对多线程的支持,通过Thread类和Runnable接口可以创建并运行多个线程。多线程在处理并发任务和提高程序效率方面非常重要。 8. **Java Swing和JavaFX**:这两者是Java的图形用户界面(GUI)...
根据提供的文件信息,我们可以推断出这是一本关于Java编程语言的基础入门教程,由传智播客出版。虽然具体的PDF内容未给出,但从标题、描述和部分可见内容来看,本书主要面向初学者,旨在帮助他们掌握Java编程的基本...
【JAVA轻松入门 源代码】是一个针对初学者的编程学习资源,主要涵盖了Java语言的基础概念和实践操作。这个压缩包包含的是与课程配套的实验源代码,通过这些代码,学习者可以直观地理解Java编程的基本语法和常用结构...
- **多线程**:Java支持并发编程,通过Thread类和Runnable接口实现多线程。 - **网络编程**:利用Socket和ServerSocket类进行网络通信。 - **JDBC**:Java Database Connectivity,用于Java应用程序与各种数据库之间...
Java的基础知识包括语法基础、面向对象编程以及高级编程接口,这些内容构成了Java开发入门到精通的核心。 **Java语法基础**是学习Java的第一步,涵盖了变量、数据类型、运算符、控制流(如if语句、for循环、while...
Java基础入门教程是一份非常适合初学者的教育资源,它引导学习者逐步掌握Java编程语言的核心概念。这份教程可能包含了以下几个关键的知识点: 1. **认识Java**:这部分通常会介绍Java的历史,由Sun Microsystems...
Java提供了丰富的线程API,如Thread类、Runnable接口以及synchronized关键字,帮助开发者实现高效的并发程序。 《JAVA语言入门》.chm文件将涵盖以上所有内容,并可能包含更多的实例、练习和解答,帮助读者从零开始...
### Java线程入门知识点详解 #### 一、Java线程基础知识概述 **1.1 什么是线程?** 线程是程序执行流的最小单元,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。在Java中...
"Java 入门入门入门入门入门入门入门入门"这个标题暗示了我们将要探讨的是针对初学者的基础知识,包括如何开始学习Java,理解其基本概念,以及如何编写简单的程序。 Java的基础知识点主要包括以下几个部分: 1. **...
5. **多线程**:Java内置了对多线程的支持,"sl"中的代码可能包含线程的创建与同步控制,如Thread类的使用、Runnable接口的实现、synchronized关键字的应用,以及线程间的通信(wait、notify、notifyAll)。...
7. **多线程编程**:Java支持并发编程,Chap17可能包含了关于线程的创建、同步和互斥的知识,如Thread类的使用、synchronized关键字、wait()、notify()和notifyAll()方法。 通过这个压缩包中的源代码,读者不仅可以...
本资源"Java从入门到项目实战【配套资源】自测题目.rar"为Java初学者和开发者提供了一套完整的自测题目,旨在巩固基础,提升实践能力,并为面试准备提供便利。以下是基于该资源涵盖的知识点的详细讲解: 1. **基础...
根据给定的信息,“Java2入门经典8”似乎是一本关于Java 2编程语言的基础学习资料。虽然提供的具体内容没有实质性的技术细节,但从标题和描述中我们可以推断出这本书旨在为初学者提供Java 2的基本概念和技术指导。...
这份"java基础课件"是专为初学者设计的,旨在帮助那些对Java编程不太熟悉的人快速入门并掌握基本概念。 首先,Java的基础知识包括语法结构。在Java中,程序由类(class)组成,类是具有特定属性和行为的对象模板。...
《Java从入门到精通》(第三版)是一本旨在帮助初学者和有一定基础的程序员深入理解Java编程语言的书籍。光盘实例作为该书的重要补充,提供了丰富的代码示例和实际应用,帮助读者巩固理论知识并提升实战技能。在这些...
14. **多线程**:Java内置对多线程的支持,可以创建Thread类的实例或实现Runnable接口来并发执行任务,提高程序效率。 15. **Java标准库**:Java API包含了大量的预定义类和接口,如Math、String、Date等,熟练使用...
本“JAVA语言入门”教程是为初学者精心编写的,旨在帮助他们快速掌握Java编程的基础知识。CHM(Compiled HTML Help)格式的文件是一种常见的电子文档形式,易于阅读和检索,适合学习和参考。 教程可能涵盖以下几个...