目录 :
1 ) . 多线程(线程间通信--示例代码)
2 ) . 多线程(线程间通信--解决安全问题)
3 ) . 多线程(线程间通信--等待唤醒机制)
4 ) . 多线程(线程间通信--代码优化)
5 ) . 多线程(线程间通信--生产者消费者)
6 ) . 多线程(线程间通信--生产者消费者升级版)
7 ) . 多线程(停止线程)
8 ) . 多线程(守护线程)
9 ). 多线程(Join方法)
一
. 多线程(线程间通信--示例代码)
1 ) . 图解 :![]()
1.1 思考 : wait() ,notify(),notifyAll(),用来操作线程为什么定义在Object类中?
[1] 这些方法存在于同步中
[2]使用这些方法时必须要标识所属的同步的锁
[3] 锁可以是任意对象,所以任意对象调用的方法一定定义Object类中
1.2 思考2 : wait(),sleep()有什么区别?
[1] wait() : 释放资源,释放锁
[2] sleep():释放资源,不释放锁
2 ) . Demo : -->解决方式在下一章
/*一个输入功能,一个输入功能,一个资源池,,然后有规律的输入输出同一个对象向inputStream存入,同一个对象在outputstream取出
出现了以下问题: 存入内容的与取出的内容相互混乱,涉及多线程问题*/class Resources{String name;String sex;}class InputStream implements Runnable{private Resources res;InputStream(Resources res){this.res=res;}public void run(){int num=0;while(true){if(num==0){synchronized(this){res.name="summer";res.sex="man";}}else{synchronized(this){res.name="小王";res.sex="妞";}}num=(num+1)%2;}}}class OutputStream implements Runnable{private Resources res;OutputStream(Resources res){this.res=res;}public void run(){while(true){synchronized(this){System.out.println(res.name+"........"+res.sex);}}}}class InputOutputDemo{public static void main(String args[]){Resources r = new Resources();Thread in = new Thread(new InputStream(r));Thread ou = new Thread(new OutputStream(r));in.start();ou.start();}}
这是出现的问题
小结 :
1. 当多个人共用一个功能的时候,这个功能可以封装成方法或者封装成类
二. 多线程(线程间通信--解决安全问题)
1 ) . 分析是否同步的两个维度 :
1.1 是否是两个或两个以上的线程
1.2 是否使用的是同一个锁
2 ) . Text : --> 现象就是-->同时输入同时输出
/*一个输入功能,一个输入功能,一个资源池,然后有规律的输入输出同一个对象向inputStream存入,同一个对象在outputstream取出出现了以下问题: 存入内容的与取出的内容相互混乱,涉及多线程问题;解决方式:通过同步代码块解决同步问题(多个进程同时进行的问题),通过唯一的资源当作锁解决多个进程使用同一个锁的问题
出现了以下问题 : 并不是一个输入一个输出有规律的输出,见下一章解决*/class Resources{String name;String sex;}class InputStream implements Runnable{private Resources res;InputStream(Resources res){this.res=res;}public void run(){int num=0;while(true){synchronized(Resources.class){if(num==0){res.name="summer";res.sex="man";}else{res.name="小王";res.sex="妞";}num=(num+1)%2;}}}}class OutputStream implements Runnable{private Resources res;OutputStream(Resources res){this.res=res;}public void run(){while(true){synchronized(Resources.class){System.out.println(res.name+"........"+res.sex);}}}}class InputOutputDemo1{public static void main(String args[]){Resources r = new Resources();Thread in = new Thread(new InputStream(r));Thread ou = new Thread(new OutputStream(r));in.start();ou.start();}}
小结 :
1. 想使用唯一一个对象掌控不同的功能,就把实例化对象给提出来,而后通过参数的方式放入不同的实现类中
三. 多线程(线程间通信--等待唤醒机制)
1 ) .问题:
问题一 : 以下三个方法都是使用在同步中,因为都要对持有监视器(锁)的线程进行操作,因此需使用在同步中,因为只有同步才具有锁
1.1 wait()
1.2 notify()
1.3 notifyAll()
问题二 : 为何以上操作线程方法要定义在Object类中呢?
因为在操作同步中的线程时,都必须得标识所操作线程的锁,而同一个锁上被等待的线程,只能被同一个锁上的notify唤醒,不可对不同锁上的线程唤醒,
而锁又可以是任意对象,所以可被任意对象调用的方法都定义在Object类中
2 ) . Text : --> 该代码完善添加了等待唤醒机制
/*一个输入功能,一个输入功能,一个资源池,然后有规律的输入输出同一个对象向inputStream存入,同一个对象在outputstream取出出现了以下问题: 存入内容的与取出的内容相互混乱,涉及多线程问题;解决方式:通过同步代码块解决同步问题(多个进程同时进行的问题),通过唯一的资源当作锁解决多个进程使用同一个锁的问题出现了以下问题 : 并不是一个输入一个输出有规律的输出解决方式 :通过在不同线程使用相同资源的情况下,采用等待唤醒机制一个一个来的方式解决 ,例子 : 关于定的游戏*/class Resources{String name;String sex;boolean flag =false;}class InputStream implements Runnable{private Resources res;InputStream(Resources res){this.res=res;}public void run(){int num=0;while(true){synchronized(Resources.class) //resources.class 也可填 res ,也就是构造函数传进来的那个对象是一个的, 重点就是使用同一个锁{//唤醒机制if(res.flag)try{Resources.class.wait();}catch(Exception e){} //resources.class 也可填 res ,也就是构造函数传进来的那个对象是一个的, 重点就是使用同一个锁if(num==0){res.name="summer";res.sex="man";}else{res.name="小王";res.sex="妞";}num=(num+1)%2;res.flag=true;Resources.class.notify(); //resources.class 也可填 res ,也就是构造函数传进来的那个对象是一个的 , 重点就是使用同一个锁}}}}class OutputStream implements Runnable{private Resources res;OutputStream(Resources res){this.res=res;}public void run(){while(true){synchronized(Resources.class) //resources.class 也可填 res ,也就是构造函数传进来的那个对象是一个的, 重点就是使用同一个锁{//唤醒机制if(!res.flag)try{Resources.class.wait();}catch(Exception e){} //resources.class 也可填 res ,也就是构造函数传进来的那个对象是一个的, 重点就是使用同一个锁System.out.println(res.name+"........"+res.sex);res.flag=false;Resources.class.notify(); //resources.class 也可填 res ,也就是构造函数传进来的那个对象是一个的, 重点就是使用同一个锁}}}}class InputOutputDemo2{public static void main(String args[]){Resources res = new Resources();Thread in = new Thread(new InputStream(res));Thread ou = new Thread(new OutputStream(res));in.start();ou.start();}}
小结 :
1. 等待的线程会放入线程池中,一般情况下唤醒的是最先等待的线程2. IllegalMonitorStateException -->当前线程不含有当前对象的锁资源的时候,使用wait()notify()notifyAll()都会抛出
3. 切记 : 使用等待唤醒机制时,切记让其等待的锁与让其唤醒的锁是同一个锁,而不同线程可使用同一个锁,若不是同一个锁就会抛出上条的异常
四. 多线程(线程间通信--代码优化)
1 ) . 代码优化的六大原则 :
1.1 单一职责 --> 各司其职
1.2 开闭原则 -->对于扩展(类,模块,函数)是开放的,对于修改是封闭的
1.3 里氏替换原则 --> 里氏替换原则就是基于这两个字:抽象,也就是所有引用基类(最多的共性类)的地方,必须能使用子类对象-->抽出共性形成类
1.4 -->依赖倒置原则 -->高层模块是调用端,底层模块就是具体实现类,高层模块与底层模块之间不依赖,统一依赖抽象模块-->也就是面向接口或面向抽象编程
1.5 -->接口隔离原则-->指将非常庞大,臃肿的接口抽取出更小的接口和更具体的接口,目的是 系统解开耦合,从而容易重构,更改和重新部署
1.6 -->迪米特原则 --> 也是最少知识原则,指一个对象应该对其他对象有最少的了解 -->
我翻译的就是 知道的越多死的越快(两者关系密切臃肿,后期还得自己先捋清关系,再修改代码),还不如知道的少点
1.7小知识点 :
[1] 抽象就是指接口和抽象类,因为两者不可被直接实例化
2 ) . Text : -->完整版,优化过的
/*一个输入功能,一个输入功能,一个资源池,然后有规律的输入输出同一个对象向inputStream存入,同一个对象在outputstream取出出现了以下问题: 存入内容的与取出的内容相互混乱,涉及多线程问题;解决方式:通过同步代码块解决同步问题(多个进程同时进行的问题),通过唯一的资源当作锁解决多个进程使用同一个锁的问题出现了以下问题 : 并不是一个输入一个输出有规律的输出解决方式 :通过在不同线程使用相同资源的情况下,采用等待唤醒机制一个一个来的方式解决 ,例子 : 关于定的游戏代码优化*///设值与输出值两者涉及同步代码块与等待唤醒机制,class Resources{private String name;private String sex;private boolean flag = false;//为对象进行设值public synchronized void set(String name,String sex){if(flag)try{this.wait();}catch(Exception e){}else{this.name=name;this.sex=sex;}this.flag=true;this.notify();}//为对象值进行输出public synchronized void out(){if(!flag)try{this.wait();}catch(Exception e){}elseSystem.out.println(this.name+"......"+this.sex);this.flag=false;this.notify();}}//输入流,进行调取对象方法输入值class InputStream implements Runnable{private Resources res;InputStream(Resources res){this.res=res;}public void run(){int num=0;while(true){if(num==0)res.set("summer","man");elseres.set("小王","妞");num=(num+1)%2;}}}//输出流,进行调取对象方法输出值class OutputStream implements Runnable{private Resources res;OutputStream(Resources res){this.res=res;}public void run(){while(true){res.out();}}}//主函数class InputOutputDemo3{public static void main(String args[]){//创建资源对象Resources res = new Resources();//创建一个输入线程,其中内含写入资源对象值,并启动-->其中写入的是res对象的值new Thread(new InputStream(res)).start();//创建一个输出线程,其中内含写出资源对象值,并启动-->其中写出的也是res对象的值new Thread(new OutputStream(res)).start();}}小结 :
1. 优化时采取模块化的方式,如何思考模块化,就是思考在其位谋其政,思考自己做自己本分事即可
五
. 多线程(线程间通信--生产者消费者)
1 ) . 业务思考四思路 : -->与以下代码无关
1.1 要求的结果是什么?
1.2 形成的结构是什么?
1.3 要使用那些知识点?
1.4 要满足哪些条件 ?
2 ) . Text-->ProducerConsumerDemo
/*需求:ProducerConsumer,生产一个,消费一个当四个线程时,会出现下边常见两个问题:问题1.出现生产者1生产的产品1没有被消费问题2.出现消费者重复消费了生产者生产的产品1分析原因:原因1:if(flag)不能循环判断,因此当同一个锁中有两个线程时,一个被激活,而后再另一个被激活时是不需要再判断直接运行下边代码了原因2:this.notify()是用来唤醒的,但唤醒的是先等待的线程,当那个不需要再次验证的线程被激活后就直接执行下边的代码了解决 :if(flag)改成while(flag) -->循环判定,就算进入等待,被激活后也得重新判断 -->但是这个会出现全部等待的问题this.notify()改成this.notifyAll()-->摆脱先唤醒先等待线程这个局限,全部唤醒,再次进入正循环即可 -->全部唤醒解决了上个全部等待问题*///主函数class ProducerConsumerDemo{public static void main(String args[]){Product pro = new Product();new Thread(new Producer(pro)).start();new Thread(new Consumer(pro)).start();new Thread(new Producer(pro)).start();new Thread(new Consumer(pro)).start();}}//产品class Product{private int count=1;private String ProductName;private boolean flag=false;public synchronized void set(String ProductName){while(flag) //if(flag) t1 ,t2try{this.wait();}catch(InterruptedException e){}this.ProductName=ProductName+"--"+count++;System.out.println(Thread.currentThread().getName()+"...........生产者"+count);this.flag=true;this.notifyAll();}public synchronized void out(){while(!flag) //if(flag) t3 , t4try{this.wait();}catch(InterruptedException e){}System.out.println(Thread.currentThread().getName()+"消费者...."+count);this.flag=false;this.notifyAll();}}//生产者class Producer implements Runnable{private Product pro;Producer(Product pro){this.pro=pro;}public void run(){while(true){pro.set("summer");}}}//消费者class Consumer implements Runnable{private Product pro;Consumer(Product pro){this.pro=pro;}public void run(){while(true){pro.out();}}}
3 ) . 生产者与消费者用到的知识点 : 同步代码块 , 等待激活机制 , 多线程循环判定问题
4 ).问题:
4.1 对于多个生产者和消费者为何要定义while判断标记?
[1] 让唤醒的线程再一次判断标记
4.2 对于多个生产者消费者为何要定义notifyAll?
[1] 因为需要唤醒对方的线程,通过notify易出现只唤醒本方线程的情况,导致所哟线程都等待,因此通过notifyAll唤醒所有的线程
小结 :
1. 学习方法 :
[1] 彻底理解问题
[2] 形成解决思路
[3]立即动手执行
[4]不断总结迭代
2. if(flag) 是判断一次, while(flag)是判断多次
3. 全部等待跟死锁的区别在于,全部等待是都没有执行权全部冻结,死锁是具有执行权,但是A锁锁住了B锁,不是一个锁
六. 多线程(线程间通信--生产者消费者升级版)
1 ) . JDK1.5新特性 : -->提供了多线程升级版解决方案在 java.util.concurrent.locks包中
1.1 Condition 接口封装了之前的object对象的wait(),notify(),notify()等方法
1.2 Lock接口实现了开启锁,关闭锁的封装方法,将同步Synchronized替换掉了,实现了同一个condition等待唤醒同一个进程
白话 : Acondition让B进程等待,只能Acondition才能唤醒B进程
2 ) . Text :
import java.util.concurrent.locks.* ;/*需求:ProducerConsumer,生产一个,消费一个当四个线程时,会出现下边常见两个问题:问题1.出现生产者1生产的产品1没有被消费问题2.出现消费者重复消费了生产者生产的产品1分析原因:原因1:if(flag)不能循环判断,因此当同一个锁中有两个线程时,一个被激活,而后再另一个被激活时是不需要再判断直接运行下边代码了原因2:this.notify()是用来唤醒的,但唤醒的是先等待的线程,当那个不需要再次验证的线程被激活后就直接执行下边的代码了解决 :if(flag)改成while(flag) -->循环判定,就算进入等待,被激活后也得重新判断this.notify()改成this.notifyAll()-->摆脱先唤醒先等待线程这个局限,全部唤醒,再次进入正循环即可优化,使用JDK5.0的并发新特性优化代码*///主函数class ProducerConsumerDemo1{public static void main(String args[]){Product pro = new Product();new Thread(new Producer(pro)).start();new Thread(new Consumer(pro)).start();new Thread(new Producer(pro)).start();new Thread(new Consumer(pro)).start();}}//产品class Product{private int count=1;private String ProductName;private boolean flag=false;//创建一个锁对象,用来开启锁和关闭锁private Lock lock = new ReentrantLock();//创建两个条件分别用于不同的锁上用来等待和唤醒private Condition conditionA =lock.newCondition();private Condition conditionB =lock.newCondition();
public void set(String ProductName) throws InterruptedException{lock.lock();try{while(flag)conditionA.await();this.ProductName=ProductName+"--"+count++;System.out.println(Thread.currentThread().getName()+"...........生产者"+count);this.flag=true;conditionB.signal();}finally{lock.unlock();}}public void out() throws InterruptedException{lock.lock();try{while(!flag) //if(flag) t3 , t4conditionB.await();System.out.println(Thread.currentThread().getName()+"消费者...."+count);this.flag=false;conditionA.signal();}finally{lock.unlock();}}}//生产者class Producer implements Runnable{private Product pro;Producer(Product pro){this.pro=pro;}public void run(){while(true){try{pro.set("summer");}catch(InterruptedException e){}}}}//消费者class Consumer implements Runnable{private Product pro;Consumer(Product pro){this.pro=pro;}public void run(){while(true){try{pro.out();}catch(InterruptedException e){}}}}
3 ) . 相关以上代码接口方法须知 :
3.1 Lock lock = new ReentrantLock(); -->用来通过reentrantlock实例化类来获取一个锁对象来使用 lock() 和 unlock() 方法
3.1 Condition conditionA =lock.newCondition(); -->用来创建一个关于 调用等待和唤醒方法的类,其中此类还可多次创建,是A开启A,B开启B的模式
3.3 切记 , 一般封装后的新功能都会在名字上加一些修改, 例如:封装后的 wait()名字是await();
4 ) . java中不可以被NEW的类 :
4.1 抽象类
4.2 接口
4.3 无公开的构造方法类
4.4 虚接口(抽象接口)
小结 :
1. JDK5.0以后的加入了并发时的新特性显性的锁机制和显性的等待唤醒机制2. 查看JDKAPI5.0以后的 软件包 java.util.concurrent.locks 看详细并发时可采用的方法
七. 多线程(停止线程)
1 ) . 概述 :
1.1 定义循环结束标记 --> 因为线程运行代码一般都是循环,只要控制了循环即可
1.2 使用interrupt(中断)方法-->该方法是结束线程的冻结状态,使线程回到运行状态中来
ps : stop()方法已经过时,不再使用
2 ) . 线程结束方式 :
2.1 基本方式 : 多线程的运行通常都是循环结构,通过控制有限循环体则就可让run方法结束,也就是线程结束
2.2 特殊情况 : 当线程处于了冻结状态,并没有指定的方式让冻结的线程恢复到运行状态时,采用清除冻结的方式,强制让线程恢复到运行状态中来,这样就可通过操作标记让线程结束
3 ) . Demo :
/*需求:结束线程方式方式: 通过waiti()进行挂起,然后通过interrupt()的方法中断异常*/class ThreadA implements Runnable{private boolean flag =true;public synchronized void run(){while(flag){try{wait();}catch(InterruptedException e){flag=false;}System.out.println(Thread.currentThread().getName()+"......runThread.......");}}public void changeFlag(){flag=false;}}class StopThread{public static void main(String args[]){ThreadA st = new ThreadA();Thread t1 = new Thread(st);t1.start();t1.interrupt();// Thread t2 = new Thread(st);// t2.start();//t2.interrupt();/* int num=0;while(true){if(num++ ==1){//st.changeFlag();t1.interrupt();t2.interrupt();break;}//System.out.println(Thread.currentThread().getName()+"......main......."+num);}*/}}
小结 :
1. 中断状态就是冻结状态,绝非停止了进程2. 停止线程的方式是先用wait()方法冻结,在用interrupt()方法停止
八
. 多线程(守护线程)
1 ) . 概述 :
1.1 守护线程就是后台线程
1.2 当前台线程都结束的时候,后台线程会自动结束,jvm虚拟机退出
1.3 声明守护线程--> new Thread(new 自定义线程对象()).setDeamon(true); -->也就是将已有线程调用.setDeamon(true)方法并设参数值为true声明守护线程
2 ) . 使用场景 :
2.1 当一个A线程依赖另一个B线程时,B线程消失,则A线程存在就没意义了,可将A线程设置为守护线程
小结 :
1. 详情见JDK的API的Thread的方法
九
. 多线程(Join方法)
1 ) . 概述
1.1 简述 : join是加入的意思,准确来说是 争夺PU执行权的意思
1.2 特点 : 引用了join()方法的线程会获得CPU的执行权,将自己的可行代码执行完,主线程(JVM虚拟机)需要等该线程挂了之后才能再次分配CPU执行权
2 ) . Demo :
/*需求:抢夺CPU资源的方法方式: join()方法特点:当A线程执行到了B线程的.join()方法时,A就会等待,等待B线程都执行完,A才会执行,也可理解为B在强制抢CPU资源使用: join可以用来临时加入线程执行*/class Demo implements Runnable{public void run(){for(int i=0;i<60;i++){System.out.println(Thread.currentThread().getName()+"runNable...."+i);}}}class JoinThread{public static void main(String args[]) throws InterruptedException{Demo demo = new Demo();Thread t0 = new Thread(demo);Thread t1 = new Thread(demo);t0.start();t0.join();//在这里加这句话,意味着t0抢占了CPU执行权,必须把to该运行的线程代码全部运行完再往下运行t1.start();}}
十
.
总结
1 ) . 如何多个线程共享一个数据
1.1 方式一 : 通过static 声明的方式 ,但 并不是所有情况都可用
1.2 方式二 : 通过实现Runnable接口来完成多个线程共享一个数据
2 ) .如何解决多线程情况下的数据错乱问题?
2.1 使用同步代码块同步线程,从而解决多线程安全问题
2.2 使用同步函数来同步线程,从而解决多线程安全问题
3 ).如何解决单例中的懒汉模式的对象实例化安全问题?
3.1 通过将实例化对象的那几段代码放入同步代码块,切记同步函数锁用的是this,静态同步函数锁用的是字节码文件对象
4 ). 如何解决死锁的问题?
4.1 死锁的产生是因为同步中嵌套同步,避免A嵌套B,B嵌套A的情况发生即可,若发生可通过以下方式解决
[1] 查看是否是两个或两个以上线程
[2] 查看两个线程用的是否是同一个锁
5 ).如何完成一个输入一个输出同时进行呢?
5.1 确保两个线程用的是同一个锁
5.2 使用同步代码块实现多个线程使用的是同一个锁的问题
5.3 使用等待唤醒机制解决一个输入过后一个再输出有规律的进行
6 ).简单阐述下生产者消费者的思路 :
6.1 通过lock开启锁和关闭锁
6.2 通过condition实现等待唤醒机制
6.3 通过try{}finally{}保证锁的执行权拿到后必须得再交出去
6.4 通过while的循环判定解决if的局限性,摆脱全部等待问题
7). 如何让线程停止?
7.1 通过waiti()进行挂起,然后通过interrupt()的方法中断异常
8).守护线程是什么?
8.1 A依赖B,B消失则A失去意义的线程,此时A是守护线程,常用于后台程序,可通过set.Deamon(true)的方法来设置守护线程
9). 如何用线程抢占CPU资源?
9.1 通过Thread中的方法join()来设置抢占资格,获取CPU执行权