栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java并发编程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java并发编程

  在调用wait方法时,线程必须要持有被调用对象的锁,当调用wait方法后,线程就会释放掉对象的锁(monitor);在调用Thread类的sleep方法时,线程是不会释放掉对象的锁的(阻塞)。

package com.msz;

public class msz_001_test {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            object.wait();
        }
    }
}

关于wait与notify和notifyAll方法的总结:

  ①当调用wait时,首先需要确保调用了wait方法的线程已经持有了对象的锁;
  ②当调用wait后,该线程就会释放掉这个对象的锁,然后进入到等待状态(wait set);
  ③当线程调用了wait后进入到等待状态时,它就可以等待其他线程调用相同对象的notify或者notifyAll方法来使自己被唤醒;
  ④一旦这个线程被其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争),只有当该线程获取到了这个对象的锁后,线程才会继续往下执行;
  ⑤调用wait方法的代码片段需要放在一个synchronized块或是synchronized方法中,这样才可以确保线程在调用wait方法前已经获取到了对象的锁;
  ⑥当调用对象的notify方法时,它会随机唤醒该对象等待集合(wait set)中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁;
  ⑦当调用对象的notifyAll方法时,它会唤醒该对象等待集合(wait set)中的所有线程,这些线程被唤醒后,又开始竞争对象的锁;
  ⑧在某一时刻,只有唯一 一个线程可以拥有对象的锁。


public class Test {
	public synchronized void method_1() {
	}
	public synchronized void method_2() {
	}
}

Test test = new Test();
  当某一个线程执行这个Test对象的method_1方法时但是还没有执行完,此时其他线程是不能再执行method_2方法的,因为对于同一个对象来说它的所有synchronized方法的锁的对象或者monitor是一个东西;当一个线程执行了method_1方法获取到对象锁时,其他线程是无论如何也不能执行method_2方法的。


Test test = new Test();
Test test_1 = new Test();
  第一个对象执行method_1方法,第二个对象执行method_2方法;是能够行得通的,因为是两个对象,它们是两个不同的锁或者monitor,第一个线程获取第一个对象的锁,第二个线程获取第二个对象的锁,相互之间没有影响的。


public class Test {
	public synchronized void method_1() {
	}
	public static synchronized void method_2() {
	}
}

Test test = new Test();
  一个线程执行上面这个对象的method_1和method_2方法是行得通的;因为method_1方法synchronized锁的是Test对象,而method_2方法synchronized锁的是Test对象所对应的class对象。一个是当前对象,一个是当前对象对应的class对象,所以锁的对象或者monitor不是一个东西;故而一个线程可以同时执行这两个方法;两个不同的对象拥有自己不同的锁或者monitor。


题目:编写一个多线程程序,实现目标:

  ①存在一个对象,该对象有一个int类型的成员成员变量counter,该成员变量的初始值为0;
  ②创建两个线程,其中一个线程对该对象的成员变量counter增1,另一个线程对该对象的成员变量减1;
  ③输出该对象成员变量counter每次变化后的值;
  ④最终输出的结果应为:1010101010…

package com.msz;

public class msz_002_MyObject {

    private int counter;

