Java多线程与并发-原理

  1. 线程安全问题

主要诱因:
存在共享数据(也称临界资源)
存在多条线程共同操作这些共享数据
解决方法:
同一时刻有且只有一个线程在操作共享数据,其它线程必须等到该线程处理完数据后再对共享数据进行操作

  1. 互斥锁的特性

互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性。
可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致

synchronized在java中满足互斥锁特性
synchronized锁的不是代码,锁的是对象

  1. 获取的锁的分类:获取对象锁和获取类锁

获取对象锁的两种方法:一是同步代码块(synchronized(this),synchronized(类实例对象)),锁是小括号()中的实例对象;二是同步非静态方法(synchronized method),锁是当前对象的实例对象。

public class SyncThread implements Runnable{
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.startsWith("A")){
            async();
        }else if (threadName.startsWith("B")){
            syncObjectBlock1();//同步对象块方法
        }else if (threadName.startsWith("C")){
            syncObjectMethod1();
        }
    }
    /**
     * 异步方法
     */
    private void async(){
        try{
            System.out.println(Thread.currentThread().getName()+"_Async_Start:"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"_Async_End:"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    /**
     * 方法中有synchronized(this|object){}同步代码块
     */
    private void syncObjectBlock1(){
        System.out.println(Thread.currentThread().getName()+"_SyncObjectBlock1:"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (this){
            try{
                System.out.println(Thread.currentThread().getName()+"_SyncObjectBlock1_Start"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"_SyncObjectBlock1_End"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    /**
     * synchronized修饰非静态方法
     */
    private synchronized void syncObjectMethod1(){
        System.out.println(Thread.currentThread().getName()+"_SyncObjectMethod1:"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
    try{
        System.out.println(Thread.currentThread().getName()+"_SyncObjectMethod1_Start"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+"_SyncObjectMethod1_End"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
    }catch (InterruptedException e){
        e.printStackTrace();
    }
    }
}
public class SyncDemo {
    public static void main(String... args) {
        SyncThread syncThread = new SyncThread();
        Thread A_thread1 = new Thread(syncThread,"A_thread1");
        Thread A_thread2 = new Thread(syncThread,"A_thread2");
        Thread B_thread1 = new Thread(syncThread,"B_thread1");
        Thread B_thread2 = new Thread(syncThread,"B_thread2");
        Thread C_thread1 = new Thread(syncThread,"C_thread1");
        Thread C_thread2 = new Thread(syncThread,"C_thread2");
        A_thread1.start();
        A_thread2.start();
        B_thread1.start();
        B_thread2.start();
        C_thread1.start();
        C_thread2.start();
    }
}

获取类锁的两种方法:一是同步代码块(synchronized(类.class)),锁是小括号()中的类对象(Class对象);二是同步静态方法(synchronized static method),锁是当前对象的类对象(Class对象)。

对象锁和类锁的总结:
使用对象锁时,多个线程访问同一个对象,会产生竞争关系,但是多个线程访问同一个类实例化的不同对象,则不会发生竞争关系,即多个线程之间互不干扰;
使用类锁时,多个线程访问同一对象或者是同一类实例化的不同对象时,都会发生竞争关系;
同一个类中的类锁和对象锁不会相互干扰,不会有竞争关系。

  1. 自旋锁和自适应自旋锁

自旋锁:
在许多情况下,共享数据的锁定状态的持续时间较短,此时切换线程不值得
在线程阻塞的时候,让线程执行忙循环等待锁的释放,不让出CPU
缺点:若等待的锁被其他线程长时间占用,会带来许多性能上的开销

自适应自旋锁:
自选的次数不再固定
由上一次在同一个锁上的自旋时间和锁的拥有着的状态来决定

  1. 锁消除和锁粗化

锁消除是JVM更彻底的优化,JIT编译时,对上下文进行扫描,去除不可能存在竞争的锁;
锁粗化是另一种极端,通过加锁的范围,避免反复加锁和解锁

//极端:锁消除
public class StringBufferWithoutSync {
    public void add(String str1,String str2){
        //StringBuffer是线程安全,由于sb只会在append方法中使用,不可能被其他的线程引用
        //因此sb属于不可能共享的资源,JVM会自动消除内部的锁
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }

    public static void main(String[] args) {
        StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
        for (int i = 0;i < 1000;i ++){
            withoutSync.add("aaa","bbb");
        }
    }
}
  1. synchronized的四种状态

无锁、偏向锁、轻量级锁、重量级锁
锁膨胀方向:无锁——>偏向锁——>轻量级锁——>重量级锁

偏向锁:较少同一线程获取锁的代价。大多数情况下,所不存在多线程竞争关系,总是由同一线程多次获得。
如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需检查Mark Word的锁标记位为偏向锁以及当前线程id等于Mark Word的ThreadID即可,这样就省去了大量有关锁申请的操作
偏向锁不适合于锁竞争比较激烈的多线程场合

轻量级锁:是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。适用于线程交替执行的同步块。若存在同一时间两个线程访问同一锁的情况,就会导致轻量级锁升级为重量级锁
在这里插入图片描述
在这里插入图片描述

  1. synchronized和ReentrantLock(再入锁)

ReentranLock(再入锁)
位于java.util.concurrent.locks包
和CountDownLatch、Futask、Semaphore一样基于AQS实现
能够实现比synchronized更细粒度的控制,如控制fairness
调用lock()之后,必须调用unlock()来释放锁
性能未必比synchronized高,并且也是可重入的。

ReentrantLock公平性的设置 :
ReentrantLock fairLock = new ReentrantLock(true);
参数为true时,倾向与将锁赋予等待时间最久的线程;
公平锁即获取锁的顺序按先后调用lock方法的顺序(慎用);
非公平锁即抢占的顺序不一定,看运气;
synchronized是非公平锁。

ReentrantLock将锁对象化:
判断是否有线程,或者某个特定的线程在排队等待获取锁;
带超时的获取锁的尝试;
能感知有没有成功获取锁。

总结:synchronzied是关键字,ReentrantLock是类;ReentrantLock可以对获取锁的等待时间进行设置,避免线程死锁;ReentrantLock可以获取各种锁的信息;ReentrantLock可以灵活的实现多路通知;锁的机制不同:sync操作的是Mark Word,ReentrantLock调用的是Unsafe类的park()方法。

  1. java内存模型JMM

Java内存模型JMM
Java内存模型(即Java Memory MOdel,简称JMM)本身是一种抽象的概念,并不真实的存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
在这里插入图片描述
由于JVM运行的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存,有些地方称为栈空间,用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作呢,即读取赋值等,必须在工作内存中进行。首先将变量从主内存拷贝到工作内存中,然后对变量进行操作,操作完成后在将变量写回主内存,而不能直接操作主内存中的变量。工作内存中存储这主内存变量中的副本,工作线程是每个线程的私有区域,因此不同的线程键无法访问对方的工作内存,线程间的通信也就是传值,必须通过主内存来完成。

  1. CAS(Compare and Sweep)乐观锁

一种高效实现线程安全性的方法
支持原子更新操作,适用于计数器,序列发生器等场景;
属于乐观锁机制,号称lock-free
CAS操作失败时由开发者决定是继续尝试还是执行别的操作。

缺点
若循环时间长,则开销很大
只能保证一个共享变量的原子操作
ABA问题 解决:AtomicStampedReference

  1. Java线程池

在这里插入图片描述
为什么要使用线程池:
降低资源消耗;
提高线程的可管理性。
在这里插入图片描述
ThreadPoolExecutorThreadPoolExecutor的构造函数:

corePoolSize:核心线程数量
maximumPoolSize:线程不够用时能够创建的最大线程数
workQueue:任务等待队列
keepAliveTime:线程池维护线程所允许的空闲时间,当线程池中的线程数量大于corePoolSize的时候,如果这是没有新的任务提交,核心线程外的线程不会立即被销毁而是会等待,直到等待的时间超过keepAliveTime才会被销毁。
threadFactory:创建新线程(Executors.defaultThreadFactory)
hander:线程池的饱和策略。如果阻塞队列满了,并且没有空闲的线程,这时如果继续提交任务,会通过hander所指定的策略来处理线程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值