前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >楠哥教你学 Java|JUC 并发编程 002 期

楠哥教你学 Java|JUC 并发编程 002 期

作者头像
南风
发布2020-05-13 17:27:57
5370
发布2020-05-13 17:27:57
举报
文章被收录于专栏:Java大联盟Java大联盟Java大联盟

1、sleep 和 wait 的区别

wait 的功能和 sleep 类似,都是让线程暂停执行任务,但其实是两个完全不同的方法。

来自不同的类

sleep 是 Thread 类提供的方法。

wait 是 Object 类提供的方法。

作用于不同的对象

sleep 是让当前的线程实例对象暂停执行任务。

wait 是让正在访问当前对象的线程休眠,它不是针对线程对象的方法,而是针对线程对象要访问的资源的方法,即调用 A 对象的 wait 方法表示:让当前正在访问 A 对象的线程暂停,同时它有一个前提,即当前线程对象必须拥有 A 对象,所以 wait 方法只能在同步方法或同步块内部调用,否则会抛出 java.lang.IllegalMonitorStateException 异常。

是否释放锁

wait 释放锁,sleep 不释放锁。

wait 的具体使用如下所示。

public class Test {
    public static void main(String[] args) {
        A a = new A();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                a.test(i);
            }
        }).start();
    }
}


class A{
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i+"-----------");
    }
}

上述代码表示当 i=5 的时候,调用了A对象的wait方法,表示让当前访问A对象的线程即main暂停执行,进入阻塞状态,并且永远不会解除阻塞。

那如何让线程解除阻塞呢?两种方法。

1、指定 wait 的时间,调用重载方法 wait(long millis) 即可,millis 毫秒之后会自动解除阻塞,和 sleep 类似的功能。

class A{
  public synchronized void test(int i){
    if(i == 5){
      try {
        this.wait(2000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    try {
      TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(i+"-----------");
  }
}

2、通过调用 notify 方法唤醒线程。

public class Test {
    public static void main(String[] args) {
        A a = new A();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                a.test(i);
            }
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(7);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            a.test2();
        }).start();
    }
}

class A{
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i+"-----------");
    }
    
    public synchronized void test2(){
        this.notify();
    }
}

2、synchronized 锁定的是谁?

如果 synchronized 修饰非静态方法,则锁定的是方法的调用者,具体代码如下所示。

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{data.func1();},"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{data.func2();},"B").start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}

结果是先输出 1 再输出 2,如果 Data 方法不加 synchronized,则先输出 2 再输出 1,因为有延时,但是加了 synchronized,就跟锁有关系了,synchronized 锁定的是方法调用者 data,所以就算 func1 延迟 3 秒,但是 data 对象被线程 A 锁定了,线程 B 根本无法获取 data,只能等待,线程 A 执行完毕,线程 B 才能获得锁,进而执行业务,我们把代码改一下,如下所示。

public class Test {
    public static void main(String[] args) {
        Data data1 = new Data();
        Data data2 = new Data();
        new Thread(()->{data1.func1();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{data2.func2();}).start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}

这时候就先输出 2 再输出 1,因为现在是两个对象,线程 A 只是锁定了 data1,但是线程 B 锁定的是 data2,两个完全不冲突,所以不会形成线程同步。

再比如代码改为如下所示,增加第三个非 synchronized 方法。

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{data.func1();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{data.func3();}).start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }

    public void func3(){
        System.out.println("3...");
    }
}

结果先输出 3 再输出 1,因为 func3 没有 synchronized,所以它就不需要去争夺锁,就不是同步方法,所以不会去排队。

如果 synchronized 修饰的是静态方法,则锁定的是类,无论多少个对象调用,都会同步,如下所示。

public class Test {
    public static void main(String[] args) {
        Data data1 = new Data();
        Data data2 = new Data();
        new Thread(()->{data1.func1();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{data2.func2();}).start();
    }
}

class Data{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }
    public synchronized static void func2(){
        System.out.println("2...");
    }
}

虽然分别用 data1 和 data2 对象调用 func1 和 func2,但是因为 func1 和 func2 都是静态方法,所以锁定的并不是对象,而是 Data 类,因为 Data 类只有一个,所以线程同步。

如果 synchronized 静态方法和 synchronized 实例方法同时存在,静态方法锁的是类,实例方法锁对象。

public class Test {
    public static void main(String[] args) {
        Data data1 = new Data();
        Data data2 = new Data();
        new Thread(()->{data1.func1();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{data2.func2();}).start();
    }
}

class Data{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }
    public synchronized void func2(){
        System.out.println("2...");
    }
}

上述代码不会实现同步,各锁各的,互不影响,修改代码如下所示,只保留一个对象,结果不变,一个锁类,一个锁对象。

public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{data.func1();}).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{data.func2();}).start();
    }
}

class Data{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }
    
    public synchronized void func2(){
        System.out.println("2...");
    }
}

如果 synchronized 修饰的是代码块,则锁定的就是传入的对象,能否实现线程同步,就看锁定的对象是否是同一个对象。

public class Test2 {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        for(int i=0;i<5;i++){
            Integer num = Integer.valueOf(1);
            new Thread(()->{
                data2.func(num);
            }).start();
        }
    }
}

class Data2{
    public void func(Integer num){
        synchronized (num){
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

上述代码的结果如下所示。

实现了线程同步,因为有排队,那有的同学会问了,明明每次传入的 num 都是不同的对象,为什么还会同步呢?因为虽然是 5 个不同的 num,但是它们是包装类,当 Integer 的值大于 -128 小于等于 127 的时候,会使用包装类常量池,所以 5 个 num 是同一个对象,把 Integer 的值改为 128 ,结果如下所示。

没有同步,因为此时 5 个 num 不是同一个对象,同理,如果使用构造器创建 num,即使值一样也不会同步。

public class Test2 {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        for(int i=0;i<5;i++){
            Integer num = new Integer(1);
            new Thread(()->{
                data2.func(num);
            }).start();
        }
    }
}

class Data2{
    public void func(Integer num){
        synchronized (num){
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

使用其他类也是一样,不会同步的,如下所示。

public class Test2 {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        for(int i=0;i<5;i++){
            A a = new A();
            new Thread(()->{
                data2.func(a);
            }).start();
        }
    }
}

class Data2{
    public void func(A a){
        synchronized (a){
            System.out.println("start...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}
class A{}

结果如下所示。

public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(()->{
            account.count();
        },"A").start();
        new Thread(()->{
            account.count();
        },"B").start();
    }
}

class Account{
    private Integer num = 0;
    private Integer id = 0;
    public void count(){
        synchronized (num){
            num++;
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
        }
    }
}

上述代码中,如果锁定 num 不能同步,锁定 id 可以同步,原因是什么?

因为 synchronized 必须锁定唯一的元素才可以实现同步。

num 的值每次都在变,所以 num 所指向的引用一直在变,不是唯一的元素,肯定无法实现同步。

id 的值永远不变,所以是唯一的元素,可以实现同步。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java大联盟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档