线程的同步与死锁

线程的同步与死锁

1.线程互斥

多线程编程三大问题:

分工:

同步:多个线程同时做一件事(线程间通信:线程合作)

互斥:在多线程并发时,某一时刻只能由一个线程访问共享资源

:一个锁只能保护一个对象(相应对象/资源),不同锁保护的不同的对象/资源

线程安全问题:多线程访问了共享的数据,会产生线程安全问题。

单线程程序不会出现线程安全问题。多线程程序没有访问共享数据,不会产生线程安全问题。

解决办法:让一个线程在访问共享数据的时候无论是否失去了CPU的执行权,让其他的线程只能等待。等待当前线程完成,再让其他线程进入。保证使用只有一个线程在工作。

1.1 线程安全问题的解决办法:即锁的实现方式:

使用synchroinzed关键字为程序逻辑上锁

synchroinzed (锁的对象| Object及其子类| 类对象)

使用synchchroinzed两种方法:

1.1.1同步代码块

使用同步代码块,需要设定锁的对象: this,任意一个Object子类对象 ,当前类.class(当前类的反射对象)

synchronized(锁的对象){
    //此处代码块在任意一个时刻只能有一个线程进入  
}
package ThreadTest.synchronizedTest;

/**
 * @Name:  同步处理
 * @Author:ZYJ
 * @Date:2019-05-29-15:02
 * @Description:
 */
public class synchronizedDemo {
    public static void main(String[] args) {
        MyThread myThead = new MyThread();
        Thread t1 = new Thread(myThead,"黄牛A");
        Thread t2 = new Thread(myThead,"黄牛B");
        Thread t3 = new Thread(myThead,"黄牛C");
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}
class MyThread implements Runnable{
    private int ticket =30;
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            //在同一时刻,只允许一个线程进入代码块处理
            synchronized (this){
                if(this.ticket>0){
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+
                            ",还有"+this.ticket--+"张票");
                }
            }
        }
    }
}
1.1.2 同步方法

不需要手动上锁,解锁

修饰普通的对象方法,锁当前对象 this

修饰类的静态方法, 锁当前类.class (本类的class属性)

直接在方法声明上使用sychronized,此时表示同步方法在任意时刻只能有一个线程进入。

public synchronized void sellTicket() :默认锁的是this
package ThreadTest.synchronizedTest;

/**
 * @Name: 锁的实现  同步方法
 * @Author:ZYJ
 * @Date:2019-05-29-16:07
 * @Description:
 */
public class tongbuDemo {
    public static void main(String[] args) {
        myThread m = new myThread();
        Thread t1= new Thread(m,"黄牛A");
        Thread t2= new Thread(m,"黄牛B");
        Thread t3= new Thread(m,"黄牛C");
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}
class myThread implements Runnable{
    private int ticket=1000;
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            this.sale();
        }
    }
    //同步方法
    public synchronized void sale() {
        if (this.ticket > 0) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "还有" + this.ticket-- + "张票");
        }
    }
}

sychronized**对象锁:**锁哪个对象:

sychronized修饰的时候静态方法或sychronized(类名称.class)sychronized锁的都是当前类的反射对象(全局唯一); 当同步方法为静态时,上诉属性 ticket也要为静态,这时所得对象不再是this ,而是当前类对象:即 myThread.class

类名称.class:d=反射对象 反射对象有且只有一个

1.1.3 LOCK锁(JDK1.5提供 基于java语言层面 的“锁”)

见下一篇

1.1.4总结

synchronized使用:

1,同步代码块

synchronized(this|任意一个Object子类对象|当前类.class) {   

}

2,同步方法

修饰普通对象方法 锁当前对象this 

修饰类的静态方法 锁当前类.class

保护的是什么?几个锁?

使用一把锁锁住了两个毫无关系的对象

