synchronized关键字的用法、使用wait及notify方法实现线程间的通信

1.多线程的同步

为什么要引入同步机制

  • 在多线程环境中,可能会有两个甚至更多的线程试图同时 访问一个有限的资源。必须对这种潜在资源冲突进行预防。
  • 解决方法:在线程使用一个资源时为其加锁即可。访问资 源的第一个线程为其加上锁以后,其他线程便不能再使用 那个资源,除非被解锁。

问题代码举例

俩个线程同时取账户的钱

/**
 * @ClassName FetchMoney
 * @author: shouanzh
 * @Description 测试
 * @date 2022/3/5 12:27
 */
public class FetchMoney {

    public static void main(String[] args) {

		// 同一资源
        Bank bank = new Bank();

//        MoneyRunnableTherd moneyRunnableTherd = new MoneyRunnableTherd();
//        moneyRunnableTherd.setBank(bank);
//        Thread thread = new Thread(moneyRunnableTherd);
//        Thread thread2 = new Thread(moneyRunnableTherd);
//        thread.start();
//        thread2.start();

        MoneyTherd moneyTherd = new MoneyTherd(bank);
        moneyTherd.setName("银行柜台");

        MoneyTherd moneyTherd2 = new MoneyTherd(bank);
        moneyTherd2.setName("ATM");

        moneyTherd.start();
        moneyTherd2.start();

    }

}

// 个人账户
class Bank {

    private int money = 1000;

    public int getMoney(int number) {

        if (number < 0) {
            return -1;
        } else if (number > money) {
            return -2;
        } else if (money < 0) {
            return -3;
        } else {
            try {
                // 取钱模拟耗时
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            money -= number;
            System.out.println("剩余 money:" + money);
            return number;
        }
    }
}

// 取钱线程extends Thread
class MoneyTherd extends Thread {

    private Bank bank;

    public MoneyTherd(Bank bank) {
        this.bank = bank;
    }

    @Override
    public void run() {
        // 取出800
        System.out.println("取出:" + bank.getMoney(800));
    }
}

// 取钱线程implements Runnable
class MoneyRunnableTherd implements Runnable {

    private Bank bank;

    public void setBank(Bank bank) {
        this.bank = bank;
    }

    @Override
    public void run() {
        // 取出800
        System.out.println("取出:" + bank.getMoney(800));
    }
}

运行结果
在这里插入图片描述

2.synchronized修饰方法

  • synchronized 关键字:当 synchronized 关键字修饰一个方 法的时候,该方法叫做同步方法。
  • Java 中的每个对象都有一个锁(lock)或者叫做监视器 (monitor),当访问某个对象的 synchronized 方法时,表 示将该对象上锁,此时其他任何线程都无法再去访问该 synchronized 方法了,直到之前的那个线程执行方法完毕 后(或者是抛出了异常),那么将该对象的锁释放掉,其 他线程才有可能再去访问该synchronized 方法。
  • 如果一个对象有多个 synchronized 方法,某一时刻某个 线程已经进入到了某个 synchronized 方法,那么在该方法 没有执行完毕前,其他线程是无法访问该对象的任何 synchronized 方法的。

举例一

/**
 * @ClassName FetchMoney
 * @author: shouanzh
 * @Description 测试
 * @date 2022/3/5 12:27
 */
public class FetchMoney {

    public static void main(String[] args) {

        Bank bank = new Bank();

        MoneyTherd moneyTherd = new MoneyTherd(bank);
        moneyTherd.setName("银行柜台");

        MoneyTherd moneyTherd2 = new MoneyTherd(bank);
        moneyTherd2.setName("ATM");

        moneyTherd.start();
        moneyTherd2.start();

    }

}

// 个人账户
class Bank {

    private int money = 1000;

    public synchronized int getMoney(int number) {

        if (number < 0) {
            return -1;
        } else if (number > money) {
            return -2;
        } else if (money < 0) {
            return -3;
        } else {
            try {
                // 取钱模拟耗时
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            money -= number;
            System.out.println("剩余 money:" + money);
            return number;
        }
    }
}

// 取钱线程
class MoneyTherd extends Thread {

