JAVA线程安全

本文介绍了Java中的线程安全问题,包括线程安全的定义、如何使用同步锁(同步代码块和同步方法)以及Lock接口的ReentrantLock实现。文章还探讨了死锁的概念及其四个必要条件,并提出了防止死锁的一种策略——指定获取锁的顺序。

线程安全/线程锁

线程安全问题出现在并发的环境下,

多个线程访问同一个资源,可能会出现线程安全问题

1.什么是线程安全:

在多线程访问和使用共享资源的情况下,线程在运行的时候可能被打断,变为阻塞状态,失去执行权。

例如i ++,i++不是原子操作 ! 其执行要分为3步: 1、读内存到寄存器; 2、在寄存器中自增; 3、写回内存。

因为不是原子操作,所以i可以被打断。

ps“补充:原子性、可见性、指令重排——线程安全问题围绕这三点。

 

如何解决:加同步锁

2.如何使用同步锁(互斥锁)

1》同步代码块

格式:synchronized(监视器/锁){需要同步的内容}

监视器:JAVA中任何一个对象都是一个监视器,也就是锁、

要保证互斥性,所有的线程必须使用同一个监视器。(即线程对自己去加锁没意义)

因此,在以继承父类作为方法时,使用object 和this均无法锁定

代码实例:售票系统

首先需要一个ticket类

//售票系统
public class Ticket {
    int tick=100;//总得票数

}

然后是User类

public class User extends Thread{
    Ticket tk;
    Object lock =new Object();

    public User(Ticket tk) {
        this.tk = tk;
    }

    @Override
    public void run() {
        //,每个人买50张票
        for (int i = 0; i <50 ; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//            ticket();
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + ":线程买了" + tk.tick + "号票");
                tk.tick--;
//            }
        }

    }
//    public synchronized void ticket(){
//        System.out.println(Thread.currentThread().getName() + ":线程买了" + tk.tick + "号票");
//        tk.tick--;
    }
}

Test类

public class Test {
    public static void main(String[] args) {
        Ticket tk =new Ticket();

        User user =new User(tk);
        User user2 =new User(tk);
        user.setName("用户A");
        user2.setName("用户B");
//        new Thread(user,"用户A").start();
//        new Thread(user,"用户B").start();
        user.start();
        user2.start();



    }
}

2》同步方法:对象锁(this)

将关键字直接放在方法上

public synchronized void ticket(){}

锁的东西就是this

代码示例 银行取钱1.0

//银行账户类
public class Account {

    private int balance;

    public Account(int balance) {
        this.balance = balance;
    }

    public int getBalance() {
        return balance;
    }

    public synchronized void add(int num) {
        balance = balance + num;
    }

    public synchronized void withdraw(int num) {
        balance = balance - num;
    }
}
//存钱线程
public class AddThread implements Runnable{
    Account account;
    int     amount;

    public AddThread(Account account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            account.add(amount);
            System.out.println(Thread.currentThread().getName()+"本次存"+amount+"元后余额为"+account.getBalance());
        }
    }
}
//取钱线程
public class WithdrawThread implements Runnable {
    Account account;
    int amount;

    public WithdrawThread(Account account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 200; i++) {
            if (account.getBalance()>0) {
                account.withdraw(amount);
                System.out.println(Thread.currentThread().getName() + "本次取" + amount + "元后余额为" + account.getBalance());
            }else {
                System.out.println("余额不足");
                break;}
        }

    }
}

//主函数类
public class Test {
    public static void main(String[] args){
        Account account = new Account(1000);
        Thread a = new Thread(new AddThread(account, 20), "add");
        Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");
        a.start();
        b.start();
//        //join?
//        a.join();
//        b.join();
        System.out.println("账户余额为"+account.getBalance());
    }
}

3.Lock(接口)

作用与synchronized一样

结合实现类使用 ReentrantLock

加锁和释放锁都是自己控制的

代码实例 银行取钱2.0

import java.util.concurrent.locks.ReentrantLock;

public class AddThread implements Runnable{
    Account account;
    int     amount;
    ReentrantLock lock = new ReentrantLock();

    public AddThread(Account account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            lock.lock();
            System.out.println("存钱进程锁");
            for (int i = 0; i < 100; i++) {
                account.add(amount);
                System.out.println(Thread.currentThread().getName() + "本次存" + amount + "元后余额为" + account.getBalance());
            }

        } //catch(InterruptedException e){e.printStackTrace();}
        finally {
            lock.unlock();
            System.out.println("存钱进程解锁");
        }
    }
}
import java.util.concurrent.locks.ReentrantLock;

public class WithdrawThread implements Runnable {
    Account account;
    int amount;
    ReentrantLock lock= new ReentrantLock();;

    public WithdrawThread(Account account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            lock.lock();
            System.out.println("取钱进程上锁");
            for (int i = 0; i < 200; i++) {
                if (account.getBalance() > 0) {
                    account.withdraw(amount);

                    System.out.println(Thread.currentThread().getName() + "本次取" + amount + "元后余额为" + account.getBalance());
                } else {
                    System.out.println("余额不足");
                    break;
                }
            }
        }
        finally {
            lock.unlock();
            System.out.println("取钱进程解锁");
        }

    }
}

这里放存钱和取钱线程的代码更改。Test和Account类不变。

补充一些问题

1.同步方法和同步代码块的区别是什么?

同步方法默认用this或者当前类class对象作为锁;

ps:当前都会有一个不需要申明的默认class对象,一半用于作为同步方法的锁。


同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;


同步方法使用关键字
synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容}进行修饰;

2.什么是死锁(deadlock)?

所谓死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。死锁产生的4个必要条件:
互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某
资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能
由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源
已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被
链中下一个进程所请求。

我自己的理解是类似于需要多到工序做饭的过程。首先资源(食材)一次智能被一个锅煮,这个锅煮了,下一个锅就得等着。在请求别的锅里的食材联动的时候,别的锅没好,这边也只能等着,同时占用锅和菜。

循环链则是接近于流水线。

3.如何确保N个线程可以访问N个资源的同时又不导致死锁?

指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值