java并发/线程
2020-07-29 09:49:16 0 举报
Java并发编程学习
作者其他创作
大纲/内容
thread
sleep yeild join
自旋方式查看前置节点状态,直到SIGNAL切换为CANCEL
无volatile修饰 字段running
Block
耗时:12515939
线程
耗时:2044894
waitStatus
纤程/协程
next
head
Thread t1 = new Thread(()->{ // Thread.sleep// Object.wait with timeout// Thread.join with timeout// LockSupport.parkNanos// LockSupport.parkUntil}).start();t1.getState();
prev
Node
Runnable包括两种状态:1、Ready,等待队列中,未被cpu使用;2、Running,cpu正在使用;
New
4、lamda
结论:锁力度要设置得尽可能细;但当一个方法中有多处synchronized锁时,也可考虑synchronized修饰到方法上。
在代码块中加锁synchronized,防止多线程间因并发抢占资源,导致程序问题
/** * @author Ben Li. * @since: 2020/7/22 5:10 下午 * * 保证线程可见性 */public class VisibilityTest { /*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别 void m() { System.out.println(\"m start\"); while (running) { } System.out.println(\"m end!\
使用
1、对于弱引用关联着的对象,JVM GC时立马对此对象回收。WeakReference<MyObject> m = new WeakReference<>(new MyObject());System.out.println(m.get());System.gc();System.out.println(m.get());
自定义类 MyThread extend Threadmain:new MyThread().start();
Thread t = new Thread(()->{}).start()t.getState();
1、对于软引用关联着的对象,在系统将要发生内存溢出异常之前,才将会把这些对象列进回收范围进行第二次回收。2、如果这次回收还没有足够的内存,才会抛出内存溢出异常。内存不足时,JVM会对此对象回收。SoftReference<byte[]> m = new SoftReference<>(new byte[1024 * 1024 * 10]);//m = null;System.out.println(m.get());System.gc();
Sleep让当前正在执行任务的线程睡眠,若线程run方法带synchronized代码块,则在睡眠期间继续持有锁,不会释放;
ExchangerExchanger<V> exchanger = new Exchanger<>();exchanger.exchange(V v);用于线程间通信,匹配两个线程,交换信息。线程T1执行exchange方法,阻塞等待线程T2调用exchange方法,线程T2 exchange()后T1 阻塞释放,完成通信。
prev=null
线程池工具类Executors.newCachedThreadPool()Executors.newSingleThreadExecutor()...
Thread t1 = new Thread(()->{ // Object.wait with no timeout// Thread.join with no timeout// LockSupport.park}).start();t1.getState();
2、防止指令重排序
进程
CLH同步队列
一个程序不同的执行路径,是操作系统调度的基本单位。不占有内存空间,共享进程的内存空间。
next=Node2
目的
加锁操作的几个类
1、Thread
waitStatus=SIGNAL
样例代码:public class SynchronizedTest { private Integer count = 0; public synchronized void add() { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } this.count++; } public Integer getCount() { return count; } public static void main(String[] args) { SynchronizedTest s = new SynchronizedTest(); Thread[] threads = new Thread[10]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { s.add(); }); } Long start = System.nanoTime(); for (int i = 0; i < threads.length; i++) { threads[i].start(); } for (int i = 0; i < threads.length; i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.getCount() + \"... time: \" + (System.nanoTime() - start)); }}
Node 是 AbstractQueuedSynchronizer 的内部静态类。在 CLH 同步队列中,一个节点(Node),表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next)、条件队列后继节点(nextWaiter)
Node2
AbstractQueuedSynchronizer (AQS)
AQS 依赖CLH队列来完成同步状态的管理1、当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程。2、当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
强软弱虚四种引用
ReadWriteLockReadWriteLock lock = new ReentrantReadWriteLock(); Lock readLock = lock.readLock(); Lock writeLock = lock.writeLock();读写锁,读锁与读锁间共享不阻塞,写锁独享阻塞。读多写少时,ReadWriteLock效率比普通独享锁高得多!
tail
注意
Yield线程执行yield方法,让出cpu使用权,自己回到等待队列中,状态从Runing变为Ready;
样例代码,同1public void add() { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (this) { this.count++; } }
github笔记整理:https://github.com/fatbun/concurrent-study
CLH阻塞队列,入列
线程的线程,存活在用户态环境中,无需经过操作系统调度。
1、保证线程可见性
结果:主线程设置running为false后,线程退出while循环。原因:当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据,并拷贝最新数据到工作空间。
无锁锁对象头 00 标志位
CountDownLatchCountDownLatch latch = new CountDownLatch(5);latch.countDown();latch.await();初始化Latch值5,每次countDown -1,await阻塞等待直到countDown到0释放阻塞注意:只执行一次countdown,不能复位。
重量级锁锁对象头 11 标志位当自旋次数达到设定的最大值,仍未获得锁资源,此时锁等级升级为重量级锁。向操作系统申请mutex互斥锁,阻塞其他等待线程,此时其他线程状态为Block,让出cpu,等待monitor notify。跟自旋锁相反,适用于锁等待时间特别长,且线程并发量特别高的场景。
Join阻塞等待指定线程完成,当前线程才能继续执行join后面的代码;
CLH阻塞队列是如何运作
NodeN
特性:
lamda表达式new Thread(()->{ System.out.println(\"Hello Lambda!\");}).start()
CLH
Thread t = new Thread(()->{ t.getState();}).start();
Runnable
概念
Waiting
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排。一般分为以下三种:源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令
2、Runnable
自定义类 MyRun extend Runnablenew Thread(new MyRun()).start();
虚引用
AQS 使用的 CLH 锁,需要一个虚拟 head 节点,这个节点的作用是防止重复释放锁。当第一个进入队列的节点没有前置节点的时候,就会创建一个虚拟的。
偏向锁,锁对象头 01 标志位synchronized锁对象的对象头上,会存放锁信息,如:是否偏向锁、被锁线程信息等。synchronized是可重入的,即当前线程在获得对象的synchronized锁过程中,可以多次使用被锁方法。
有volatile修饰 字段running
Thread t = new Thread();t.getState();
自旋锁锁对象头 10 标志位线程1,获得synchronized对象锁,此时锁等级为偏向锁;随后线程2请求发现该所已被线程1占有,此时锁等级升级为自旋锁。线程2默认自旋10次,等待锁资源释放。线程自旋的过程中仍然占用cpu资源。因此自旋锁不适用于锁等待时间特别长,且线程并发量特别高的场景。用户可以通过-XX:PreBlockSpin来进行更改自旋次数。
结果:线程一直while循环。原因:running字段存储在堆内存空间,当线程t1执行时,会把running字段拷贝到自己的工作空间上做后续的使用(此时running为true),后续主线程把running置为false,线程工作空间上的running字段并不会发生改变。
创建线程的几个方法
弱引用
2、synchronized加在代码块上
假设指令不重排,则右边代码条件 x==0 && y==0 永远不成立。验证:出现打印语句:出现指令重排!
AQS
volatile
1.1 保证volatile修饰对象可见,但不保证其属性可见性
进程、线程、纤程的定义
强引用
1、保证线程可见性2、防止指令重排序
...
Node1
TimeWaiting
软引用
LongAdder原理:特殊的AtomcXXX操作,使用分段锁。目的:cas操作在高并发下会造成自旋不断失效,分段锁能减低cas热度,从而提高效率。
synchronized的优化
1、对于强引用关联着的对象,在系统将要发生内存溢出异常之前,不会对这些对象列入回收范围。2、如果JVM回收还没有足够的内存,会抛出内存溢出异常。Object o = new Object()System.gc();
一个程序在操作系统上运行的状态,是操作系统分配资源的基本单位。占有独立的内存空间。
AtomicInteger原理:incrementAndGet()累加的线程安全,由unsafe包下的cas(自旋锁)保证原子操作。
LockSupportLockSupport.park()LockSupport.parkUntil(long t)LockSupport.parkNanos(long t)LockSupport.unpark(Thread t)可由unpark(t)释放线程阻塞,或线程interrupt()中断阻塞释放
线程的几个状态
CLH阻塞队列,出列
1、synchronized加在方法上
synchronized
Terminated
4、Executor
prev=Node1
next=nextNode
(一)AQS阻塞队列,双向链表 /** 当前节点的等待状态 */ volatile int waitStatus; /** * 在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。 */ static final int CANCELLED = 1; /** * 处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。 */ static final int SIGNAL = -1; /** * 该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。 */ static final int CONDITION = -2; /** * 在共享模式中,该状态标识结点的线程处于可运行状态。 */ static final int PROPAGATE = -3;(二)AQS条件队列,单向链表 /**条件队列,单向链表保存,用nextWaiter来连接*/ Node nextWaiter; /** 共享模式,直接唤醒下一个节点 */ static final Node SHARED = new Node(); /** 独占模式,等待当前线程执行完成后再唤醒 */ static final Node EXCLUSIVE = null;
0 条评论
下一页