1.多个线程操作相同的资源,但是操作动作不同,所以存在安全问题
例如:
public class Test { public static void main(String[] args) { Resource r = new Resource(); Input in = new Input(r); Output out = new Output(r); Thread tin = new Thread(in); Thread tout = new Thread(out); tin.start(); tout.start(); }}/** * 两个线程共同的资源 * @author WangShuang * */class Resource{ private String name; private String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; }}/** * 存资源的线程 * @author WangShuang * */class Input implements Runnable { private Resource resource; public Input(Resource resource) { this.resource=resource; } @Override public void run() { int x =0; while(true){ synchronized (new Object()) { if(x==0){ resource.setName("张三"); resource.setSex("男"); }else{ resource.setName("lili"); resource.setSex("女女女"); } x=(x+1)%2; } } }}/** * * @author 取资源的线程 * */class Output implements Runnable { private Resource resource; public Output(Resource resource) { this.resource=resource; } @Override public void run() { while(true){ synchronized (new Object()) { System.out.println(resource.getName()+"..."+resource.getSex()); } } }}
运行结果:
张三...男
张三...女女女
张三...男
张三...男
张三...女女女
lili...男
发生上述问题的原因:当output取线程时,output还没取完,例如只get到了name张三,cpu的执行权就被input夺走,执行了两个setlili 女,当output再次取线程是sex已经变成了女,所以出
现了 张三。。。女的现象
添加了同步也不管用,那么就应该思考同步的前提 是不是两个或两个以上的线程操作共享资源,是不是同一个锁对象,很明显没有满足
2.现在更改代码用同一个锁
public class Test { public static void main(String[] args) { Resource r = new Resource(); Input in = new Input(r); Output out = new Output(r); Thread tin = new Thread(in); Thread tout = new Thread(out); tin.start(); tout.start(); }}/** * 两个线程共同的资源 * @author WangShuang * */class Resource{ private String name; private String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; }}/** * 存资源的线程 * @author WangShuang * */class Input implements Runnable { private Resource resource; public Input(Resource resource) { this.resource=resource; } @Override public void run() { int x =0; while(true){ synchronized (resource) { if(x==0){ resource.setName("张三"); resource.setSex("男"); }else{ resource.setName("lili"); resource.setSex("女女女"); } x=(x+1)%2; } } }}/** * * @author 取资源的线程 * */class Output implements Runnable { private Resource resource; public Output(Resource resource) { this.resource=resource; } @Override public void run() { while(true){ synchronized (resource) { System.out.println(resource.getName()+"..."+resource.getSex()); } } }}
运行结果
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
张三...男
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
lili...女女女
虽然已经解决了张三。。。女和lili...男的问题,但是出现的大片的张三...男和大片的lili...女,这是为什么呢
当tin线程获得cpu的执行权后,执行了setName和setSext方法,cpu的执行权依然在tin线程手里,又一次执行了setName和setSext方法,覆盖了之前的setName和setSex方法,之后的同理。。。。。,那么又该如何实现存一个取一个呢?java的多线程中存在等待唤醒机制,代码如下
public class Test1 { public static void main(String[] args) { Resource1 r = new Resource1(); Input1 input = new Input1(r); Output1 output = new Output1(r); new Thread(input).start(); new Thread(input).start(); new Thread(output).start(); new Thread(output).start(); }}/** * 两个线程共同的资源 * @author WangShuang * */class Resource1{ private String name; private String sex; private int count; private boolean flag;//添加一个标记用来表示Resource中的资源是否为空(Input以后代表存入不为空,Output以后代表取出为空) public String getOutput() { System.out.println(Thread.currentThread().getName()+"消费了一个"+sex+"---------------"+name); return name+"---"+sex; } public void setInput(String name,String sex) { this.name = name+count++; this.sex = sex; System.out.println(Thread.currentThread().getName()+"生产了一个"+this.sex+"---"+this.name); } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; }}/** * 存资源的线程 * @author WangShuang * */class Input1 implements Runnable { private Resource1 resource; public Input1(Resource1 resource) { this.resource=resource; } @Override public void run() { int x =0; while(true){ synchronized (resource) { if(resource.isFlag()){//如果flag是真,代码资源库中的资源还没有被取走,此时该线程应该放弃cpu的执行权,并把另一个线程叫醒 try {resource.wait();} catch (InterruptedException e) {e.printStackTrace();} } try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//此处的线程等待是为了方便看运行结果 if(x==0){ resource.setInput("张三","男"); }else{ resource.setInput("lili","女"); } x=(x+1)%2; resource.setFlag(true); resource.notify(); } } }}/** * * @author 取资源的线程 * */class Output1 implements Runnable { private Resource1 resource; public Output1(Resource1 resource) { this.resource=resource; } @Override public void run() { while(true){ synchronized (resource) { if(!resource.isFlag()){//如果flag是真,代码资源库中的资源还没有被取走,此时该线程应该放弃cpu的执行权,并把另一个线程叫醒 try {resource.wait();} catch (InterruptedException e) {e.printStackTrace();} } try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//此处的线程等待是为了方便看运行结果 resource.getOutput(); resource.setFlag(false); resource.notify(); } } }}
运行结果如下:
Thread-0生产了一个男---张三0
Thread-3消费了一个男---------------张三0
Thread-1生产了一个男---张三1
Thread-2消费了一个男---------------张三1
Thread-1生产了一个女---lili2
Thread-3消费了一个女---------------lili2
Thread-0生产了一个女---lili3
Thread-3消费了一个女---------------lili3
Thread-0生产了一个男---张三4
Thread-3消费了一个男---------------张三4
Thread-1生产了一个男---张三5
Thread-2消费了一个男---------------张三5
Thread-1生产了一个女---lili6
Thread-3消费了一个女---------------lili6
Thread-0生产了一个女---lili7
Thread-3消费了一个女---------------lili7
Thread-0生产了一个男---张三8
Thread-3消费了一个男---------------张三8
Thread-0生产了一个女---lili9
Thread-3消费了一个女---------------lili9
Thread-0生产了一个男---张三10
Thread-2消费了一个男---------------张三10
Thread-0生产了一个女---lili11
Thread-1生产了一个男---张三12
Thread-0生产了一个男---张三13
Thread-1生产了一个女---lili14
Thread-0生产了一个女---lili15
Thread-2消费了一个女---------------lili15
Thread-3消费了一个女---------------lili15
Thread-1生产了一个男---张三16
Thread-2消费了一个男---------------张三16
Thread-3消费了一个男---------------张三16
Thread-1生产了一个女---lili17
出现了生产一个,消费2个的状态,那么这是为什么呢?
首先明白一件事:不具备执行资格的线程存在于内存中的线程池中,唤醒的线程的顺序,是谁先被等待,谁先被唤醒
当Thread-0获得cpu的执行权时,先判断资源是否为空,是开始生产资源,然后把判断资源是否为空的标记flag,设置为true,唤醒一个线程,Thread-0继续拥有cpu的执行权,先判断资源是否为空,不是空,wait()等待。然后Thread-1获得cpu的执行权,先判断资源是否为空,不是空,wait()等待。然后Thread-2获得cpu的执行权,先判断资源是否为空,不是空,取出资源,然后flag为false,唤醒一个线程,然后Thread-3开始执行,先判断资源是否为空,是空,wait()等待,然后Thread-0获得cpu的执行权,上次Thread-0失去cpu的执行权时是通过resource.wait(),接着继续运行,生产资源,设置flag为true,唤醒一个线程,Thread-1获得cpu的执行权,上次Thread-1失去cpu的执行权时是通过resource.wait(),接着继续运行,生产资源,因此产生了生产两个资源的现象,设置flag为true,唤醒一个线程,然后Thread-2获得cpu的执行权,因为上次失去cpu的执行权是在resource.wait(),所以继续执行,不用判断资源是否为空,取出资源,然后flag为false,唤醒一个线程,然后Thread-3开始执行,因为上次失去cpu的执行权是在resource.wait(),所以继续执行,不用判断资源是否为空,取出资源,因此产生了消费两个相同资源的现象
注意现在的线程顺序是我自己本人想象出来的cpu的执行顺,cpu的执行顺序是随机的,所以各种现象都能解释的通,例如生产了两个,消费了一个,生产一个消费两个,生产两个不一样的,消费2个一样的(上述解释)等等,那么这个问题该如何解决呢?
产生上述问题的原因是因为,没有判断标记,那么
把判断资源是否为空的地方if(resource.getFlag())改成while(resource.getFlag()),这样就可以循环的判断标记了,当我们再次运行程序时,会发现Thread-0,Thread-1,Thread-2,Thread-3,全部等待的状态,产生这种现象的原因,自己按照代码的执行顺序自己理一遍就明白了,那么又该怎么办呢?
唤醒时,不要只唤醒一个线程,全部都唤醒好了notifyAll()
下面的代码是最后的并且简化后的代码
public class Test { public static void main(String[] args) { Resource r = new Resource(); Input input = new Input(r); Output output = new Output(r); new Thread(input).start(); new Thread(input).start(); new Thread(output).start(); new Thread(output).start(); }}/** * 两个线程共同的资源 * @author WangShuang * */class Resource{ private String name; private String sex; private int count; private boolean flag;//添加一个标记用来表示Resource中的资源是否为空(Input以后代表存入不为空,Output以后代表取出为空) public synchronized String getOutput() { while(flag){//如果flag是真,代码资源库中的资源还没有被取走,此时该线程应该放弃cpu的执行权,并把另一个线程叫醒 try {this.wait();} catch (InterruptedException e) {e.printStackTrace();} } System.out.println(Thread.currentThread().getName()+"消费了一个"+sex+"---------------"+name); flag=true; this.notifyAll(); return name+"---"+sex; } public synchronized void setInput(String name,String sex) { while(!flag){//如果flag是假,代码资源库中的资源已经被取走,此时该线程应该放弃cpu的执行权,并把另一个线程叫醒 try {this.wait();} catch (InterruptedException e) {e.printStackTrace();} } this.name = name+count++; this.sex = sex; System.out.println(Thread.currentThread().getName()+"生产了一个"+this.sex+"---"+this.name); flag=false; this.notifyAll(); }}/** * 存资源的线程 * @author WangShuang * */class Input implements Runnable { private Resource resource; public Input(Resource resource) { this.resource=resource; } @Override public void run() { int x =0; while(true){ if(x==0){ resource.setInput("张三","男"); }else{ resource.setInput("lili","女"); } x=(x+1)%2; } }}/** * * @author 取资源的线程 * */class Output implements Runnable { private Resource resource; public Output(Resource resource) { this.resource=resource; } @Override public void run() { while(true){ resource.getOutput(); } }}
共同学习,写下你的评论
评论加载中...
作者其他优质文章