class Account {
    // 余额
	int sal;
    // 密码
    String password;
    // 余额资源的锁
    private Object salLock = new Object();
    // 密码资源的锁
    private Object passLock = new Object();
    public int getMoney() {
    	synchorinzed(salLock) {}
    }
    public void setMoney() {
    	synchorinzed(salLock) {}
    }
    public String getPassword() {
    	synchorinzed(passLock) {}
    }    
    public void setPassword() {
    	synchorinzed(passLock) {}
    }
}

如何保护毫无关系的资源?

使用多把锁锁住不同的资源

转账
A -> B 100
A -= 100;
B += 100;
如何保护有关联关系的对象:
使用同一把锁
public void zhuangzhang(Account target) {
	synchroinzed(Account.class) {
		this.sal -= 100;
		target.sal += 100;
	}
}
由于转账涉及两个账户间的sal操作,因此需要将两个账户同时锁定。
由于方法的synchrond只能锁一个对象,因此锁不住转账操作

2. synchronized底层实现

在使用synchronized时,必须保证锁定对象必须为Object及其子类对象。synchronized使用的是JVM层级别的MoniteratioEnterMoniterExit实现。
在这里插入图片描述
这两个指令都必须获取对象的同步监视器Monitor

2.1 对象锁Monitor(监视器)机制

执行同步代码块后首先要先执行monitorenter指令,退出时执行monitorexit指令。分析可得,使用synchronized进行同步, 其关键就是必须对对象的监视器monitor进行获取 ,当线程获取monitor后才能继续往下执行,否则只能等待。这个获取monitor的过程是互斥的,即同一时刻只有一个线程能够获取到monitor

程序中一般包含一个monitorenter指令和多个monitorexit指令。那是因为JVM需要需要保证所获得的锁 在正常的执行路径和异常执行路径上,都能够被解锁。所以会在异常体系中是释放锁,保证程序异常时,也能有释放锁。

但应synchronized标记方法时,字节码中的方法访问标记包括 ACC_SYNCHRONIZED.该标记表示,在进入该方法时,java虚拟机需要进行monitorenter操作。而在退出该方法时,不管是正常返回,还是向调用者抛异常,JVM均需要进行monitorexit操作。

monitorentermonitorexit操作所对应的锁对象都是隐式的。对于实例方法来说,这两个操作对应的锁对象是this,对于静态方法来说,这两个操作对应的锁对象则是所在类Class实例。

MoniteratioEnter:

检查锁定的对象的计数器是否为0,若为0表示此监视器还未被任意一个线程获取,此时线程可以进入同步代码块并且将Monitor值+1,将Monitor的持有线程标记为当前线程

Monitor计数器不为0,且持有线程不是当前线程,表示Monitor监视器吗已经被别的线程占用,当前线程只能阻塞等待

Monitor计数器0,但持有线程恰好为当前线程

**可重入锁:**当执行MoniteratioEnter使。对象的Monitor计数器值不为0,但是持有线程恰好是当前线程,此时将Monitor计数器值再次+1,当前线程继续进入同步方法或代码块。

//线程2 
synchronized0{

//线程1
}

Monitorexit

Monitor计数器值-

2.2 JDK1.6之后对于synchronized优化

synchronized:互斥

优化让每个线程通过同步代码块时速度提高

之前synchronized获取锁失败,将线程挂起-悲观锁策略

2.2.1 **CAS操作 **(非阻塞同步)

比较交换鉴别线程(无锁实现的同步-乐观锁)-自旋(循环)

CompareAndSwap(O,V,N)

O:当前线程存储的变量值

V:内存中该变量的具体值

N: 希望修改后的变量值

当O==V时,此时表示还没有线程修改共享变量的值,此时可以成功的将内存中的值修改为N

当O!==V时,表示此时内存中的共享变量值已被其他线程修改,此时返回内存中的最新值V,再次尝试修改变量。

CAS的操作需要硬件指令集的支持,在JDK1.5之后才可以使用处理器提供的CMPXCHG指令实现

线程挂起阻塞:车熄火

自旋:脚踩刹车,车不熄火

2.2.2 自旋出现的问题

1.ABA问题

