Java篇--多线程二

一、安全问题引入

多线程在操作共享数据或者是操作数据的线程代码有多行,如果有其他线程也参与了运算,就会导致线程的安全问题。

package com.xiaoqiang.interview.thread;

/**
 * Created by Huiq on 2021/4/13.
 */
public class ThreadDemo06 {

    public static void main(String[] args) {
        Bank bank = new Bank();
        Runnable runnable = new MoneyThread(bank);
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }
}

class Bank {
    private int money = 1000;

    public int get(int number) {
        if (number < 0) {
            System.out.println("不能取负数的钱。。。");
            return -1;
        } else if (number > money) {
            System.out.println("余额不足1。。。");
            return -2;
        } else if (money < 0) {
            System.out.println("余额不足2。。。");
            return -3;
        } else {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            money -= number;
            System.out.println("还剩:" + money);
            return number;
        }
    }
}

class MoneyThread implements Runnable {
    private Bank bank;

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

    @Override
    public void run() {
        bank.get(800);
    }
}

运行上面的代码,可能出现这三种情况:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、安全问题解决方法

加锁:synchronized关键字来完成对对象的加锁功能,同一个时间点只能有一个线程访问,只有等这个线程执行完毕或者抛出异常之后,其他的线程才可以进来。
在这里插入图片描述
例子二:

package com.xiaoqiang.interview.thread;

/**
 * Created by Huiq on 2021/4/13.
 */
public class ThreadDemo07 {

    public static void main(String[] args) {
        Example example = new Example();
        Runnable runnable = new TheThread(example);

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        thread2.start();
    }
}

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

class TheThread implements Runnable {

    private Example example;

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

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

没有synchronized的时候运行结果:
在这里插入图片描述
有synchronized的时候运行结果:
在这里插入图片描述

三、synchronized代码块

在这里插入图片描述
synchronized(object):对该object对象上锁
在这里插入图片描述
synchronized方法与synchronized代码块的区别:synchronized方法是一种粗粒度的控制,因为它控制的是整个方法,同一时刻只能有一个线程进入到该synchronized方法中。synchronized代码块是一种细粒度的控制,它只是将我们可能存在问题的代码修饰起来了。

四、线程同步拓展

上面的是两个线程对于同一个对象,改成两个线程对两个不同的对象,运行结果是各自管各自的没有任何干扰。
在这里插入图片描述
再修改一下代码,思考下运行结果是有顺序还是没有顺序的?

package com.xiaoqiang.interview.thread;

/**
 * Created by Huiq on 2021/4/13.
 */
public class ThreadDemo07 {

    public static void main(String[] args) {
        Example example = new Example();
        Runnable runnable1 = new TheThread(example);
        Runnable runnable2 = new TheThread2(example);

        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable2);

        thread1.start();
        thread2.start();
    }
}

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

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

class TheThread implements Runnable {

    private Example example;

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

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

class TheThread2 implements Runnable {

    private Example example;

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

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

答案是顺序的,当两个不同的线程方法对同一个对象的不同synchronized方法,只要哪个先进来它就立马对这个对象上把锁,其他的线程就无法进来了。运行结果为:
在这里插入图片描述

五、synchronized与static synchronized的区别

  在Java中,synchronized是用来表示同步的,我们可以synchronized来修饰一个方法。也可以synchronized来修饰方法里面的一个语句块。那么,在static方法和非static方法前面加synchronized到底有什么不同呢?大家都知道,static的方法属于类方法,它属于这个Class(注意:这里的Class不是指Class的某个具体对象),那么static获取到的锁,是属于类的锁。而非static方法获取到的锁,是属于当前对象的锁。所以,他们之间不会产生互斥。

举例:

pulbic class Something(){  
    public synchronized void isSyncA(){}  
    public synchronized void isSyncB(){}  
    public static synchronized void cSyncA(){}  
    public static synchronized void cSyncB(){}  
}

那么,假如有Something类的两个实例x与y,那么下列各组方法被多线程同时访问的情况是怎样的?

a. x.isSyncA()与x.isSyncB()   
b. x.isSyncA()与y.isSyncA()  
c. x.cSyncA()与y.cSyncB()  
d. x.isSyncA()与Something.cSyncA()
  • a. 都是对同一个实例(x)的synchronized域访问,因此不能被同时访问。(多线程中访问x的不同synchronized域不能同时访问)
    如果在多个线程中访问x.isSyncA(),因为仍然是对同一个实例,且对同一个方法加锁,所以多个线程中也不能同时访问。(多线程中访问x的同一个synchronized域不能同时访问)
  • b. 是针对不同实例的,因此可以同时被访问(对象锁对于不同的对象实例没有锁的约束)
  • c. 因为是static synchronized,所以不同实例之间仍然会被限制,相当于Something.isSyncA()与 Something.isSyncB()了,因此不能被同时访问。
  • d. 是可以被同时访问的,理由是synchronzied的是实例方法与synchronzied的类方法由于锁不同的原因。也就是synchronized 与static synchronized 各自管各自,相互之间就无约束了,可以被同时访问。

总结:
1.synchronized修饰的普通方法是对对象加锁,修饰的static方法是对类进行加锁。
2.若类对象被锁,则类对象的所有同步方法全被锁。
3.若实例对象被锁,则该实例对象的所有同步方法全被锁。
 
阿里系的一道笔试题:为什么是这个结果,中间到底间隔了多久才输出

public class ThreadDemo08 {

    public static void main(String[] args) {
        C c = new C();
        Thread t1 = new T1(c);
        Thread t2 = new T2(c);
        t1.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class C {
    public synchronized static void hello() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello");
    }

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

class T1 extends Thread {
    public C c;
    public T1(C c) {
        this.c = c;
    }

    @Override
    public void run() {
        c.hello();
    }
}

class T2 extends Thread {
    public C c;
    public T2(C c) {
        this.c = c;
    }

    @Override
    public void run() {
        c.world();
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小强签名设计

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值