Java 并发编程之美???

1.并发线程基础

1.1 线程基础

进程是系统进行资源分配和调度的最小单元,进程中多个线程共享进程的资源。
线程是操作系统运行的最小单元;进程里包含了多个线程,他们处理不同的任务,组成了一个应用或者一个系统的整体逻辑。

每个线程都有自己的程序计数器和栈
程序计数器:记录当前线程执行的指令地址,为了线程之间切换后,知道下次执行到什么位置。
栈:线程的局部变量
方法区:线程共享的。存储JVM加载的类、常量及静态变量。

public class LifeOff implements Runnable{
private int countDown=10;
private static int taskCount=0;
private final int id=taskCount++;
    public LifeOff(int countDown) {
        this.countDown = countDown;
    }
    public LifeOff() { }
    @Override
    public void run() {
        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
        while (countDown-->0){
            System.out.print(status());
       		 Thread.yield();//让步
    	}
	}
	public String status(){
	    return "#"+Thread.currentThread().getName()+"("+(countDown>0 ? countDown:"lifeoff!\n")+")";
	}
}

1.2 实现线程的方式:

1.继承Thread
2.实现Runable接口
3.使用FutureTask对象包装Callable接口的实现类,然后获取get方法,返回结果。

区别 :实现Runable接口扩展性更好,因为继承只能单向继承

1.3 notify和wait

  • wait() 当一个线程调用了变量的wait()时,调用线程被挂起,同时不阻塞,释放CPU资源。此时其他线程调用了该变量的notify() notifyAll(),或者其他线程调用了当前线程的interrupt(),该线程抛出中断异常。

注意:如果调用wait方法的线程没有事先获取该对象的监视器锁,则会抛出异常。IllegalMonitorStateException
wait notify notifyAll是object的方法。

虚假唤醒:一个线程可以从挂起状态变为运行状态,叫被唤醒。即使该线程没有被其他线程调用notify notifyAll 进行通知,或者被中断,或者等待超时,这是虚假唤醒。这种情况不常见。

  • notify() 当一个线程调用notify方法后,会唤醒一个在该变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,唤醒哪一个是随机的。
    此外,被唤醒的线程不会马上从wait方法返回并继续,它必须在获取锁才执行。
  • notifyAll() 雷同于notify(),它可以唤醒变量上所有wait等待被挂起的线程。

最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行。

1.4 等待线程执行完毕的join

Thead.join(): 加入,阻塞式的。当一个线程执行join(),证明该线程优先执行,会终止当前正在运行的线程,开始执行join的线程且执行完毕。

可以实现排队

1.5 让线程睡眠的sleep

Thread.sleep():阻塞线程,占有CPU资源

1.6 让出CPU执行权的yield

Thread.yield():让步,让出CPU的执行权。当一个线程执行yield()方法,证明该线程执行让步,当时线程调度器可以忽略。让其他线程有可能的获取资源运行。

1.7 后台线程Daemon

Thread.setDaemon(boolean);设置当前线程为后台线程,后台线程要在start之前调用才有效。后台线程,是指程序运行的时候在后台提供一种通用服务的线程,且这种线程并不属于程序中不可或缺的部分;只要有任何非后台线程在运行,程序就不会终止。

1.8 线程中断 interrupt

中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止线程的执行,而是被中断的线程根据中断状态自行处理。

1.8.1 public void interrupt() 中断线程。

当A 运行,B调用A的interrupt()方法 设置A的中断标志为true,并立即返回。设置标志仅仅设置了标志,但A并没有中断,它会继续往下执行。如果A 调用了wait.join sleep方法而被阻塞,这时候B调用A的interrupt(),则A会抛出异常InterruptedException(),且清除标志位。

 /**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *   * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     * @revised 6.0
     * @spec JSR-51
     */
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

1.8.2 public static boolean interrupted()