    public synchronized void increase() {
        if (counter  != 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counter++;
        System.out.println(counter);
        notify();
    }
    public synchronized void decrease() {
        if (counter == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counter--;
        System.out.println(counter);
        notify();
    }
}
package com.msz;

public class msz_003_IncreaseThread extends Thread {
    private msz_002_MyObject myObject;

    public msz_003_IncreaseThread(msz_002_MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        for (int i=0; i<30; ++i) {
            try {
                Thread.sleep((long)Math.random() * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myObject.increase();
        }
    }
}
package com.msz;

public class msz_004_DecreaseThread extends Thread{
    private msz_002_MyObject myObject;

    public msz_004_DecreaseThread(msz_002_MyObject myObject) {
        this.myObject = myObject;
    }

    @Override
    public void run() {
        for (int i=0; i<30; ++i) {
            try {
                Thread.sleep((long)Math.random() * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myObject.decrease();
        }
    }
}
package com.msz;

import sun.misc.InnocuousThread;

public class msz_005_Client {
    public static void main(String[] args) {
        msz_002_MyObject myObject = new msz_002_MyObject();
        Thread increaseThread = new msz_003_IncreaseThread(myObject);
        Thread decreaseThread = new msz_004_DecreaseThread(myObject);
        increaseThread.start();
        decreaseThread.start();
    }
}

package com.msz;

import sun.misc.InnocuousThread;

public class msz_005_Client {
    public static void main(String[] args) {
        msz_002_MyObject myObject = new msz_002_MyObject();
        Thread increaseThread = new msz_003_IncreaseThread(myObject);
        Thread increaseThread2 = new msz_003_IncreaseThread(myObject);
        Thread decreaseThread = new msz_004_DecreaseThread(myObject);
        Thread decreaseThread2 = new msz_004_DecreaseThread(myObject);
        increaseThread.start();
        increaseThread2.start();
        decreaseThread.start();
        decreaseThread2.start();
    }
}

  当启动四个线程时程序出现两个问题:
  ①输出结果不正确;
  ②程序不退出(挂起状态)
  
  分析原因:当程序启动两个线程情况下,假如一个线程执行一个线程等待,当这个执行线程执行到notify时,唤醒的必然是另外一个等待的线程,以此相互【等待唤醒】【唤醒等待】,故能够完成题目中的需求。当线程启动四个或者更多的时候线程被唤醒时需要再次先判断一下counter然后再执行逻辑,所以使用while,并且在执行过程中可能多个线程处于等待状态,所以唤醒用notifyAll。原程序执行四个线程出现的问题耐心逐步分析便可找到事发原由。下面是正确的程序代码:

package com.msz;

public class msz_002_MyObject {

    private int counter;

    public synchronized void increase() {
        while (counter  != 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counter++;
        System.out.println(counter);
        notifyAll();
    }
    public synchronized void decrease() {
        while (counter == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        counter--;
        System.out.println(counter);
        notifyAll();
    }
}

synchronized关键字:

  如果一个对象里面有若干个synchronized方法,那么在某一时刻只能有唯一的一个线程进入到这里面的其中一个synchronized方法(那么在某一时刻这些synchronized方法当中只会有唯一的一个synchronized方法被某一个线程访问,而其他的线程即便想要访问另外的synchronized方法,也要去等待);因为当一个对象里面有若干个synchronized方法的时候,线程要执行其中一个方法需要尝试获取“锁”,这个锁是当前对象的锁,而当前对象只有唯一的一把锁。
  当一个线程去访问某一个对象的
synchronized static这样一个方法的时候它需要尝试获取的锁并不是当前对象的锁,而是当前对象所对应的class对象的锁。本质上来说,一个静态的方法并不属于当前对象,它是属于当前对象所对应的class对象。方法执行完或者抛出异常都会释放锁。

  当我们使用synchronized关键字来修饰代码块时,字节码层面上是通过monitorenter与monitorexit指令来实现的锁的获取与释放动作。
  当线程进入到monitorenter指令后,线程将会持有Monitor对象,退出monitorenter指令后,线程将会释放Monitor对象。

  对于synchronized关键字修饰方法来说,并没有出现monitorentor与moitorexit指令,而是出现了一个ACC_SYNCHRONIZED标志。JVM使用了ACC_SYNCHRONIZED访问标志来区分一个方法是否为同步方法;当方法被调用时,调用指令会检查该方法是否拥有ACC_SYNCHRONIZED标志,如果有,那么执行线程将会先持有方法所在对象的Monitor对象,然后再去执行方法体;在该方法执行期间,其他任何线程均无法再获取到这个Monitor对象,当线程执行完该方法后,它会释放掉这个Monitor对象。


待更新...
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/341122.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号