概念

关于线程

  1. 线程就是独立的执行路径
  2. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
  3. 多个线程对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  4. 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
  5. main()称之为主线程,为系统的入口,用于执行整个程序
  6. 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的;(尽管我们可以设置线程的优先级,但是优先级高的线程也只是权重大,并不意味着优先级高的就先执行,线程的执行顺序完全取决于cpu的调度执行)
  7. 线程会带来额外的开销,如cpu调度时间,并发控制开销

线程同步

  • 线程同步,个人理解是对多个线程进行资源同步
  • 背景:由于一个进程的多个线程共享同一块存储空间,在带来便利的同时,也带来了访问冲突的问题
  • 解决思路:为了保证数据在方法中被访问的正确性,在访问时加入锁 synchronized 机制,当一个线程获得对象的排他锁,独占资源,其他线程等待,该线程使用后释放锁
  • 存在的问题
    1. 一个线程持有锁会导致其他所有需要此锁的线程挂起
    2. 在多线程竞争下,加锁、释放锁会导致上下文切换和调度延迟,引发性能问题
    3. 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引发性能问题

Synchronized关键字的两种用法

  1. 理解:由于我们可以通过private关键字来保证数据对象只能被访问,所以我们只需要针对方法提供一套机制,这套机制就是sychronized关键字,它包括两种用法synchronized方法和synchronized块
  2. synchronized方法
    1. 同步方法
    public synchronized void method(int args){}
    
    1. syschronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞
    2. 缺陷:若将一个大的方法申名为 synchronized将会影响效率
    3. ⚠️同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[反射]
  3. synchronized
    1. 同步块
    synchronized(Obj){}
    
    1. Obj称之为同步监视器(个人理解同步监视器就是多个线程争夺的资源对象);Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    2. 同步监视器的执行过程
      1. 第一个线程访问,锁定同步监视器,执行其中代码
      2. 第二个线程访问,发现同步监视器被锁定,无法访问
      3. 第一个线程访问完毕,解锁同步监视器
      4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

线程同步示例

  • 背景:夫妻二人有一个账户,名为结婚基金,账户当前余额为100万。今妻子欲取100万,你要取50万,保证线程安全!
public class UnsafeDrawing {
    public static void main(String[] args) {
        //账户总共100
        Account account=new Account(100,"结婚基金");

        Persion wife = new Persion(account,100,"妻子");   //妻子要取100
        Persion man = new Persion(account,50,"你");  //你要取50

        //两个线程的执行顺序取决于cpu的调度
        man.start();
        wife.start();
    }
}
//账户
class Account{
    int money;  //余额
    String name;    //卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//操作人
class Persion extends Thread{
    Account account; //账户
    int drawingMoney;   //取出
    int nowMoney;   //手里现在的钱

    public Persion(Account acount, int drawingMoney, String name){
        super(name);
        this.account = acount;
        this.drawingMoney = drawingMoney;
    }
    //取钱
    @Override
    public void run() {
        //锁的对象就是变化的量,需要增删改的对象
        synchronized (account) {
            if(account.money-drawingMoney<0){    //账户余额不足
                System.out.println( "当前账户余额不足,"+Thread.currentThread().getName()+"取不了?");
                return;
            }
            //sleep可以放大线程不安全问题
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡内余额
            account.money=account.money - drawingMoney;
            System.out.println(account.name+ "当前余额为:"+account.money);
            //你手里现在的钱
            nowMoney=nowMoney+drawingMoney;
            //Thread.currentThread().getName()=this.getName()
            System.out.println(this.getName()+"手里现在的钱:"+nowMoney);
        }
    }
}