Java------多线程_并发_同步_队列与锁_synchronized(八)

本文深入探讨了Java中的线程同步机制,通过实例分析了synchronized关键字在方法和同步块中的应用,以及在抢票和取钱场景中的问题与解决方案。讲解了线程同步带来的等待机制、线程安全和性能问题,并展示了如何通过锁对象来避免并发问题。同时,还展示了在容器操作中的线程安全实践。
摘要由CSDN通过智能技术生成

Java------多线程_并发_同步_队列与锁(八)
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程,进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
锁保证线程安全,实现线程同步。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。
为了保证数据在方法中被访问时的正确性,在访问时加入“锁机制(synchronized)”,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在以下问题:
1.一个线程持有锁会导致其他所有需要此锁的线程挂起。
2.在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
3.如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
线程同步实现:
已经通过private关键字保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,就是synchronized关键字。有两种用法,synchronized方法和synchronized块。
synchronized方法控制对“成员变量|类变量”对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,知道从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
锁具体的对象
缺点:若果将一个大的方法声明为synchronized将会大大影响效率。
难点在于安全性性能兼顾。
**案例一:**模拟抢票,此时synchronized写在方法上,成员方法锁的是this对象。

/**
 * 线程安全:保证数据的安全性,同时效率尽可能高
 * 第一个例子:抢票
 * synchronized
 */
public class ThreadSyn1 {
    public static void main(String[] args) {
        UnSafe1 threadTest01 = new UnSafe1();
        //多个代理,加入名称区分
        new Thread(threadTest01,"thread01").start();
        new Thread(threadTest01,"thread02").start();
        new Thread(threadTest01,"thread03").start();
    }

}

class UnSafe1 implements Runnable{
    private  int tickNums = 10;
    private  Boolean flag = true;
    @Override
    public void run(){
        while (flag){
            test();
        }
    }

    //线程同步,synchronized加到方法上
    //成员方法锁的this对象的资源
    public synchronized void test(){
        if (tickNums<0){
            flag = false;
            return;
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"--->"+tickNums--);
    }
}

案例二:取钱案例目标不对锁定失败。这里不是锁this,而是应该锁account。
问题出在:account.money-drawingMoney;就是账户的余额减去取的钱。
问题是多个人取到一致账户余额。此时在方法上锁还是会失败。
应该锁account。

public class ThreadSyn021 {
    public static void main(String[] args) {
        Account1 account = new Account1(100,"家庭基金");
        Draw1 draw = new Draw1(account,70,"男人");
        Draw1 draw2 = new Draw1(account,70,"女人");
        draw.start();
        draw2.start();
    }

}

//账户 Account1
class Account1{
    public int money;
    public String name;

    public Account1(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

class Draw1 extends Thread{
    Account1 account; //账户
    int drawingMoney;//取的钱
    int packetTotal;//口袋里的钱

    public Draw1(Account1 account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        this.test();
    }

    public synchronized void test(){
        //加上判断,当银行余额-取钱小于0时,不让取
        if (account.money-drawingMoney<0){
            return;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money -= drawingMoney;
        packetTotal += drawingMoney;
        System.out.println(this.getName()+"---->账户余额"+account.money);
        System.out.println(this.getName()+"---->口袋余额"+packetTotal);
    }
}

同步块:synchronized(obj){},obj称之为同步监视器
1.obj可以是任何对象,但是推荐使用共享资源作为同步监视器
2.同步方法中无需执行同步监视器,因为同步方法的同步监视器,是this,即该对象本身,或Class,即类的模子。
同步监视器的执行过程
1.第一个线程访问,锁定同步监视器,执行其中代码。
2.第二个线程访问,发现同步监视器被锁定,无法访问。
3.第一个线程访问完毕,解锁同步监视器。
4.第二个线程访问,发现同步监视器未锁,锁定并访问。
**案例三:**还是取钱例子,添加synchronized关键字,锁account对象

/**
 * synchronized
 * 1.同步块,目标更明确
 */
public class ThreadSyn022 {
    public static void main(String[] args) {
        Account2 account = new Account2(100,"家庭基金");
        Draw2 draw = new Draw2(account,70,"男人");
        Draw2 draw2 = new Draw2(account,70,"女人");
        draw.start();
        draw2.start();
    }

}

//账户 Account1
class Account2{
    public int money;
    public String name;

    public Account2(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

class Draw2 extends Thread{
    Account2 account; //账户
    int drawingMoney;//取的钱
    int packetTotal;//口袋里的钱

    public Draw2(Account2 account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        this.test();
    }

    public synchronized void test(){
        //当小于0时,不用直接再去试图获取锁,直接返回,优化
        if (account.money<0){
            return;
        }
        synchronized (account){
            //加上判断,当银行余额-取钱小于0时,不让取
            if (account.money-drawingMoney<0){
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= drawingMoney;
            packetTotal += drawingMoney;
            System.out.println(this.getName()+"---->账户余额"+account.money);
            System.out.println(this.getName()+"---->口袋余额"+packetTotal);
        }
    }
}


案例四:容器添加,加synchronized

import java.util.ArrayList;

/**
 * 容器不安全例子
 * 加synchronized,使得安全
 */
public class ThreadSyn031 {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<String> arrayList = new ArrayList<String>();
        for (int i = 0 ; i<100;i++){
            new Thread(()->{
            //synchronized块
                synchronized (arrayList){
                    arrayList.add(Thread.currentThread().getName());
                }
            }).start();
        }
        //打印太快,如果还没添加就打印,加个延时
        Thread.sleep(10000);
        System.out.println(arrayList.size());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值