多线程(线程间通信-示例)
以前啊有一个共同特点就是,多个线程是不是在执行同一个动作,多个线程是不是执行同一个任务
多线程通讯生活中的示例就是例如拉媒,执行的是同一个资源吧,一个人往这里面放煤,另外一个在拉煤,是不是啊
这个我们怎么去描述呢
第一步该怎么做,这个资源里面的数据不唯一,那就封装成对象
第二步:那这个输入和输出,这是不是同一个动作啊,你扔一个我输出一个,他们是不是同时运行啊,这个时候就涉及到多线程技术了啊,那他们的任务是不是不一样啊,是不是就有两个run方法,要分别封装在两个类中,所以在这儿我都想到了三个类,一个是资源,一个是输入,一个是输出,
这里面的输出是不是打印就完事了啊,你打印一次不合适吧,我仍一个你输出一个扔一个输出一个,,是不是不断在做这件事情啊,那就来个while(true)后面就知道了,为什么要写这个无限
是不是这样做啊,这里面有一个问题就是操作的不是同一个资源,输出new了一个,输入又new了一个,这里面我们怎么样来保证操作的是同一个资源也,如果你使用单例也也,这个对象就只能创建一个对象,使用静态的也不合适
首先,在我们输入资源里面不能new对象,但是在这个里面我们还得去操作资源,不能new对象是因为我们要和输出是同一个对象,但是还得用这个引用
我们可能通过另外一种方式,叫做传参递,把参数传递进来,我在外面把资源对象建好,分别传给输入和输出,是不是就可以保证是同一个对象也,能接受参数有两种形式,要么是一般方法,要么是构造函数啊,你觉得,哥们是任务,要处理资源,你觉得任务对象一初始化是不是就得有资源啊,就这么一想就完了,就像线程对象一样,线程对象一创建完,是不是就要有任务啊,
输出
是不是出问题了啊,这个是不是出现线程安全问题,那儿造成的,怎么造成的也,分析分析,是不是要加同步锁啊,是的
见图
为什么加了锁还是没有解决问题呢
这个时候你加同步了,问题还是没有解决,这个时候你就要去考虑加同步的前提,
前提合并完了就一句话就是:在一个锁里面有多个线程,这多个线程是不是在同一个锁当中,
我们一分析发现synchronized这是不是同步啊,是啊,这里面有几个线程啊,就一个,因为输出线程在下面,所以也我们要保证输出线程也要在同步里面啊,你输出不在同步里面,光输入同步有什么用啊,什么意思,你输入一半的时候也,没有输入完,你就不能去取啊
This不行啊,是两个对象吧,静态的可不可以也,可以,这里面可以使用Rusource,是不是操作的是同一个资源的啊,就用Resource
怎么全是女也,是因为吧,前一个线程吧赋完mike过后,这个输入线程还有cpu的执行权,然后就赋值为了丽丽,就把前面的数据给覆盖了
可不可实现我输入一个make男,你就输出一次make男,我输入一次丽丽 女就你输出一次女也,上面完整的代码:
/*
线程间通讯:
多个线程在处理同一资源,但是任务却不同。
*/
//资源
class Resource
{
String name;
String sex;
}
//输入
class Input implements Runnable
{
Resource r ;
// Object obj = new Object();
Input(Resource r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
synchronized(r)
{
if(x==0)
{
r.name = "mike";
r.sex = "nan";
}
else
{
r.name = "丽丽";
r.sex = "女女女女女女";
}
}
x = (x+1)%2;
}
}
}
//输出
class Output implements Runnable
{
Resource r;
// Object obj = new Object();
Output(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
synchronized(r)
{
System.out.println(r.name+"....."+r.sex);
}
}
}
}
class ResourceDemo
{
public static void main(String[] args)
{
//创建资源。
Resource r = new Resource();
//创建任务。
Input in = new Input(r);
Output out = new Output(r);
//创建线程,执行路径。
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
//开启线程
t1.start();
t2.start();
}
}
请听下面讲解
接下来我们的需求是什么呢,就是说我们希望啊,你输入一个姓名和性别以后,你就别在输入下一个,把这个输出过后你再输入下一个
多线程(线程间通信-等待唤醒机制)
你在输入的时候是不是要先判断一下,有没有数据啊,如果有你是不是覆盖了啊,这里就要使用到wait和notify来解决这个问题
这里面必须要明确是哪一个锁,就像操场是有两起小朋友在玩冰棍这种游戏一样,你那边wait了,我这边不能救吧
r.wait();要有所属,如果你被wait了,你被wait在哪儿这个r的线程池中,如果调用了
多线程(线程间通信-等待唤醒机制-代码优化)
class Resource
{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name,Stringsex)
{
if(flag)
try{this.wait();}catch(InterruptedException e){}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out()
{
if(!flag)
try{this.wait();}catch(InterruptedException e){}
System.out.println(name+"...+...."+sex);
flag = false;
notify();
}
}
//输入
class Input implements Runnable
{
Resource r ;
// Object obj = new Object();
Input(Resource r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
if(x==0)
{
r.set("mike","nan");
}
else
{
r.set("丽丽","女女女女女女");
}
x = (x+1)%2;
}
}
}
//输出
class Output implements Runnable
{
Resource r;
// Object obj = new Object();
Output(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ResourceDemo3
{
public static void main(String[] args)
{
//创建资源。
Resource r = new Resource();
//创建任务。
Input in = new Input(r);
Output out = new Output(r);
//创建线程,执行路径。
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
//开启线程
t1.start();
t2.start();
}
}
多线程(线程间通信-多生产者多消费者问题)
接下来我们把这个等待与唤醒机制深化,玩等待与唤醒的经典事例是多生产者与多消费者
这里面多生产,多消费就出问题呢,而单生产,单消费就不出问题呢
我们不光只记住这结果,一定要分析这过程
现在带着我们一步一步的来分析这结果
现在我们假设生产者得到了cpu执行权,如果读到这个set方法,是不是拿来锁了啊,,假吗,就不用等待啊,假设生产了烤鸭1,后面count++就变成了2了,这哥们执行完set完以后,是不是释放锁了啊,是不是还有可能获取到cpu执行权,所以这哥们又进到了set里面来了,进来了,是真了吧,t0等待了吧,t0和t1是不是在set这里面运行了啊,t2和t3是不是在out这里面运行啊,那么这里面的t0一挂,是不是有三个处于临时阻塞状态吧,他们三个是不是有一个会被cpu执行到,是不是随机的啊,假设t1被执行到了,t1就进来了,为真吧,t1也等待了,现在t2假设有了执行权了一判断为真,是不是输出了,这个时候消费了烤鸭1,置了假了吧,置完以后,noticfy吧,现在线程池里面有两个吧,假设t0被唤醒了,t2是不是持有执行权啊,一noticfy,一回来是不是假了啊非假就为真了吧,t2就等待了吧,现在活着的有几个t0,和t3,假设t3抢到了执行权了,非假为真吧,这个时候t3也等待了吧,现在活的只有一个吧,t0,听一下关键就在这儿呢t0在try{this.wait();}catch(InterruptedExceptione){}t0在这儿醒的吧,还判断标记不,不判断了,是不是往下走啊,这个时候就生产了烤鸭2了啊,编号就变成了三,接着输出生产烤鸭2,接着就变为真了,然后notify了吧,就在这儿,我们池子里面有几个,有三个吧,这三个是不是活一个,那t1,t2,t3都有机会,如果你活的是t2,t3,这事就和谐了,没有想到这三个里面活的是t1,现在t1活了吧,,有可能t0还有cpu执行权了,然后判断,为真,t0就等待了吧,t1活了,不再判断标记了,往下走吧,就变成了烤鸭3,编号就为4了,然后就输出烤鸭3,到这儿生产,消费是不是有问题了啊 ,这个时候生产烤鸭2是不是没有被消费
分析原因:原因就出现在这儿,t1唤醒了过后,没有判断标记就生产了,,我让t1醒了过后再去判断一次是不是就ok了啊,那怎么样让t1醒来过后再回来去判断标记呢,要得程序往回走才行啊 ,把这个if换成while就解决问题了,当t1醒了过后,是不是就要去判断while啊,if和while区别,if是不是判断一次,while判断多次啊,
现在再去执行就不动了,成了死锁了
这个过程不用再重头说了吧,就从关键点说好吧
现在假设t0,和t1,进来以后也被wait了,t2和t3就进来了吧,t2就消费了一次,正常消费吧,然后就置为false,就notify了吧,它唤醒了一个啊,假设t0唤醒了,然后t2就wait了吧,t3也wait了吧,t0活着呢,就从这儿开始了,t0活着以后吧,它就循环回来判断标记来了,t2把标记置为了假了吧,,t0就生产了一只烤鸭,生产完以后改为真的吧,再notify一次吧,这一notify是不是有三了啊,唤醒其中一个,唤醒了t1,while判断,t0就再次wait了吧,现在活着的还有谁 ,t1吧,t1是不是要回来判断标记了啊,这时为真吧,这个时候t1又等待在这儿了吧,现在是不是这四个线程都在这儿等待着呢,成死锁了吧
下面我们解决死锁的情况吧
多线程(线程间通信-多生产者多消费者问题解决)
刚才我们说的while是为了解决每次醒的线程都需要判断标记,,可是也这个t0醒了以后,它去运行了,它唤醒的是不是自己方啊,把本方唤醒以后再判断标记,本方是不是也睡着了啊,对方也没有被唤醒过,是不是大家都睡着了啊,我们希望什么也,t0唤醒的时候也是不是唤醒至少一个对方啊,没有对方才导致我们的程序死锁,那么怎么唤醒对方,现在我们准备去唤醒所有等待的线程,使用了notifyAll过后就这问题就解决了
一会也我们准备画一张图,把这个事说清楚,把这个为什么要做while,为什么要做notifyAll的标记判断的原因给大家再说一下
生产者,消费者。
多生产者,多消费者的问题。
if判断标记,只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。
while判断标记,解决了线程获取执行权后,是否要运行!
notify:只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。
notifyAll解决了本方线程一定会唤醒对方线程的问题。
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)//
{
while(flag)
try{this.wait();}catch(InterruptedException e){}// t1 t0
this.name = name + count;//烤鸭1 烤鸭2 烤鸭3
count++;//2 3 4
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);//生产烤鸭1 生产烤鸭2 生产烤鸭3
flag = true;
notifyAll();
}
public synchronized void out()// t3
{
while(!flag)
try{this.wait();}catch(InterruptedException e){} //t2 t3
System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
flag = false;
notifyAll();
}
}
class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("烤鸭");
}
}
}
class Consumer implements Runnable
{
private Resource r;
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
你这个效率是不是有点低啊,唤醒本方做什么呢,唤醒了还要去判断一次
下面的讲解就会解决这个问题
多线程(线程间通信-多生产者多消费者问题-JDK1.5新特性-Lock)
接着往下面说,我们的确使用了while和notifyAll解决了多生产者与多消费者的问题
现在我们就是说能不能做到只唤醒对方也,就使用了jdk5.0来解决这个问题了
我们使用lock对synchronized进行了替代,之前我们使用的synchronized是对函数或者是代码块进行封装,之后也使用了了对象,把lock封装成了对象,使得操作更加灵活
而后期,把锁封装成了对象 ,按照面向对象的方式来思考锁动作获取锁和释放锁是不是锁自己最清楚啊
29-多线程(线程间通信-多生产者多消费者问题-JDK1.5新特性-Condition)
以前是把wait,notify,notifyAll这些对象封装在object里面在,现在把object的这些方法封装在了Condition里面,以前的object只能封装一组wait,notify,notifyAll方法,现在把它换成了Condition过后,可以蒋多个Condtion封装在一个lock当中
这个Lock是为了替代synchronized,然而这个Condition替代了object类中的wait,notify,notifyAll
30-多线程(线程间通信-多生产者多消费者问题-JDK1.5解决办法)
以前吧我们一组监视器既监视生产者又监视消费者,痛苦就痛苦在这儿,,现在我们将线程进行分类,一组负责生产一组负责消费啊,我们希望生产者能唤醒消费者吧,希望消费者能唤醒者生产者吧,那我们现在弄两组监视器,就解决这个效率问题,完整代码:
/*
jdk1.5以后将同步和锁封装成了对象。
并将操作锁的隐式方式定义到了该对象中,
将隐式动作变成了显示动作。
Lock接口: 出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成现实锁操作。
同时更为灵活。可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,通常需要定义finally代码块中。
Condition接口:出现替代了Object中的wait notify notifyAll方法。
将这些监视器方法单独进行了封装,变成Condition监视器对象。
可以任意锁进行组合。
await();
signal();
signalAll();
*/
importjava.util.concurrent.locks.*;
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
// 创建一个锁对象。
Lock lock = new ReentrantLock();
//通过已有的锁获取该锁上的监视器对象。
// Condition con = lock.newCondition();
//通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name)// t0 t1
{
lock.lock();
try
{
while(flag)
// try{lock.wait();}catch(InterruptedException e){}// t1 t0
try{producer_con.await();}catch(InterruptedException e){}// t1 t0
this.name = name + count;//烤鸭1 烤鸭2 烤鸭3
count++;//2 3 4
System.out.println(Thread.currentThread().getName()+"...生产者5.0..."+this.name);//生产烤鸭1 生产烤鸭2 生产烤鸭3
flag = true;
// notifyAll();
// con.signalAll();
consumer_con.signal();
}
finally
{
lock.unlock();
}
}
public void out()// t2 t3
{
lock.lock();
try
{
while(!flag)
// try{this.wait();}catch(InterruptedException e){} //t2 t3
try{cousumer_con.await();}catch(InterruptedException e){} //t2 t3
System.out.println(Thread.currentThread().getName()+"...消费者.5.0......."+this.name);//消费烤鸭1
flag = false;
// notifyAll();
// con.signalAll();
producer_con.signal();
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource r;
Producer(Resourcer)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("烤鸭");
}
}
}
class Consumer implements Runnable
{
private Resource r;
Consumer(Resourcer)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ProducerConsumerDemo2
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
31-多线程(线程间通信-多生产者多消费者问题-JDK1.5解决办法-范例)
下面这个范例代码最好自己能写一下
import java.util.concurrent.locks.Condition;
importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
32-多线程(wait和sleep的区别)
重要的小知识点:在同步里面,我们说过,某一时刻只能有一个线程在执行啊,
Synchronized(this)
{
wait();//t0,t1,t2,现在有三个在同步里面活着,在同步里面要想运行,要具备一个资格才行,就是你得持有锁,没有锁什么都白扯
}
wait 和 sleep 区别?
1,wait可以指定时间也可以不指定。
sleep必须指定时间。
2,在同步中时,对cpu的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
class Demo
{
void show()
{
synchronized(this)//
{
wait();//t0 t1 t2
}
}
void method()
{
synchronized(this)//t4
{
//wait();
notifyAll();
}//t4
}
}
33-多线程(停止线程方式-定义标记).avi
就是在线程中为什么使用while()解释说明:
开启多线程的目的不就是因为有些代码需要运行很多次吗,影响了其他代码的执行,所以我们想要这两部分代码同时执行吗,这才是我们写循环的原因,当时举例也不在说吗,线程一下去碰到循环,一阵转,转不完,下面是不是执行不了,这个时候我们需要开另外一个通路负责这个循环转,然后再继续往下面走啊, 所以我们写循环
一般我们使用标记的方式将程序停下来,下面的程序就是定义标记的方式
34-多线程(停止线程方式-Interrupt)
上面这种定义标记的方式也有停不下来的时候,也有弊端
下面的代码定义标记,线程也不能停下来,我们只是改了上面的while的代码,其他的都没有变,look
停止线程:
1,stop方法(已过时)不再使用,不安全。
2,run方法结束。
怎么控制线程的任务结束呢?
任务中都会有循环结构,只要控制住循环就可以结束任务。
控制循环通常就用定义标记来完成。
但是如果线程处于了冻结状态,无法读取标记。如何结束呢?
可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。
当时强制动作会发生了InterruptedException,记得要处理
*/
class StopThread implements Runnable
{
private boolean flag = true;
public synchronized void run()
{
while(flag)
{
try
{
wait();//t0 t1
}
catch (InterruptedException e)
{
System.out.println(Thread.currentThread().getName()+"....."+e);
flag = false;
}
System.out.println(Thread.currentThread().getName()+"......++++");
}
}
public void setFlag()
{
flag = false;
}
}
35-多线程(守护线程-setDaemon)
听守护听不懂,那就换个词,理解为后台线程,我们以前做的哪些线程叫做前台线程
这个有什么区别也:
后台线程的特点在于它和前台线程都正常进行开启,就是结束不一样,运行都一样互相抢cpu的执行权,结束不一样,前台线程你必须要进行一个手动结束,比如设定标记的形式来进行结束,你不结束它就会一直在哪儿等待,在哪儿耗费资源,对于后台进程,如果所有的前台线程都结束了,后台线程无论处于什么状态,都自动结束
36-多线程(其他方法-join等)
在JoinDemo里加入了这句话过后,永远都是thread-0先执行,这个join到底什么意思呢
现在我们将代码解读一下一开始吧,主线程是不是开始往下执行啊,执行到t1.join();时,也发现也t1线程要申请加入进来,运行。临时加入一个线程运算时可以使用join方法。这个时候本来是主线程有cpu执行权吧,然后这个主线程就让出cpu执行权和资格,让给t1运行,等t1执行完了,主线程再运行,主线程就等t1,t2执没有执行完,跟主线程没有关系
class Demo implements Runnable
{
public void run()
{
for(int x=0; x<50; x++)
{
System.out.println(Thread.currentThread().toString()+"....."+x);
Thread.yield();
}
}
}
class JoinDemo
{
public static void main(String[] args) throws Exception
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
// t2.setPriority(Thread.MAX_PRIORITY);
// t1.join();//t1线程要申请加入进来,运行。临时加入一个线程运算时可以使用join方法。
for(int x=0; x<50; x++)
{
// System.out.println(Thread.currentThread()+"....."+x);
}
}
}
37-多线程(面试题)
举个线程中的小例子
/*class Test implementsRunnable
{
public void run(Thread t)
{}
}*/
//如果错误 错误发生在哪一行?错误在第一行,应该被abstract修饰
//这个run方法是子类特有的方法吧,
//一个类如果实现了一个接口 ,如果接口里面抽象方法没有被覆盖的话,该类是抽象的,要被abstract修饰才行
class ThreadTest
{
public static void main(String[] args)
{
new Thread(new Runnable()
{
public void run()
{
System.out.println("runnable run");
}
})
{
public void run()
{
System.out.println("subThread run");
}
}.start();
///本来吧,是要用任务的,你子类把父类的run方法覆盖了,就复写了啊,那就以子类为主了呗
/*
new Thread()
{
public void run()
{
for(int x=0;x<50; x++)
{
System.out.println(Thread.currentThread().getName()+"....x="+x);
}
}
}.start();
for(int x=0;x<50; x++)
{
System.out.println(Thread.currentThread().getName()+"....y="+x);
}
Runnable r = newRunnable()
{
public void run()
{
for(int x=0;x<50; x++)
{
System.out.println(Thread.currentThread().getName()+"....z="+x);
}
}
};
new Thread(r).start();
*/
}
}
多线程技术总结
/*
多线程总结:
1,进程和线程的概念。
|--进程:
|--线程:
2,jvm中的多线程体现。
|--主线程,垃圾回收线程,自定义线程。以及他们运行的代码的位置。
3,什么时候使用多线程,多线程的好处是什么?创建线程的目的?
|--当需要多部分代码同时执行的时候,可以使用。
4,创建线程的两种方式。★★★★★
|--继承Thread
|--步骤
|--实现Runnable
|--步骤
|--两种方式的区别?
5,线程的5种状态。
对于执行资格和执行权在状态中的具体特点。
|--被创建:
|--运行:
|--冻结:
|--临时阻塞:
|--消亡:
6,线程的安全问题。★★★★★
|--安全问题的原因:
|--解决的思想:
|--解决的体现:synchronized
|--同步的前提:但是加上同步还出现安全问题,就需要用前提来思考。
|--同步的两种表现方法和区别:
|--同步的好处和弊端:
|--单例的懒汉式。
|--死锁。
7,线程间的通信。等待/唤醒机制。
|--概念:多个线程,不同任务,处理同一资源。
|--等待唤醒机制。使用了锁上的 wait notify notifyAll. ★★★★★
|--生产者/消费者的问题。并多生产和多消费的问题。 while判断标记。用notifyAll唤醒对方。★★★★★
|--JDK1.5以后出现了更好的方案,★★★
Lock接口替代了synchronized
Condition接口替代了Object中的监视方法,并将监视器方法封装成了Condition
和以前不同的是,以前一个锁上只能有一组监视器方法。现在,一个Lock锁上可以多组监视器方法对象。
可以实现一组负责生产者,一组负责消费者。
|--wait和sleep的区别。★★★★★
8,停止线程的方式。
|--原理:
|--表现:--中断。
9,线程常见的一些方法。
|--setDaemon()
|--join();
|--优先级
|--yield();
|--在开发时,可以使用匿名内部类来完成局部的路径开辟。