黑马程序员 —— Java多线程2 (第十二天)
来源:互联网 发布:淘宝包邮拒签邮费谁出 编辑:程序博客网 时间:2024/06/05 16:17
------- android培训、java培训、期待与您交流! ----------
一 线程间通信1 - 示例代码
之前写的程序,每个线程运行的代码都是一致的。
而现在需要线程之间通信,它们代码要不一致。
A线程往资源写数据,B线程往资源读数据,同时运行。
这样就有两个run方法同时运行,也就是有两个类。
Input类对象存入数据 → 对象(String name,String Sex) → Output类对象读取数据
首先,资源要描述一下,Input和Output要描述一下。
package Test3;class Res {public String name;public String sex;}class Input implements Runnable {Res res;boolean flag = true;public Input(Res res) {this.res = res;}public void run() {while (true) {if (flag == true) {res.name = "麦克";res.sex = "男";flag = false;} else {res.name = "May";res.sex = "girl";flag = true;}}}}class Output implements Runnable {Res res;public Output(Res res) {this.res = res;}public void run() {while (true) {System.out.println(res.name + " --- " + res.sex);}}}public class ThreadTongXin {public static void main(String[] args) {Res res = new Res();new Thread(new Input(res)).start();new Thread(new Output(res)).start();}}
程序输出:
麦克 --- girl麦克 --- 男May --- girl麦克 --- 男麦克 --- girlMay --- 男
为什么会出现输出的数据混乱,而且一下子输出一大片同一个人数据的情况呢?
原因:一下子出一大片 的原因是Outpu输出完,不一定是Input获取到CPU执行权,
可以继续是Output获取到,连续都获取到了,就会出现一大片一个人的数据了。
另外,Input往Res类的对象res里刚存完name,Output就获取到CPU执行权,
然后就输出了,性别的数据也就混乱了。
二 线程间通信2 - 解决安全问题
当然是“同步”。
两个类的while(true)中加上同步代码块,发现还是不行。
此时应该想到,同步需要满足两个前提:
1.这个程序只同步了一个线程,另外一个没有同步,而它们处理的是同一资源;
2.不是同一个锁,改为内存中唯一的对象,例如:Input.class,Res.class,res都行。
class Input implements Runnable {Res res;boolean flag = true;public Input(Res res) {this.res = res;}public void run() {while (true) {synchronized(Res.class){ //注意这里if (flag == true) {res.name = "麦克";res.sex = "男";flag = false;} else {res.name = "May";res.sex = "girl";flag = true;}}}}}class Output implements Runnable {Res res;public Output(Res res) {this.res = res;}public void run() {while (true) {synchronized(Res.class){ //注意这里System.out.println(res.name + " --- " + res.sex);}}}}
为节省篇幅,仅截取Input和Ouput两个被修改类的代码。
三 线程间通信3 - 等待唤醒机制
现在需要将程序修改为:一个写入后,等另一个输出才继续写入。
为了满足需求,可以再Res类中加入标记,用于标记Input类的对象是否已写入新值。
模拟执行过程:
Input对象判断标记为false,无新值,写入并将标记改为true,Input对象再输入判断到为true,就等着。
Output对象判断,标记为true有内容,输出然后修改标记为false,唤醒Input的对象,它自己等着。
等待的线程在哪里?
线程运行时,内存中会建立线程池,等待的线程就存放在线程池中,
notify唤醒线程池中的线程,通常唤醒第一个wait的。
notify的原理,可以用“抓人游戏”做类比。
如果想唤醒线程池中全部正在wait的线程,可以用notifyAll()方法。
注意:wait()、notify()、notifyAll()是定义在Object类中的方法,
观察一下这三个方法在API文档中的描述。
注意文档中“必须拥有对象监视器”和“在此对象监视器上”的描述,
这表明,这三个方法,必须用在 synchronized“同步”当中,不然会抛异常,
这里十分重要,因为这个原因,上机浪费了一个多半小时!!!
参考: IllegalMonitorStateException 异常例子的解决方法
而且,必须标示出wait()方法所操作的线程所属的锁。
为什么?因为同步有可能会出现嵌套。
Q:为什么定义在Object类?
A:因为“锁”可以是任意对象,任意对象能调用的方法,当然是要在Object类中。
示例代码:
/*线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。 */import java.lang.*;class Res {String name;String sex;boolean flag = false;}class Input implements Runnable {private Res r;Input(Res r) {this.r = r;}public void run() {int x = 0;while (true) {synchronized (r) {if (r.flag) {try {r.wait();} catch (Exception e) {}}if (x == 0) {r.name = "mike";r.sex = "man";} else {r.name = "丽丽";r.sex = "女女女女女";}x = (x + 1) % 2;r.flag = true;r.notify();}}}}class Output implements Runnable {private Res r;Output(Res r) {this.r = r;}public void run() {while (true) {synchronized (r) {if (!r.flag) {try {r.wait();} catch (Exception e) {}}System.out.println(r.name + " " + r.sex);r.flag = false;r.notify();}}}}public class Demo {public static void main(String args[]) {Res r = new Res();Input in = new Input(r);Output out = new Output(r);Thread t1 = new Thread(in);Thread t2 = new Thread(out);t1.start();t2.start();}}
四 线程间通信4 - 代码优化
class Res {private String name;private String sex;private boolean flag = false;public synchronized void set(String name, String sex) {if (this.flag) {try {this.wait();} catch (Exception e) {}}this.name = name;this.sex = sex;this.flag = true;this.notify();}public synchronized void out() {if (!this.flag) {try {this.wait();} catch (Exception e) {}}System.out.println(this.name + " " + this.sex);this.flag = false;this.notify();}}class Input implements Runnable {private Res r;Input(Res r) {this.r = r;}public void run() {int x = 0;while (true) {if (x == 0) {r.set("mike", "man");} else {r.set("丽丽", "女女女女女");}x = (x + 1) % 2;}}}class Output implements Runnable {private Res r;Output(Res r) {this.r = r;}public void run() {while (true) {r.out();}}}public class Demo {public static void main(String args[]) {Res r = new Res();new Thread(new Input(r)).start();new Thread(new Output(r)).start();}}
加上private和getter、setter方法赋值时需要同步,因为可能出现安全问题。
五 线程间通信5 - 生产者消费者问题
上面的示例代码,输出的是姓名,不好分辨。
现在通过生产者消费者问题(“生产一个、消费一个……”)来进行说明。
通过带编号产品(新产品编号在setter方法中+1)来进行区分。
Q:单个生产单个消费没问题,那如果换为多个生产多个消费呢?
可能会出现生产一个,消费两个的情况,或者生产两个消费一个,为什么呢?
A:因为没判断标记flag,只要将标记判断的if改为while就可以了,
但会全部等待了。还要notifyAll()。
package Test3;class Resource {private String name;private int conut = 1;private boolean ResControlFlag = false;public synchronized void Set(String name) {while (true) {while (ResControlFlag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.name = name + this.conut++;System.out.println(Thread.currentThread().getName() + " --------> "+ this.name);ResControlFlag = true;this.notifyAll();}}public synchronized void Out() {while (true) {while (!ResControlFlag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + " <--- "+ this.name);ResControlFlag = false;this.notifyAll();}}}class Producer implements Runnable {private Resource res;public Producer(Resource res) {this.res = res;}public void run() {res.Set("电脑");}}class Consumer implements Runnable {private Resource res;public Consumer(Resource res) {this.res = res;}public void run() {res.Out();}}public class ProducerConsumerDemo {public static void main(String[] args) {Resource res = new Resource();new Thread(new Producer(res), "生产者").start();new Thread(new Consumer(res), "消费者").start();new Thread(new Producer(res), "生产者").start();new Thread(new Consumer(res), "消费者").start();new Thread(new Producer(res), "生产者").start();new Thread(new Consumer(res), "消费者").start();}}
Q:对于多个生产者和消费者,为什么要定义while判断标记?
A:为了让被唤醒的线程再一次判断标记。
Q:为什么要使用notifyAll?
A:因为需要唤醒对方线程,因为只用notify,
容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
六 线程间通信6 - 生产者消费者问题(升级版)
Q:这时候,问题就来了,notifyAll会唤醒在此对象监视器上等待的所有线程,
怎样才能“不唤醒自己,而只唤醒其他线程呢?”
A:JDK1.5中提供了现成的升级解决方案:将“同步synchronized
”替换成“显式锁Lock”操作,
将 object 中的 wait ,notify,notifyAll,替换成了condition 对象。
public interface Lock:
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock这个接口,把隐式的加锁,变成显式的加锁了。
public interface Condition :
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
synchronized → Lock
Condition → wait、notify 和 notifyAll
示例代码:
import java.util.concurrent.locks.*;class Demo {public static void main(String args[]) {Resource r = new Resource();Producer pro = new Producer(r);Consumer con = new Consumer(r);Thread t1 = new Thread(pro);Thread t3 = new Thread(pro);Thread t2 = new Thread(con);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();}}class Resource {private String name;private int count = 1;private boolean flag = false;private Lock lock = new ReentrantLock();private Condition condition_pro = lock.newCondition();private Condition condition_con = lock.newCondition();public void set(String name) throws InterruptedException {lock.lock();try {while (flag) {condition_pro.await();}this.name = name + "--" + count++;System.out.println(Thread.currentThread().getName() + "...生产者..."+ this.name);flag = true;condition_con.signal();} finally {lock.unlock();}}public void out() throws InterruptedException {lock.lock();try {while (!flag) {condition_con.await();}System.out.println(Thread.currentThread().getName()+ "...消费者..........." + this.name);flag = false;condition_pro.signal();} finally {lock.unlock();}}}class Producer implements Runnable {private Resource res;Producer(Resource res) {this.res = res;}public void run() {while (true) {try {res.set("+商品+");} catch (InterruptedException e) {}}}}class Consumer implements Runnable {private Resource res;Consumer(Resource res) {this.res = res;}public void run() {while (true) {try {res.out();} catch (InterruptedException e) {}}}}
更多用法和说明直接看API文档就行了,说得很清楚。
七 线程停止
查阅API文档,Thread类的stop()方法已经过时。
那我们应该如何让线程停止呢?
停止线程只有一种方法,就是让run()方法结束。
让run()方法结束的两种情况:
1.通常情况:多线程运行,运行代码通常是循环结构。只要控制循环,就可以让run方法结束,也就是线程结束。
2.特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结状态进行清除。
强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
使用interrupt()方法,将处于冻结状态的线程强制的恢复到运行状态中来。
class Demo {public static void main(String args[]) {StopThread st = new StopThread();Thread t1 = new Thread(st);Thread t2 = new Thread(st);t1.start();t2.start();int num = 0;while (true) {if (num++ == 60) {st.changeFlag();t1.interrupt();t2.interrupt();break;}System.out.println(Thread.currentThread().getName() + num);}}}class StopThread implements Runnable {private boolean flag = true;public void run() {while (flag) {System.out.println(Thread.currentThread().getName() + "....run");}}public void changeFlag() {flag = false;}}
八 守护线程
守护线程:可以理解为“后台线程”,把线程标记为“后台线程”后,
它开启、运行等等状态跟“前台线程”都没有区别,就结束有区别,
当所有”前台线程”都结束后,”后台线程”会自动结束。
void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。
知识点:
1.setDeamon(true)设为守护线程
2.全剩下守护线程,程序结束
九 Join方法
join()方法:
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
class Demo {public static void main(String args[]) throws Exception {StopThread st = new StopThread();Thread t1 = new Thread(st);Thread t2 = new Thread(st);t1.start();t1.join();t2.start();t2.join();for (int x = 0; x < 70; x++) {System.out.println(Thread.currentThread().getName() + " " + x);}System.out.println("over");}}class StopThread implements Runnable {public void run() {for (int x = 0; x < 70; x++) {System.out.println(Thread.currentThread().getName() + " " + x);}}}
join()方法,抢先获得CPU的执行权,先于主线程执行,
例如,t1.join();。此时,主线程处于冻结状态,只有t1可以运行;
只有t1运行结束,主线程才可以运行,等待让主线程让出执行权的线程死亡。一般用于临时加入线程。
join方法的位置会有影响,如果放在多个线程开启的下面,例如,t1.start();t2.start();t1.join();,
t1和t2会交替执行,因为t1抢的是主线程的运行权,和t2无关。主线程依然在t1结束后运行,和t2的执行无关。
当A线程执行到了B线程的join方法时,A线程就会等待,等B线程都执行完,A线程才会执行。可以用来临时加入线程执行。可以嵌套使用。
如果t1等待,会造成主线程无法运行,所以使用interrupt方法来结束冻结状态,继续运行。
因为是强制恢复运行,所以可能会出现异常,所以join方法会抛出InterruptedException异常。
开启线程的线程组,称为主线程组,里面的成员构成一个线程组。也可以通过ThreadGroup创建新的对象,封装进其他的组。很少用。
十 优先级和yield方法
1、关于线程的优先级
线程的执行有优先级,可通过setPriority(int newPriority)方法更改线程的优先级。
优先级:代表抢资源的频率,设置越高,执行越频繁,优先执行。
所有线程的优先级,包括主线程,优先级默认设置为5,共有10级,数字越大,越优先执行。
如果数字相差不大,优先程度几乎没有差别,只有1、5、10之间最明显,
分别对应Thread类的三个字段:MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY
。
虽然可以设为最高级,也只是相对高而已,不可能只执行一个线程。
2、yield方法
Thread.yield();暂停当前正在执行的线程对象,并执行其他线程。
多个线程运行时,通过临时强制释放线程的执行权,使得其他线程可以执行。
这样可以减缓线程的运行,使得线程都有机会运行。
3、开发时如何编写线程
Q:什么时候用多线程?
A:当某些代码需要同时运行时,可以用单独的线程进行封装。
也可以封装成类,或者对象。
示例代码:
new Thread()//通过Thread类建立匿名类{ public void run() { for (int x=0;x<30;x++ ) { System.out.println(Thread.currentThread().getName()+"...Jobs..."+x); } }}.start();
Runnable r = new Runnable()//通过Runnable接口建立匿名类对象{ public void run() { for (int x=0;x<30;x++ ) { System.out.println(Thread.currentThread().getName()+"...Intel..."+x); } }};new Thread(r).start();
------- android培训、java培训、期待与您交流! ----------
- 黑马程序员 —— Java多线程2 (第十二天)
- 【黑马程序员】多线程(二) 第十二天
- 黑马程序员--Java基础学习(多线程)第十二天
- 黑马程序员——Java 反射 (第十二篇)
- 黑马程序员——JAVA多线程2
- 黑马程序员——Java多线程2
- 黑马程序员——Java(多线程)
- 黑马程序员—java多线程
- 黑马程序员—JAVA多线程
- 黑马程序员—java多线程
- 黑马程序员—Java多线程
- 黑马程序员—java多线程
- 黑马程序员—Java多线程
- 黑马程序员——>第十二天<多线程(线程间通信-守护线程)>
- 黑马程序员——Java基础--多线程(2)
- 黑马程序员——Java基础---多线程(2)
- 黑马程序员——java基础学习笔记——第十二天
- 黑马程序员——Java 多线程
- C# 中的委托和事件
- 多态和虚表
- ijkplayer-学习笔记
- Unix 网络编程(三)- 网络套接字编程“基础API”介绍
- IOS 多线程NSThread、NSOperation、GCD详解
- 黑马程序员 —— Java多线程2 (第十二天)
- 外挂辅助技术研究-分析选怪功能
- 【Distribution】分布式系统的事务处理
- AndroidのTween动画实现
- 大年三十
- Animation实现动画(xml实现非代码实现)
- Polymorphism in Overloaded and Overridden Method
- C++使用libcurl做HttpClient
- 【机器学习】决策树-ID3算法的Python实现