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

2020-05-13 17:27:57 浏览数 (1)

1、sleep 和 wait 的区别

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

来自不同的类

sleep 是 Thread 类提供的方法。

wait 是 Object 类提供的方法。

作用于不同的对象

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

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

是否释放锁

wait 释放锁,sleep 不释放锁。

wait 的具体使用如下所示。

代码语言:javascript复制
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 类似的功能。

代码语言:javascript复制
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 方法唤醒线程。

代码语言:javascript复制
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 修饰非静态方法,则锁定的是方法的调用者,具体代码如下所示。

代码语言:javascript复制
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 才能获得锁,进而执行业务,我们把代码改一下,如下所示。

代码语言:javascript复制
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 方法。

代码语言:javascript复制
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 修饰的是静态方法,则锁定的是类,无论多少个对象调用,都会同步,如下所示。

代码语言:javascript复制
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 实例方法同时存在,静态方法锁的是类,实例方法锁对象。

代码语言:javascript复制
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...");
    }
}

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

代码语言:javascript复制
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 修饰的是代码块,则锁定的就是传入的对象,能否实现线程同步,就看锁定的对象是否是同一个对象。

代码语言:javascript复制
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,即使值一样也不会同步。

代码语言:javascript复制
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...");
        }
    }
}

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

代码语言:javascript复制
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{}

结果如下所示。

代码语言:javascript复制
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 的值永远不变,所以是唯一的元素,可以实现同步。

0 人点赞