Java SE 10_Multi-threading
来源:互联网 发布:mac php集成开发环境 编辑:程序博客网 时间:2024/06/05 16:20
Java SE 10th day:Multi-threading
——线程操作案例
——生产者和消费者
1、本次课程知识点
1、掌握多线程的两种实现手段
2、了解多线程的基本操作方法
3、理解同步及死锁的概念
2、具体内容
2.1 进行与线程
从计算机操作系统的发展来看,经历了这样的两个阶段。
● 单进程处理:最传统的DOS系统中只要有病毒出现,则立刻有反映,因为在DOS系统中属于单线程处理,即:在同一个时间段上只能有一个程序在执行。
● 多线程处理:windows操作系统是一个多进程,例如,假设在windows中出现病毒了,则系统照样可以使用。
那么对于资源来讲,所有的IO设备、CPU等等都只有一个,那么对于多线程的处理来讲,在同一个时间段上会有多个程序运行,但是在同一个时间点只能有一个程序运行。
线程是在进程的基础上的进一步划分,举一个不恰当的例子来说,word中的拼写检查,是在word整个程序运行中运行的。
所以,如果进程消失了,则线程就消失,而如果线程消失的话,则进程依然会执行,未必会消失。
Java本身是属于多线程的操作语言,所以提供了线程的处理机制。
2.2 线程实现的两种方式(理解)
在java中有两种方式实现多线程操作,一种是继承Thread类,另外一种是实现Runnable接口。
2.2.1 Thread
Thread类是在java.lang包中定义的。
一个类只要继承了Thread类,同时覆写了本类中的run()方法,则就可以实现多线程的操作了。
package org.threaddemo;
public class MyThread extends Thread {
private String name; // 定义name属性
publicMyThread(String name) {
this.name = name;
}
public void run() { // 覆写run()方法
for (int i = 0; i < 10; i++) {
System.out.println("Thread运行:" +name + ",i = " + i);
}
}
}
以上的类已经完成了多线程的操作类,那么下面就启动线程。
package org.threaddemo;
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
mt1.run(); // 调用线程体
mt2.run(); // 调用线程体
}
}
Thread运行:线程A,i = 0
Thread运行:线程A,i = 1
Thread运行:线程A,i = 2
Thread运行:线程A,i = 3
Thread运行:线程A,i = 4
Thread运行:线程A,i = 5
Thread运行:线程A,i = 6
Thread运行:线程A,i = 7
Thread运行:线程A,i = 8
Thread运行:线程A,i = 9
Thread运行:线程B,i = 0
Thread运行:线程B,i = 1
Thread运行:线程B,i = 2
Thread运行:线程B,i = 3
Thread运行:线程B,i = 4
Thread运行:线程B,i = 5
Thread运行:线程B,i = 6
Thread运行:线程B,i = 7
Thread运行:线程B,i = 8
Thread运行:线程B,i = 9
但是,此时的执行可以发现非常有规律,先执行玩第一个对象,再执行第二个对象,也就是说并没有实现交互的运行。
从JDK的文档中可以发现,一旦调用start()方法,则会通过JVM找run()方法
● public void start()
使用start()方法启动线程看一下
package org.threaddemo;
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
mt1.start(); // 调用线程体
mt2.start(); // 调用线程体
}
}
Thread运行:线程A,i = 0
Thread运行:线程A,i = 1
Thread运行:线程A,i = 2
Thread运行:线程A,i = 3
Thread运行:线程B,i = 0
Thread运行:线程B,i = 1
Thread运行:线程B,i = 2
Thread运行:线程B,i = 3
Thread运行:线程B,i = 4
Thread运行:线程B,i = 5
Thread运行:线程B,i = 6
Thread运行:线程B,i = 7
Thread运行:线程B,i = 8
Thread运行:线程B,i = 9
Thread运行:线程A,i = 4
Thread运行:线程A,i = 5
Thread运行:线程A,i = 6
Thread运行:线程A,i = 7
Thread运行:线程A,i = 8
Thread运行:线程A,i = 9
此时,程序已经可以正常的进行交互式的运行了。
但是,需要思考的是,为什么非要使用start()方法启动多线程呢?
在JDK的安装路径下,src.zip是全部的java源程序,通过此代码找到Thread类中的start()方法的定义:
--jdk1.7.0_03版本(参考):
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
jdk1.5.0_04版本:
public synchronized void start(){ //定义的start()方法
if(started) //判断线程是否已经启动
throw new IllegalThreadStateException();
started = true; //如果没有启动项修改状态
start0(); //调用start0()方法
}
private native void start0(); //使用native关键字声明的方法,没有方法体
可以发现在start()方法上现在抛出了一个“IllegalThreadStateException”异常,之所以现在在此方法之中没有处理此异常,肯定是因为这个异常是RuntimeException的子类,观察此类的继承结构:
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
java.lang.IllegalArgumentException
java.lang.IllegalThreadStateException
这个异常主要是发生在线程重复启动的情况下,所以每一个线程对象只能启动一次。
操作系统有很多,Windows、Linux、UNIX,既然多线程操作中要进行CPU资源的抢占,也就是说要等待CPU调度,那么这些调度的操作是由各个操作系统的底层实现的,所以在java程序中根本就没法实现,那么此时java的设计者定义了native关键字,使用此关键字表示可以调度操作系统的底层函数,那么这样的技术又称为JNI技术(Java Native Interface)。
而且,此方法在执行的时候将调度run()方法完成,由系统默认调度的。
而JNT的开发技术,指的是使用Java程序调度操作系统的底层函数,但是对于这种操作一般不建议使用,也基本上不会再开发之中出现,因为Java的最大特点是可移植性,如果真的这样开发的话,那么就意味着此特性将彻底丧失。
之所以会在start0()方法上使用此关键字,主要是表示这个方法将交给JVM实现,对于线程的资源分配一定要有CPU等资源的支持,而这些资源要被操作系统所调度,所以才会在不同的操作系统上植入不同的JVM,这样的话就可以由JVM去适应各个操作系统的资源调配,因此此时的start0()实际上九表示交给JVM进行处理了。
以后一定要记住:只要是线程的启动就一定要由Thread类中的start()方法完成。
但是,第一种操作中有一个最大的限制,一个类只能继承一个父类。
2.2.2 Runnable接口
在实际的开发中一个多线程的操作类很少去使用Thread类完成,而是通过Runnable接口完成。
观察Runnable接口的定义。
public interface Runnable
public void run(){
}
所以,一个类只要实现了此接口,并覆写run()方法。
package org.runnabledemo;
public class MyThread implements Runnable {// 实现Runnable接口
private String name; // 定义name属性
public MyThread(String name) {
this.name = name;
}
public void run() { // 覆写run()方法
for (int i = 0; i < 10; i++) {
System.out.println("Thread运行:" +name + ",i = " + i);
}
}
}
完成之后,下面继续启动多线程。
但是在现在使用Runnable定义的子类中并没有start()方法,而只有Thread类中才有。
在Thread类中存在以下的一个构造方法:
public Thread(Runnable target)
此构造方法接收Runnable的子类实例。也就是说现在可以通过Thread类来启动Runnable实现的多线程。
package org.runnabledemo;
public class RunnableDemo01 {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
new Thread(mt1).start();// 调用线程体
new Thread(mt2).start();// 调用线程体
}
}
以上的操作代码也属于交替的运行,所以此时程序也同样实现了多线程的操作。
2.2.3 两种实现方式的区别及联系
在程序的开发中只要是多线程则肯定永远以实现Runnable接口为正统操作,因为实现Runnable接口相比继承Thread类有如下的好处:
● 避免单继承的局限,一个类可以同时实现多个接口。
● 适合于资源的共享(重要)
为了确定Runable和Thread类的关系,观察Thread类的定义结构:
public class Thread extends Object implements Runnable
可以发现Thread类本身就是Runable接口的子类,那么如果这样的话,那么上一程序的关系如下:
这样的设计思路非常类似于“代理模式”但是又不是代理,因为如果是代理的话,则代理所调用的方法依然是run()方法,而此处却是start()方法。
范例:以卖票的程序为例:
package org.differencedemo;
public class MyThread extends Thread {// 继承Thread类
private int ticket = 5; // 一共才5张票
public void run() { // 覆写run()方法
for (int i = 0; i < 50; i++) {
if (this.ticket > 0) {
System.out.println("卖票:ticket = " +this.ticket--);
}
}
}
}
下面建立三个线程,同时卖票。
package org.differencedemo;
public class ThreadTicket {
public static void main(String args[]) {
MyThread mt1 = new MyThread();// 一个线程
MyThread mt2 = new MyThread();// 一个线程
MyThread mt3 = new MyThread();// 一个线程
mt1.start(); // 开始卖票
mt2.start(); // 开始卖票
mt3.start(); // 开始卖票
}
}
发现现在一共卖了15张票,但是实际上只有5张票,所以证明每一个线程都卖自己的票,没有达到资源共享的目的。
那么,如果现在实现的是Runnable接口的话,则就可以实现资源的共享:
package org.differencedemo02;
public class MyThread implements Runnable {// 实现Runnable接口
private int ticket = 5; // 一共才5张票
public void run() { // 覆写run()方法
for (int i = 0; i < 50; i++) {
if (this.ticket > 0) {
System.out.println("卖票:ticket = " +this.ticket--);
}
}
}
}
编写多个线程进行卖票的操作
package org.differencedemo02;
public class ThreadTicket {
public static void main(String args[]) {
MyThread mt = new MyThread();// 一个线程
new Thread(mt).start();// 开始卖票
new Thread(mt).start();// 开始卖票
new Thread(mt).start();// 开始卖票
}
}
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 3
虽然现在程序中有三个线程,但是三个线程一共才卖出了5张票。也就说明使用Runnable实现的多线程可以到达资源共享的目的。
可是如上程序如果直接继承了Thread类,如下所示,效果也一样,因为Thread类也是Runable接口子类。
……
class MyThread extends Thread {
……
但是这种概念只是相对的,因为即使本程序继承Thread类功能实现效果也是完全一样的,但是有一点不合适,因为一旦MyThread类继承了Thread类之后就已经具备start()方法,那么继续依靠其他类的start()方法操作救你不太合适了。
面试题:请解释多线程的两种实现方式及区别?
1) 多线程的实现可以通过继承Thread类和Runable接口来实现,不管使用何种方式都要覆写run()方法;
2) 继承Thread类与实现Runable接口相比,会有继承的局限;
3) Thread类也是Runable接口的子类,不管通过何种方式实现的多线程,最终都要依靠Thread类的start()方法启动;
4) 使用Runable接口可以更加清楚的表示数据共享的概念。
手记:在实现多个线程进行资源共享时,不要添加Thread.sleep()方法,否则也会导致票数超过5张(多出一张,根据设置的票数不同而不同)的问题。因为如果让进程休眠,从控制台输出可知,是多个进程(由定义的进程数决定)一齐蹦出来的,所以会导致问题的产生,时而正常,时而不正常。
实际测试代码如下:
package course_2;
class MyThread implements Runnable {
private int num = 5;
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(500); // 每个休眠500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num > 0) {
System.out.println(Thread.currentThread().getName() +
"运行-->"+num--);
}
}
}
}
public class Demo {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt,"线程A");
Thread t2 = new Thread(mt,"线程B");
t1.start();
t2.start();
}
}
线程A运行-->5
线程B运行-->5
线程A运行-->4
线程B运行-->3
线程B运行-->1
线程A运行-->2
手记:经过多次不断测试,即使没有Thread.sleep()也会出错。所以此时需要使用同步操作!
实际上Runnable接口和Thread类之间还是存在联系的:
public class Thread extends Object implements Runnable
发现Thread类也是Runnable接口的子类。
2.3 线程的操作方法
对于线程来讲所有的操作方法都是在Thread类中定义的,所以如果想要明确的理解操作方法,则肯定要从Thread类着手。
2.3.1 设置和取得名字
在Thread类中存在以下的几个方法可以设置和取得名字:
● 设置名字:
|- set : public final void setName(String name) →fina表示子类不可覆写
|- 构造:
|- public Thread(Runnable target,String name)
|- public Thread(String name)
● 取得名字:
|- public final String getName()
在线程的操作中因为其操作的不确定性,所以提供了一个方法,可以取得当前的操作线程:
● publicstatic Thread currentThread()
对于线程的名字一般是在启动前进行设置,最好不要设置相同的名字,最好也不要为一个线程改名字。
package org.namedemo;
public class MyThread implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() +"线程正在运行");
}
}
}
那么此时测试一下,上面取得的线程的名字。
package org.namedemo;
public class ThreadNameDemo01 {
public static void main(String[] args) {
MyThread mt = new MyThread();// Runnable子类实例
Thread thread1 = new Thread(mt,"线程A");
Thread thread2 = new Thread(mt,"线程B");
Thread thread3 = new Thread(mt,"线程C");
thread1.start();
thread2.start();
thread3.start();
}
}
线程B线程正在运行
线程B线程正在运行
……
线程C线程正在运行
线程C线程正在运行
……
线程A线程正在运行
线程A线程正在运行
……
明白代码的作用之后,在来看如下代码:
package org.namedemo;
public class ThreadNameDemo02 {
public static void main(String[] args) {
MyThread mt = new MyThread();// Runnable子类实例
new Thread(mt, "自定义线程").start();
mt.run(); // 直接通过对象调用
}
}
main线程正在运行
main线程正在运行
自定义线程线程正在运行
main线程正在运行
……
得出一个结论:在程序运行时主方法实际上就是一个主线程。
一直强调java是多线程的操作语言,那么它是怎么实现多线程的呢?
实际上对于java来讲,每一次执行java命令对于操作系统来讲都将启动一个JVM的进程,那么主方法实际上只是这个进程上的进一步划分。
问题:在java执行中一个java程序至少启动几个线程?
两个:main、gc(垃圾收集线程)
2.3.2 线程的休眠(重要)
让一个线程稍微休息一下,之后起来继续工作,称为休眠:
● publicstatic void sleep(long millis) throwsInterruptedException
在使用此方法时需要进行try……catch处理
package org.sleepdemo;
public class MyThread implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500); // 每个休眠500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"线程正在运行");
}
}
}
主程序:
package org.sleepdemo;
public class ThreadSleepDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt, "线程A").start();
new Thread(mt, "线程B").start();
}
}
程序的执行将变得缓慢起来。
2.3.3 线程的中断
在sleep()方法中存在InterruptedException,那么会造成此异常的方法就是中断:
●public void interrupt()
一个线程需要被另外一个线程中断。
package org.interruptdemo;
class MyThread implements Runnable {
public void run() {
System.out.println("1、进入run()方法体");
try {
System.out.println("2、线程休眠20秒钟");
Thread.sleep(20000); // 休眠20秒
System.out.println("3、线程正常休眠20秒钟");
} catch (InterruptedException e) {
System.out.println("4、线程休眠被中断");
return; // 返回方法调用处
}
System.out.println("5、正常结束run()方法体");
}
}
public class InterruptDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread thread = new Thread(mt,"线程A");
thread.start(); // 启动线程
try {
Thread.sleep(2000); // 保证程序至少执行2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt(); // 中断
}
}
1、进入run()方法体
2、线程休眠20秒钟
4、线程休眠被中断
2.3.4 设置线程的优先级
那么在多线程操作中,所有的代码实际上都是存在优先级的,优先级高的就有可能先执行。在线程中使用以下的方法设置:public final voidsetPriority(int newPriority)
对于优先级来说在线程中有如下三种:
●最低:public static final int MIN_PRIORITY = 1
●中等:public static final int NORM_PRIORITY = 5
●最高:public static final int MAX_PRIORITY = 10
package org.prioritydemo;
class MyThread implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500); // 延迟操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"线程正在运行");
}
}
}
public class PriorityDemo01 {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt,"线程A");
Thread t2 = new Thread(mt,"线程B");
Thread t3 = new Thread(mt,"线程C");
t1.setPriority(Thread.NORM_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
各个线程可以设置优先级,那么主方法的优先级是什么呢?
package org.prioritydemo;
public class PriorityDemo02 {
public static void main(String args[]) {
System.out.println(Thread.currentThread().getPriority());
System.out.println("Thread.MAX_PRIORITY = " + Thread.MAX_PRIORITY);
System.out.println("Thread.NORM_PRIORITY = " + Thread.NORM_PRIORITY);
System.out.println("Thread.MIN_PRIORITY = " + Thread.MIN_PRIORITY);
}
}
5
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
Thread.MIN_PRIORITY = 1
从输出结果可以发现,主方法的优先级是普通的优先级。
2.4 同步与死锁(理解)
2.4.1 问题的引出
现在有如下的操作代码:
package org.syndemo;
class MyTicketThread implements Runnable { // 实现Runnable接口
private int ticket = 5; // 一共才5张票
public void run() { // 覆写run()方法
for (int i = 0; i < 50; i++) {
if (this.ticket > 0) {
try {
Thread.sleep(300); // 延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " +this.ticket--);
}
}
}
}
public class SynDemo01 {
public static void main(String args[]) {
MyTicketThread mt = new MyTicketThread();// 一个线程
new Thread(mt, "票贩子-A").start(); // 开始卖票
new Thread(mt, "票贩子-B").start(); // 开始卖票
new Thread(mt, "票贩子-C").start(); // 开始卖票
}
}
卖票:ticket = 5
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 0
卖票:ticket = -1
造成此类问题的根本原因在于,判断剩余票数和修改票数之间加入了延迟操作。
2.2.2 同步解决
在java中可以通过同步代码的方式进行代码的加锁操作,同步的实现有两种方式:
● 同步代码块
● 同步方法
同步代码块:
● 使用synchronized关键字进行同步代码块的声明,但是在使用此操作时必须明确的指出到底要锁定的是哪个对象,一般都是以当前对象为主:
synchronized(对象){ //一般都是将this进行锁定
需要同步的代码;
}
使用同步代码块修改之前的程序:
package org.syndemo;
class MyTicketThread implements Runnable { // 实现Runnable接口
private int ticket = 5; // 一共才5张票
public void run() { // 覆写run()方法
for (int i = 0; i < 50; i++) {
synchronized (this) {// 形成同步代码块
if (this.ticket > 0) {
try {
Thread.sleep(300); //延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " +this.ticket--);
}
}
}
}
}
public class SynDemo01 {
public static void main(String args[]) {
MyTicketThread mt = new MyTicketThread();// 一个线程
new Thread(mt, "票贩子-A").start(); // 开始卖票
new Thread(mt, "票贩子-B").start(); // 开始卖票
new Thread(mt, "票贩子-C").start(); // 开始卖票
}
}
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
此时,确实解决同步的问题,但是在java中也可以通过同步方法解决这样的问题。
同步方法定义格式:
synchronized 方法返回值方法名称(参数列表){}
范例:通过同步方法实现卖票程序
package org.syndemo;
class MyTicketThread implements Runnable { // 实现Runnable接口
private int ticket = 5; // 一共才5张票
public void run() { // 覆写run()方法
for (int i = 0; i < 50; i++) {
this.sale(); // 调用同步方法
}
}
public synchronized void sale() { // 增加同步方法
if (this.ticket > 0) {
try {
Thread.sleep(300); // 延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " +this.ticket--);
}
}
}
public class SynDemo01 {
public static void main(String args[]) {
MyTicketThread mt = new MyTicketThread();// 一个线程
new Thread(mt, "票贩子-A").start(); // 开始卖票
new Thread(mt, "票贩子-B").start(); // 开始卖票
new Thread(mt, "票贩子-C").start(); // 开始卖票
}
}
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
此时,实际上就可以给出java中方法定义的完整格式了:
[访问权限(public | protected | default |private)]
[static] [final] [abstract] [synchronized] [native]
返回值类型 | void | ……
方法名称(参数列表)[throws 异常1,异常2,……]{}
发现同步可以保证资源的完整性,但是过多的同步也会出现问题。
2.4.3 死锁
在程序中过多的同步会产生死锁的问题,那么死锁
package org.syndemo;
class FanBo {
public synchronized void say(ZhangYang zy) {
System.out.println("把钱给我,放了你弟弟。");
zy.give();
}
public synchronized void give() {
System.out.println("得到了钱,同时被警察抓了。");
}
}
class ZhangYang {
public synchronized void say(FanBo fb) {
System.out.println("把弟弟放了,我给你钱。");
fb.give();
}
public synchronized void give() {
System.out.println("弟弟救回来,同时报案了。");
}
}
public class DeadLockDemo implements Runnable {
private FanBo fb = new FanBo();
private ZhangYang zy = new ZhangYang();
public DeadLockDemo() {
new Thread(this).start();
fb.say(zy);
}
public void run() {
zy.say(fb);
}
public static void main(String[] args) {
new DeadLockDemo();
}
}
把钱给我,放了你弟弟。
把弟弟放了,我给你钱。
在本块中只需要记住一个概念即可:
面试题:多个线程操作同一资源的时候需要注意什么?会带来哪些问题?
多个线程共享同一个资源的时候需要进行同步,但是过多的同步会造成死锁。
2.5 生产者-消费者(理解)
在多线程中有一个最经典的操作案例—— 生产者和消费者,生产者不断生产内容,但是消费者不断取出内容。
2.5.1 基本实现
现在假设生产的内容都保存在Info类之中,则在生产者中要有Info类的引用,而消费者中也要存在Info类的引用。
生产者应该不断的上产信息,消费者不断的取出,所以实现多线程的操作:
1、保存信息的类——Info.java:
package org.pcdemo01;
public class Info {
private String title = "李兴华";
// private String title = "MLDN";
private String content = "Java讲师";
// private String content = "www.mldnjava.cn";
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
2、生产者——Producer.java
package org.pcdemo01;
public class Producer implements Runnable {
private Info info = null;
public Producer(Info info) {
this.info = info;
}
public void run() {
for (int x = 0; x < 100; x++) {// 不断的生产
if (x % 2 == 1) {//是奇数
this.info.setTitle("MLDN");
try {
Thread.sleep(300); // 休眠0.3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
this.info.setContent("www.mldnjava.cn");
} else {
this.info.setTitle("李兴华");
try {
Thread.sleep(300); // 休眠0.3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
this.info.setContent("Java讲师");
}
}
}
}
3、消费者——Consumer.java
package org.pcdemo01;
public class Consumer implements Runnable {
private Info info = null;
public Consumer(Info info) {
this.info = info;
}
public void run() {
for (int x = 0; x < 100; x++) {
try {
Thread.sleep(300); // 休眠0.3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.info.getTitle() +" -> "
+ this.info.getContent());// 取内容
}
}
}
4、测试类——TestInfoDemo01.java
package org.pcdemo01;
public class TestInfoDemo01 {
public static void main(String[] args) {
Info info = new Info();
Producer pro = new Producer(info);// 实例化生产者对象
Consumer con = new Consumer(info);// 实例化消费者对象
new Thread(pro).start();// 启动线程
new Thread(con).start();// 启动线程
}
}
MLDN -> Java讲师
MLDN -> Java讲师
MLDN -> Java讲师
李兴华 -> www.mldnjava.cn
MLDN -> Java讲师
李兴华 -> www.mldnjava.cn
MLDN -> Java讲师
李兴华 -> www.mldnjava.cn
.
.
.
.
从代码中可以发现以下几点问题:
● 生产的内容有可能出现不一致的情况
● 出现了重复取值和重复设置的问题
2.5.2 加入同步
为了保证程序操作数据的完整性,此时,最好加入同步操作,可以直接在Info类中定义一个同步方法。专门完成设置和取得内容。
此时可以保证数据的一致性,但是依然存在重复取和重复设置的问题,那么该如何去掉这些问题呢?
1、保存信息的类——Info.java:
package org.pcdemo02;
public class Info {
private String title = "李兴华";
// private String title = "MLDN";
private String content = "Java讲师";
// private String content = "www.mldnjava.cn";
public synchronized void set(String title, String content) {
this.setTitle(title);
try {
Thread.sleep(300); // 休眠0.3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setContent(content);
}
public synchronized void get() {
System.out.println(this.title +" --> " + this.content);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
2、生产者——Producer.java
package org.pcdemo02;
public class Producer implements Runnable {
private Info info = null;
public Producer(Info info) {
this.info = info;
}
public void run() {
for (int x = 0; x < 100; x++) {// 不断的生产
if (x % 2 == 1) {//是奇数
this.info.set("MLDN","www.mldnjava.cn");
} else {
this.info.set("李兴华","Java讲师");
}
}
}
}
3、消费者——Consumer.java
package org.pcdemo02;
public class Consumer implements Runnable {
private Info info = null;
public Consumer(Info info) {
this.info = info;
}
public void run() {
for (int x = 0; x < 100; x++) {
this.info.get();
}
}
}
4、测试类——TestInfoDemo01.java
package org.pcdemo02;
public class TestInfoDemo02 {
public static void main(String[] args) {
Info info = new Info();
Producer pro = new Producer(info);// 实例化生产者对象
Consumer con = new Consumer(info);// 实例化消费者对象
new Thread(pro).start();// 启动线程
new Thread(con).start();// 启动线程
}
}
2.5.3 Object对线程的支持
现在就利用Object类中的以上方法来解决线程的等待和唤醒操作。修改Info类和Producer类即可。
Info.java:
package org.pcdemo03;
public class Info {
private String title = "李兴华";
// private String title = "MLDN";
private String content = "Java讲师";
// private String content = "www.mldnjava.cn";
private boolean flag = false;// 默认是false
/*
* 1、flag = true,表示可以生产,但是不能取走
*
* 2、flag = false,表示可以取走,但是不能生产
*/
public synchronized void set(String title, String content) {
if (flag ==false) {//已经生产过了,需要等待
try {
super.wait();// 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setTitle(title);
try {
Thread.sleep(300); // 休眠0.3秒//这个休眠是必须的吗?
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setContent(content);
this.flag =false;//表示不能生产了
super.notify(); // 唤醒其他等待的线程
}
public synchronized void get() {
if (flag ==true) {//表示不能取
try {
super.wait();// 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.title +" --> " + this.content);
this.flag =true;false//表示不能取走了
super.notify(); // 唤醒
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
Producer.java:
package org.pcdemo03;
public class Producer implements Runnable {
private Info info = null;
public Producer(Info info) {
this.info = info;
}
public void run() {
for (int x = 0; x < 100; x++) {// 不断的生产
if (x % 2 == 0) {//是奇数
this.info.set("MLDN","www.mldnjava.cn");
} else {
this.info.set("李兴华","Java讲师");
}
}
}
}
面试题:请解释sleep()和wait()方法的区别?
sleep()是Thread类中定义的方法,表示让线程休眠,时间一到自动唤醒;
wait()是Object类中定义的方法,表示让线程等待,需要使用notify()唤醒。
2.6 线程的生命周期
范例:停止线程运行
package MutilThread;
class MyThread implements Runnable {
private boolean flag = true;// 定义标志位
public void run() {
int i = 0;
while (this.flag) {
System.out.println(Thread.currentThread().getName() +"运行,i = "
+ (i++));
}
}
public void stop() {
this.flag =false; // 修改标志位
}
}
public class StopDemo {
public static void main(String args[]) {
MyThread my = new MyThread();
Thread t = new Thread(my,"线程"); // 建立线程对象
t.start(); // 启动线程
//让主线程睡眠30毫秒,以便让线程t可以运行30毫秒后才停止
try {
Thread.sleep(30);
} catch (Exception e) {
}
my.stop(); // 修改标志位,停止运行
}
}
3、总结
1、线程与进程的区别
2、掌握线程的两种实现手段及区别,重点
3、可以清楚的阐述出同步及死锁的概念即可
- Java SE 10_Multi-threading
- java se 10
- java-SE-10
- java SE 教程 10
- java SE复习笔记10
- JAVA SE — Day 10
- java se
- Java SE
- java se
- java se
- java se
- JAVA SE
- JAVA SE
- Advanced Java: Multi-threading
- Java Multi-Threading
- Java SE 6 update 10 Beta 简介
- Java SE 学习笔记 Lesson 10
- 圣思园——Java SE Lesson 10
- 548 - Tree
- JavaSE 7th day —— Package &Access Permissions
- Java SE 8th day Eclipse
- Java SE 09_Generics
- 字符串之统计元音
- Java SE 10_Multi-threading
- Java SE 11_Frequently-used Class Library(1)
- Codeforces Round #222 (Div. 2) ABCD
- C++中的'&'和const的使用注意事项!
- Java SE 12_Frequently-used Class Library(2)
- Java SE 13_Regular Expression
- Java SE 14th IO (1)
- Java SE 15th IO (2)
- 设置Bundle display name 与 Bundle name 的区别是什么?