线程同步机制 ★★★★

线程同步机制★★★★

1、synchronized-线程同步

线程同步机制的语法是:

synchronized(){
	// 线程同步代码块。
}

synchronized()小括号内容是至关重要的,它必须得是要同步的多个线程所共享的数据


举个例子:

假设目前程序内共有t1到t5五个线程,我只想让t1,t2,t3排队,其余线程不排队,要怎么办呢?

一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。

在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。100个对象,100把锁。1个对象1把锁。


2、eg.理解线程同步

/**
 * 通过代码来理解线程同步synchronized
 * 多线程共享同一银行账户,取钱
 */
public class Test {
    public static void main(String[] args) {
        // 创建账户对象(只创建1个)
        Account act = new Account("act-001", 10000);
        // 创建两个线程,共享同一个对象
        Thread t1 = new Account.AccountThread(act);
        Thread t2 = new Account.AccountThread(act);

        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
    static class Account {
        private String actno;//账户编号
        private double balance;//账户余额

        //对象
        Object o = new Object(); // 实例变量。(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)

        public Account() {
        }

        public Account(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }

        public String getActno() {
            return actno;
        }

        public void setActno(String actno) {
            this.actno = actno;
        }

        public double getBalance() {
            return balance;
        }

        public void setBalance(double balance) {
            this.balance = balance;
        }

        //取款方法
        public void withdraw(double money) {
            /**
             * 以下可以共享,金额不会出错
             * 以下这几行代码必须是线程排队的,不能并发。
             * 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
             */
            synchronized (this) {
                //synchronized(actno) {
                //synchronized(o) {

                /**
                 * 以下不共享,金额会出错
                 */
           //Object obj2 = new Object();
           //synchronized(obj2) { // 这样编写就不安全了。因为obj2不是共享对象。
           //String s = null;
           //synchronized(null) {//编译不通过
           //synchronized(s) {//java.lang.NullPointerException
                double before = this.getBalance();
                double after = before - money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.setBalance(after);
                //}
            }
        }

        static class AccountThread extends Thread {
            // 两个线程必须共享同一个账户对象。
            private Account act;

            // 通过构造方法传递过来账户对象
            public AccountThread(Account act) {
                this.act = act;
            }

            public void run() {
                double money = 5000;
                act.withdraw(money);
                System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款" + money + "成功,余额" + act.getBalance());
            }
        }
    }
}

如何判断是否共享?

  1. 如果某个对象是多个线程所共享的,那么该对象中的实例变量自然也是这几个线程所共享的,如代码中的下图部分

 /**
 * 以下可以共享,金额不会出错
 * 以下这几行代码必须是线程排队的,不能并发。
 * 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
 */
	synchronized (this)
	synchronized(actno)
	synchronized(o)
  1. 在新方法中创建的实例变量不属于在线程方法中定义的对象Account的实例变量,这些实例变量自然就不是多个线程所共享的

2、运行结果

观察以下几张图片的运行结果,证明对象或实例变量是否共享

以上代码锁this、实例变量actno、实例变量o都可以!因为这三个被多个线程共享

共享



查看结果可知,t1,t2两个线程排队对act-001账户对象进行取款操作,说明this为多线程共享



查看结果可知,t1,t2两个线程排队对act-001账户对象进行取款操作,说明实例变量actno为多线程共享



查看结果可知,t1,t2两个线程排队对act-001账户对象进行取款操作,说明实例变量o为多线程共享


不共享


用同样的方式测试后发现,不属于线程方法中定义的对象Account的实例变量都是不共享的

obj2,null,s都不共享


3、线程同步原理

  • 假设t1和t2线程并发,开始执行代码的时候,肯定有一个先一个后

  • 假设t1先执行了,遇到了synchronized,这个时候自动找“共享对象”的对象锁
    找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
    占有这把锁的。直到同步代码块代码结束,这把锁才会释放。

  • 假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有共享对象的这把锁

    结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束
    直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
    t2占有这把锁之后,进入同步代码块执行程序。

    这样就实现了线程排队执行。


4、在实例方法中可以使用synchronized(不推荐)

  • 举个例子,在上边的代码中,synchronized是可以在实例方法中使用的,但是,此时的锁只能是this!没得挑,只能是this,不能是其他的对象。所以这种方式不灵活

  • synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低。所以这种方式不常用。但这种方式可以精简代码。

  • 如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。


1、eg.

在实例方法中使用synchronized

    public synchronized void withdraw(double money){
        double before = this.getBalance();
        double after = before - money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }

在方法调用处使用synchronized

    public void run(){
        double money = 5000;
        // 取款
        // 多线程并发执行这个方法。
        //synchronized (this) { //这里的this是AccountThread对象,这个对象不共享!
        synchronized (act) { // 这种方式也可以,只不过扩大了同步的范围,效率更低了。
            act.withdraw(money);
        }

        System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
    }

2、疑问(待解决)


为什么this不共享?运行发现也没有问题啊?待解决

posted @   无关风月7707  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示