判断线程是否中断。 该方法清除了线程的中断状态 。

    /**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

1.8.3 public boolean isInterrupted()

判断这个线程是否被中断。 此方法不影响线程的中断状态

线程中断被忽略,因为线程不活跃 在中断的时候会,所以这个方法 返回false


    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

Executors(执行器)

Executors会统一管理线程。

  • newCachedThreadPool会为每一个任务创建一个线程。
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new LifeOff());
executor.shutdown();
  • newFixedThreadPool会创建固定数量的线程来执行任务。
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(new LifeOff());
executor.shutdown();
  • newSingleThreadExector会创建一个线程来执行任务。如有多个任务,会排队加载任务。执行一个结束,才会处理下一个任务。
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new LifeOff());
executor.shutdown();
  • newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);

for (int i = 0; i < 15; i = i + 5) {
    pool.schedule(() -> System.out.println("我被执行了,当前时间" + new Date()), i, TimeUnit.SECONDS);
}
pool.shutdown();

生产者 消费者模型

https://blog.csdn.net/chentaishan/article/details/117743315

对象锁和类锁 区别

对象锁–> 实例锁

因为是实例对象,所以可以有多个实例,不同的实例的锁互不影响。
1.synchronized 块里传入的是个对象实例
2.synchronized 块里传入的是this
3.非静态synchronized方法

类锁–> class对象锁

class是有jvm加载,在整个程序里具有唯一性。是所有线程可以共享的锁,所以同一时刻,只能有一个线程使用加了锁的方法或方法体,不管是不是同一个实例。

1.静态synchronized 方法
2.synchronized 块里传入的是个xx.class
3.synchronized 块里传入的是个static修饰的对象实例

理解线程上下文切换

线程死锁

ThreadLocal

2.并发其他基础知识

并发和并行区别

Concurrency means multiple tasks which start, run, and complete in overlapping time periods, in no specific order.Parallelism is when multiple tasks OR several part of a unique task literally run at the same time, e.g. on a multi-core processor. Remember that Concurrency and parallelism are NOT the same thing.

并发需要存在多个任务,其次,这些任务在重叠的时间段内以无序的方式启动,运行和完成。在单个线程的一个时间片上,只能运行一个任务。并行是指多个任务或唯一任务的多个部分在逻辑上同时运行的情况。

目前这个地址讲的比较好:
https://howtodoinjava.com/java/multi-threading/concurrency-vs-parallelism/

所谓共享资源,就是说该资源被多个线程所持有或者说多个线程都可以去访问该资源。线程安全问题指多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果的问题。

2.1 内存可见性volatile

Java内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间中,线程读写变量时操作的是自己工作内存中的变量
可以使用volatile来实现内存的可见性。使用synchronized太笨重,因为它会带来线程上下文的切换开销
volatile确保对一个变量的更新对其他线程马上可见。线程在写入变量时不会把值缓存到寄存器或者其他地方,而是直接刷新到主内存。不能保证操作的原子性。

使用场景:
1.写入变量值不依赖变量的当前值
2.读写变量值没有加锁

2.2 synchronized同步

synchronized提供了原子性内置锁,Java中每个对象都可以把它当作同步锁来使用,使用者看不到的锁称为内部锁。内置锁是排它锁,当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。
使用synchronized关键字,是把在synchronized块内使用到的变量从线程的工作内存中清除,而是直接从主内存获取。退出synchronized块的内存语义是同步块内对内存变量的修改刷新到主内存。
synchronized可以解决内存可见性,还实现原子性操作

2.3 原子性

执行一系列操作时,要么全部执行,要么全部不执行,不存在执行其中一部分的情况。
可以使用synchronized实现原子性和可见性,但synchronized是独占锁,没有获取锁的线程会被阻塞。

2.4 Java中的CAS操作

使用锁会导致线程上下文切换和重新调度开销。Java提供了volatile关键字来解决共享变量的可见性问题,但是不能解决读-改-写的原子性问题。所以Java提供了非阻塞原子性的的关键字CAS,通过硬件保证了比较-更新操作的原子性。
compareAndSwap 比较和交换

Unsafe类提供了compareAndSwap方法。

boolean compareAndSwapLong(object obj,long valueoffset,long expect,long update)

obj 对象内存位置
valueoffset 对象中变量的偏移量
expect 变量预期值
update新值

ABA问题:
变量的状态值产生了环形转换,A->B ,B->A,虽然结果可能是正确的,但是也有问题。
可以通过JDK中的AtomicStampedReference类给每个变量的状态值都配备一个时间戳,避免ABA问题。

2.5 Unsafe重要方法

jdk中rt.jar包中的unsafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法。使用JNI形式访问C++实现库。

long objectFieldOffset 返回指定的变量在所属类中的内存偏移地址。

int arrayBaseOffset

int arrayIndexScale

boolean compareAndSwapLong()

public native long getLongVolatile

void putLongvolatile

void putOrderedLong()

void park()

void unpark();

long getAndSetLong()

long getAndAddLong()

2.6 指令重排序

Java内存模型允许编译器和处理器对指令重新排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。
多线程下指令重排序会存在问题。

2.7 伪共享

2.8 锁分类

1 乐观锁 悲观锁

乐观锁 :相对悲观锁,认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排它锁,而是在进行数据提交更新时,才会正式对数据冲突与否进行检测。
悲观锁:对数据被外界修改持保守态度,认为数据很容易被其他线程修改,所有处理数据之前先对数据进行加锁。

2. 公平锁 非公平锁

公平锁表示线程获取锁的顺序是按照线程请求锁的时间顺序决定的。
非公平锁在运行时闯入,随机性更大。

ReentrantLock提供了公平锁和非公平锁

//公平锁
ReentrantLock reentrantLock = new ReentrantLock(true);

//非公平锁  也是默认值
ReentrantLock reentrantLock = new ReentrantLock(true);

3. 独占锁 共享锁

独占锁保证任何时候都只有一个线程能得到锁,ReentrantLock就是以独占方式实现。独占锁是悲观锁。
共享锁可以同时多个线程持有,ReadWriteLock读写锁,它允许一个资源可以被多线程同时进行读操作。乐观锁,放宽了加锁条件,允许多个线程同时进行读操作。

4. 可重入锁 自旋锁

可重入锁:当一个线程获取被其他线程持有的独占锁时,该线程会被阻塞。如果当线程已经获取到锁,可以再次获取锁,那么就是可重入锁。可以无限次的进入线程锁内。

自旋锁:当前线程在获取锁时,如果锁已经被被其他线程占有,它不会马上阻塞自己,在不放弃CPU使用权的情况下,多次尝试获取(默认尝试10次),可能后面几次获取到了锁,如果还是没有获取到锁,就会挂起阻塞。

3.原子操作类

JUC并发包里有AtomicLong、AtomicInteger等操作原子类,这些类都是使用非阻塞算法CAS实现的,相比使用锁,性能很多提升。主要完成一些原子性增减的操作逻辑
AotmicLong在高并发下存在性能问题。

4.1 LongAdder JDK1.8新增

LongAdder 是JDK1.8新增用来克服在高并发下使用AtomicLong的缺点。
原理:把一个变量分解成多个变量,让多个线程去竞争多个资源。

5 并发List源码分析

5.1 copyOnWriteArrayList

copyOnWriteArrayList是一个线程安全的ArrayList.

6 并发锁原理

6.1 LockSupport 工具类

JDK 中的 rt.jar 里面的 LockSupport 是个工具类,它的主要作用是挂起和唤醒线程。该工具类是创建锁和其他同步类的基础。
LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。LockSupport是使用Unsafe类实现的,下面介绍LockSupport中的几个主要函数。

1. void park() 挂起当前线程

如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()时会马上返回,
否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。

如下代码直接在main函数里面调用park方法,最终只会输出beginpark!,然后当前
线程被挂起,这是因为在默认情况下调用线程是不持有许可证的。

public static void main( String[] args )
	System. out .println( "begin park!" ) ;
	LockSupport.park() ;
	System. out.println( "end park!" ) ;
}

在其他线程调用unpark(Thread thread)方法并且将当前线程作为参数时,调用park方法而被阻塞的线程会返回。另外,如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则阻塞线程也会返回。所以在调用park方法时最好也使用循环条件判断方式。
需要注意的是,因调用park()方法而被阻塞的线程被其他线程中断而返回时并不会抛 出InterruptedException异常

2. void unpark(Thread thread)方法,当线程拥有许可证。

当一个线程调用unpark时,如果参数thread线程没有持有thread与LockSupport类 关联的许可证,则让thread线程持有如果thread之前因调用park()而被挂起,则调用unpark后,该线程会被唤醒。如果thread之前没有调用park,则调用unpark方法后,再调用park方法,其会立刻返回。修改代码如下。

调用unpark后,可以让代码有了继续执行下去的权限。

public static void main( String[] args )
	System. out .println( "begin park!" );
	//使当前线程获取到许可证
	LockSupport. unpark (Thread. currentThread()) ;
	//再次调用park方法
	LockSupport.park() ;
	System. out.println( "end park!" );
}

6.2 抽象同步队列 AQS 概述

6.2.1 AQS一一锁的底层支持

AbstractQueuedSynchronizer 抽象同步队列简称 AQS ,它是实现同步器的 础组件,并发包中锁的底层就是使用 QS 实现的
在这里插入图片描述
AQS是一个FIFO 的双向队列,其内部通过节点 head tail
首和队尾元素,队列元素的类型为 Node。

  1. 其中 Node 中的 thread 变量用来存放进入 AQS队列里面的线程:
  2. Node 节点内部的 SHARED 用来标记该线程是获取共 资源时被阻挂起后放入 QS 队列的,
  3. EXCLUSIVE 用来标记线程是 取独占资源时被挂起后放AQS 队列的
  4. waitStatus 记录当前线程等待状态,可以为
    CANCELLED (线程被取消了)、
    SIGNAL 线程需要被唤醒)、
    ONDITION (线程在条件队列里面等待〉、
    PROPAGATE (释放共享资源时需要通知其他节点〕;
  5. prev 记录当前节点的前驱节点,
  6. next 记录当前节点的后继节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值