**解决ABA问题:**添加版本号 JDK1.5 之后atomic包中提供了AtomicStampedReference解决该问题。

2.自旋在CPU上跑的无用指令,会浪费CPU资源

解决办法自适应自旋:JVM尝试自旋一段时间,若在此时间内,线程成功获取到锁,再下次获取锁时, 适当延长自旋时间。若在此时间内,线程没有获取到锁,则下次获取锁时,适当缩短自旋时间。

3.公平性问题

不公平的锁机制:处于阻塞状态的线程,无法立刻竞争被释放的锁,而处于自旋状态的线程则很有可能优先获得这把锁。

**解决办法:**Lock锁可以实现公平性,synchronized无法实现公平锁。

2.3 JVM 中synchronizationized实现中锁的代价

2.3.1 偏向锁:

JDK1.6之后默认synchronized。最乐观的锁:进入同步块或同步方法的始终是一个线程,不加锁直接过;当出现另一个线程也尝试获取锁(不同时刻)时,偏向锁会升级为轻量级锁

2.3.2轻量级锁:

多个线程在不同的时间段请求同一把锁,即没有锁的竞争。

2.3.3重量级锁

JDK1.6之前synchronized都是重量级锁,将线程阻塞挂起(JDK1.6自适应自旋)。

2.3.4总结:

1.重量级锁会阻塞、唤醒请求加锁的线程。它针对的多个线程同时竞争一把锁的情况。JVM采用了自适应自旋,来避免线程在面对非常小的synchronized代码块时,仍会被阻塞、唤醒的情况

2.轻量级锁采用CAS操作,将锁对象的标记字段替换为一个指针,指向当前线程栈上的一块空间,存储的锁对象原本的标记字段。它针对的是多个线程在不同时间段申请同一把锁的情况。

3.偏向锁只会在第一次请求时采用CAS操作,在锁对象的标记字段中记录下当前线程的地址。在之后的运行过程中,持有该偏向锁的线程的加锁操作将直接返回。它针对的是锁仅会被同一线程持有的情况。

**锁粗化:**当出现多次连续的加锁与解锁过程,会将多次加减锁过程粗话为一次加解锁过程

**锁消除:**当对象不属于共享资源时,对象内部的同步方法或同步代码块的锁会被自动解除

2.4 死锁

以下四种情况同时发生才会出现死锁

**1.互斥:**共享资源只能同时被一个线程占用

**2.占用且等待:**拿到了worker锁,不释放但去申请money锁;

3.不可抢占:线程Thread1拿到对象锁X之后,其他线程无法强行抢占X锁。

**4.循环等待/(循环占有):**线程T1拿到了资源X的锁,去申请Y的锁;

​ 线程T2拿到了资源Y的锁,去申请X的锁;

package ThreadClass.DeadLock;

/**
 * @Name:  模拟死锁
 * @Author:ZYJ
 * @Date:2019-06-03-20:27
 * @Description:
 */
public class TestDemo {
    private  static Pen pen = new Pen();
    private  static  Book book =new Book();

    public static void main(String[] args) {
        new TestDemo().deadLock();
    }
    public void deadLock(){
        //使用匿名
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (pen){
                    System.out.println(Thread.currentThread()+":我有笔,我就不给你");
                    synchronized (book){
                        System.out.println(Thread.currentThread()+":把你的本给我!");
                    }
                }
            }
        },"Pen").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (book){
                    System.out.println(Thread.currentThread()+":我有本子,我就不给你!");
                    synchronized (pen){
                        System.out.println(Thread.currentThread()+":把你的笔给给我");
                    }
                }
            }
        },"Book").start();
    }
}
class Pen{
    private String pen="笔";

    public String getPen() {
        return pen;
    }
}
class Book{
    private  String book="书";

    public String getBook() {
        return book;
    }
}

死锁一旦出现之后,整个程序就会中断执行,所以死锁属于严重性问题

解决:破坏其中一个 互斥不能破坏,不可抢占不可解决,占有且等待不可解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值