    private Bank bank;

    public MoneyTherd(Bank bank) {
        this.bank = bank;
    }

    @Override
    public void run() {
        // 取出800
        System.out.println("取出:" + bank.getMoney(800));
    }
}

运行结果:
在这里插入图片描述
举例二
如果一个对象有多个 synchronized 方法,某一时刻某个 线程已经进入到了某个 synchronized 方法,那么在该方法 没有执行完毕前,其他线程是无法访问该对象的任何 synchronized 方法的。

/**
 * @ClassName ThredTeat1
 * @author: shouanzh
 * @Description ThredTeat1
 * @date 2022/3/5 13:31
 */
public class ThredTeat1 {

    public static void main(String[] args) {

        Example example = new Example();

        TheThred theThred = new TheThred(example);
        TheThred2 theThred2 = new TheThred2(example);

        theThred.start();
        theThred2.start();


    }

}

class Example {

    public synchronized void execute ()  {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(500);
                System.out.println("hello:" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void execute2 ()  {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(500);
                System.out.println("word:" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class TheThred extends Thread {

    private Example example;

    public TheThred(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute();
    }

}

class TheThred2 extends Thread {

    private Example example;

    public TheThred2(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute2();
    }

}

运行结果:

hello:0
hello:1
hello:2
hello:3
hello:4
word:0
word:1
word:2
word:3
word:4

Process finished with exit code 0

3.synchronized修饰static方法

如果某个 synchronized方法是 static的, ,那么当线程访问该方法时, 它锁的并不是synchronized方法所在的对象,而是synchronized 方 法所在的对象所对应的Class对象,因为Java中无论一个类有多少 个对象,这些对象会对应唯一一个Class对象,因此当线程分别访 问同一个类的两个对象的两个 static, synchronized 方法时,他们 的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行 完毕后另一个线程才开始执行。

static的方法属于类方法,它属于这个Class(注意:这里的Class不是指Class的某个具体对象),那么static获取到的锁,就是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。而‘非static’方法获取到的锁,就是当前调用这个方法的对象的锁了。所以,他们之间不会产生互斥。

public class ThredTeat1 {

    public static void main(String[] args) {

        Example example = new Example();

        TheThred theThred = new TheThred(example);
        TheThred2 theThred2 = new TheThred2(example);

        theThred.start();
        theThred2.start();


    }

}

class Example {

    public synchronized void execute ()  {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(500);
                System.out.println("hello:" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized static void execute2 ()  {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(500);
                System.out.println("word:" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class TheThred extends Thread {

    private Example example;

    public TheThred(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute();
    }

}

class TheThred2 extends Thread {

    private Example example;

    public TheThred2(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute2();
    }

}

运行结果: execute() 和 execute2() 同时执行,说明锁的不是同一个对象

hello:0
word:0
word:1
hello:1
word:2
hello:2
word:3
hello:3
word:4
hello:4

Process finished with exit code 0

举例二

public class ThredTeat1 {

    public static void main(String[] args) {

        Example example = new Example();

        TheThred theThred = new TheThred(example);

        example = new Example();

        TheThred2 theThred2 = new TheThred2(example);

        theThred.start();
        theThred2.start();


    }

}

class Example {

    public synchronized static void execute ()  {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(500);
                System.out.println("hello:" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized static void execute2 ()  {
        try {
            for (int i = 0; i < 5; i++) {
                Thread.sleep(500);
                System.out.println("word:" + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class TheThred extends Thread {

    private Example example;

    public TheThred(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute();
    }

}

class TheThred2 extends Thread {

    private Example example;

    public TheThred2(Example example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute2();
    }

}

运行结果

hello:0
hello:1
hello:2
hello:3
hello:4
word:0
word:1
word:2
word:3
word:4

Process finished with exit code 0

4.synchronized代码块

synchronized代码块写法

synchronized(object) {

}
表示线程执行时会对object上锁

/**
 * @ClassName TherdTest2
 * @author: shouanzh
 * @Description synchronized代码块
 * @date 2022/3/5 14:50
 */
public class TherdTest2 {

    public static void main(String[] args) {

        Example2 example2 = new Example2();

        ThredTest thredTest = new ThredTest(example2);
        ThredTest1 thredTest1 = new ThredTest1(example2);

        thredTest.start();
        thredTest1.start();

    }

}

class Example2 {

    private Object object = new Object();

    public void execute() {

        synchronized (object) {
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(500);
                    System.out.println("hello:" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public void execute2() {
        synchronized (object) {
            try {
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(500);
                    System.out.println("word:" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class ThredTest extends Thread {

    private Example2 example;

    public ThredTest(Example2 example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute();
    }

}

class ThredTest1 extends Thread {

    private Example2 example;

    public ThredTest1(Example2 example) {
        this.example = example;
    }

    @Override
    public void run() {
        example.execute2();
    }

}

运行结果

hello:0
hello:1
hello:2
hello:3
hello:4
word:0
word:1
word:2
word:3
word:4

Process finished with exit code 0

5.小结

synchronized 方法是一种粗粒度的并发控制,某一时刻, 只能有一个线程执行该synchronized 方法;
synchronized 块 则是一种细粒度的并发控制,只会将块中的代码同步,位 于方法内、synchronized 块之外的代码是可以被多个线程 同时访问到的。

6.使用wait及notify方法实现线程间的通信

wait 与 notify 方法都是定义在 Object 类中,而且是 final 的,因此会被所有的 Java类所继承并且无法重写。这两个 方法要求在调用时线程应该已经获得了对象的锁,因此对 这两个方法的调用需要放在synchronized方法或块当中。

当线程执行了 wait 方法时,它会释放掉对象的锁。

另一个会导致线程暂停的方法就是 Thread 类的 sleep方法, 它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中 是不会释放掉对象的锁的。

代码举例

/**
 * @ClassName Sample
 * @author: shouanzh
 * @Description Sample
 * @date 2022/3/5 16:04
 */
public class Sample {

    private int number;

    public synchronized void increase() {
        while (number != 0) {
            try {
                // wait() 会释放锁、释放cpu  所以每次被唤醒 都要重新检查状态,因为不知道等待期间发生了什么
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        number++;
        System.out.println(number);

        notify();

    }

    public synchronized void decrease() {

        while (number == 0) {
            try {
                // wait() 会释放锁、释放cpu  所以每次被唤醒 都要重新检查状态,因为不知道等待期间发生了什么
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        number--;
        System.out.println(number);

        notify();
    }


}
/**
 * @ClassName IncreaseThred
 * @author: shouanzh
 * @Description IncreaseThred
 * @date 2022/3/5 16:13
 */
public class IncreaseThred extends Thread{

    private Sample sample;

    public IncreaseThred(Sample sample) {
        this.sample = sample;
    }

    @Override
    public void run() {

        try {
            for (int i = 0; i < 10; i++) {

                Thread.sleep(600);

                sample.increase();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

/**
 * @ClassName DecreaseThred
 * @author: shouanzh
 * @Description DecreaseThred
 * @date 2022/3/5 16:18
 */
public class DecreaseThred extends Thread{

    private Sample sample;

    public DecreaseThred(Sample sample) {
        this.sample = sample;
    }

    @Override
    public void run() {

        try {
            for (int i = 0; i < 10; i++) {

                Thread.sleep(500);

                sample.decrease();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

/**
 * @ClassName Maintest
 * @author: shouanzh
 * @Description Maintest
 * @date 2022/3/5 16:19
 */
public class Maintest {

    public static void main(String[] args) {

        Sample sample = new Sample();

        IncreaseThred increaseThred = new IncreaseThred(sample);
        DecreaseThred decreaseThred = new DecreaseThred(sample);
        IncreaseThred increaseThred1 = new IncreaseThred(sample);
        DecreaseThred decreaseThred1 = new DecreaseThred(sample);

        increaseThred.start();
        decreaseThred.start();
        increaseThred1.start();
        decreaseThred1.start();

    }

}

运行结果

1
0
1
0
1
0
1
0
1
0
1
0
1


Process finished with exit code 0

线程状态图
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值