操作系统运行一个程序时,会创建一个进程。操作系统调度的最小单元是线程,一个进程里可以创建多个线程,各个线程有着自己的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量,处理器在这些线程上高速切换。
- 更多的处理器核心--利用多线程将计算逻辑分配到多个处理器核心上,显著减少程序的处理时间。
- 更快的响应时间
- 更好的编程模型
系统操作采用时分的形式调度运行的线程,通过为线程分配时间片,当线程用完时间片后就会发生线程调度,并等待下次分配。线程的优先级决定线程需要多或少分配一些处理器资源的线程属性(级别在1-10,基本没有,系统甚至会忽略优先级的设置,不能保证成程序的正确性)。
线程的生命周期:
- New,线程被构建,还没调用start();
- Runnable,运行状态,java将操作系统中的就绪和运行两种状态同称为运行。
- Blocked,阻塞状态,线程阻塞与锁。
- Waiting,等待状态,进入等待,进入该状态表示当前线程需要等待其他线程做一些特定动作(通知或中断)。
- Time_Waiting,超时等待状态,该状态不同于Wating,是可以在指定的时间自行返回运行。
- Terminated,终止状态,表示当前线程已经被执行完毕。
jstack pid-----
"block 2" #15 prio=5 os_prio=0 tid=0x000000001eecd800 nid=0xc68 waiting for monitor entry [0x000000002140f000] java.lang.Thread.State: BLOCKED (on object monitor)##阻塞在获取锁对象上 at com.wxt.concurrency.sync.First$Blocked.run(First.java:57) - waiting to lock <0x000000076b9eca78> (a java.lang.Class for com.wxt.concurrency.sync.First$Blocked) at java.lang.Thread.run(Thread.java:748) "block 1" #14 prio=5 os_prio=0 tid=0x000000001eeca800 nid=0x7a94 waiting on condition [0x000000002130f000] java.lang.Thread.State: TIMED_WAITING (sleeping)##已经获取到了对象锁,超时等待 at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at com.wxt.concurrency.sync.First.second(First.java:64) at com.wxt.concurrency.sync.First$Blocked.run(First.java:57) - locked <0x000000076b9eca78> (a java.lang.Class for com.wxt.concurrency.sync.First$Blocked) at java.lang.Thread.run(Thread.java:748) "waiting" #13 prio=5 os_prio=0 tid=0x000000001eec9000 nid=0x6f94 in Object.wait() [0x000000002120e000] java.lang.Thread.State: WAITING (on object monitor)###阻塞等待获取对象锁 at java.lang.Object.wait(Native Method) - waiting on <0x000000076b9ea0e0> (a java.lang.Class for com.wxt.concurrency.sync.First$Waiting) at java.lang.Object.wait(Object.java:502) at com.wxt.concurrency.sync.First$Waiting.run(First.java:45) - locked <0x000000076b9ea0e0> (a java.lang.Class for com.wxt.concurrency.sync.First$Waiting) at java.lang.Thread.run(Thread.java:748) "time waiting" #12 prio=5 os_prio=0 tid=0x000000001eec7800 nid=0x4fe4 waiting on condition [0x000000002110f000] java.lang.Thread.State: TIMED_WAITING (sleeping)##超时等待 at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at com.wxt.concurrency.sync.First.second(First.java:64) at com.wxt.concurrency.sync.First$TimeWaiting.run(First.java:35) at java.lang.Thread.run(Thread.java:748)
daemon线程
main线程启动后,守护线程也跟着运行,main结束,daemon结束,当虚拟机退出时,所有的daemon线程都需要立即终止。不要再daemon中使用finally,不一定会执行。
线程的构建:
//根据父线程的属性,给子线程赋值。 Thread parent = currentThread(); this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); //设置子线程中的inheritableThreadLocals为父线程的inheritableThreadLocals if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID();
构建完后,前线程(父线程)会同步告知虚拟机,只要线程规划器空闲,应立即调用启动了start()方法的线程。
中断:
可理解为一个标志位属性,表示一个线程是否被其他线程进行中断 interrupt() 操作,线程通过检查自身是否被中断来进行响应,isInterrupted(),调用静态的Thread.interrupted() 进行标志位的复位。如果线程已经处于终结状态,即使被中断过,isInterrupted() 也会返回false。
过期的suspend、stop、resume
不要使用。suspend在调用后,线程不会释放已经占有的资源(锁),而是占着 资源进入睡眠状态,容易引发死锁。同样stop也不一定保证线程资源正常释放。
安全地终止线程
可以通过中断加标志位的方式进行终止,能够使线程在终止时有机会取清理资源。
多线程个线程同时访问变量时,每个线程都有着这个变量的拷贝(加快程序的执行),因此每个线程得到的数据不一定是最新的。
volidate可以告知程序对共享变量的访问要从共享内存中获取,对共享变量的改变也必须刷回共享变量,保证可见性。而synchronized可以确保同一时刻只有一个线程处于同步方法里,保证了可见性和排他性。
无论是同步块还是方法,其本质都是对一个对象的监视器(monitor)的获取,而这个获取是排他的,也就是只能有一个线程能够得到,只有先得到对象的监视器,才能进入同步方法或块中,否咋将阻塞在同步方法或块的入口处,进入Blocked状态。
javap -v xxx.class
stack=2, locals=3, args_size=1 0: ldc #2 // class com/wxt/concurrency/sync/First 2: dup 3: astore_1 4: monitorenter 同步块则使用 monitorenter 和exit 进行加锁和解锁 5: aload_1 6: monitorexit 7: goto 15 10: astore_2 11: aload_1 12: monitorexit 13: aload_2 14: athrow 15: invokestatic #3 // Method m:()V 18: return static synchronized void m(); descriptor: ()V flags: ACC_STATIC, ACC_SYNCHRONIZED 同步方法 使用标志位ACC_SYNCHRONIZED 来获取锁 Code: stack=0, locals=0, args_size=0 0: return LineNumberTable: line 29: 0 }
等待和通知机制
- 使用这些方法需要先获取锁。
- 调用wait,释放当前锁,状态从Running-----》Waiting,并将当前线程放置到对象的等待队列。
- 调用notify,释放锁,等待线程才有机会从wait返回。
- notify将等待队队列里的一个线程放到同步队列,而notifyAll是将所有的放到同步队列。
- wait返回的条件是已经得到了该锁。
等待/通知的经典范式
等待:
- 获取对象锁。
- 如果条件不满足,那么调用对象的wait,被通知后仍要检查条件。
- 条件满足则执行对应的逻辑。
通知:
- 获取对象的锁。
- 改变条件。
- 通知所有等待在对象上的线程。
Thread.join():当前线程A等待thread线程终止后才从thread.join()返回。其本质还是使用了wait方法。
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
ThreadLocal
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } 。。。。。。。。。。。。。。。。。。。。。。。 static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; // 1.这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量能提高性能,据说性能的提升不是一点两点(没有亲测) 2.当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。 Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } } private Entry getEntry(ThreadLocal<?> key) { //ThreadLocals依赖于附加的每线程线性探针哈希映射 //到每个线程(Thread.threadLocals和InheritableThreadLocals)。 //ThreadLocal对象充当键,通过threadLocalHashCode搜索 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else //key是弱引用,如果 key回收,避免value无法回收。 return getEntryAfterMiss(key, i, e); } //key为空时 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; //循环从entry中获取 while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) //获取为空时调用 清空掉 expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } //remove get set 都会调用 private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot //复制为null,删除掉 tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收。set操作也有类似的思想,将key为null的这些Entry都删除,防止内存泄露。有一个前提条件:要调用ThreadLocalMap的genEntry函数或者set函数。如果通过ThreadLocal的方法触发expungeStaleEntry()进行value置null,由于key为弱引用,那么gc后会出现 key回收了,value没有回收。
为什么要用WeakReference?
①强引用:Java中默认的引用类型,一个对象如果具有强引用那么只要这种引用还存在就不会被GC。
②软引用:简言之,如果一个对象具有弱引用,在JVM发生OOM之前(即内存充足够使用),是不会GC这个对象的;只有到JVM内存不足的时候才会GC掉这个对象。软引用和一个引用队列联合使用,如果软引用所引用的对象被回收之后,该引用就会加入到与之关联的引用队列中
③弱引用:如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器GC掉(被弱引用所引用的对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。弱引用也是和一个引用队列联合使用,如果弱引用的对象被垃圾回收期回收掉,JVM会将这个引用加入到与之关联的引用队列中。若引用的对象可以通过弱引用的get方法得到,当引用的对象呗回收掉之后,再调用get方法就会返回null
④虚引用:虚引用是所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。(不能通过get方法获得其指向的对象)
//自己的local变量 ThreadLocal.ThreadLocalMap threadLocals = null; //父线程的inheritableThreadLocals变量 只能获取到父线程初始的值,如果后续父线程改变,子线程获取步到最新的值。 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { Thread parent = currentThread(); //判断父类有没有inheritableThreadLocals 有赋值给自己的inheritableThreadLocals if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } static ThreadLocal.ThreadLocalMap createInheritedMap(ThreadLocal.ThreadLocalMap parentMap) { return new ThreadLocal.ThreadLocalMap(parentMap); } private ThreadLocalMap(ThreadLocal.ThreadLocalMap parentMap) { ThreadLocal.ThreadLocalMap.Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new ThreadLocal.ThreadLocalMap.Entry[len]; //变量父类的inheritableThreadLocals 复制 for (int j = 0; j < len; j++) { ThreadLocal.ThreadLocalMap.Entry e = parentTable[j]; if (e != null) { ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); ThreadLocal.ThreadLocalMap.Entry c = new ThreadLocal.ThreadLocalMap.Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } static class ThreadLocalMap { /** * 是继承自WeakReference,key是弱引用指向ThreadLocal的弱引用 */ static class Entry extends WeakReference<ThreadLocal<?>> { Object value; //ThreadLocal的引用,被传递给WeakReference的构造方法 Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } public WeakReference(T referent) { super(referent); } Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
设置为弱引用的key能预防大多数内存泄漏的情况。如果key 使用强引用,引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
内存的泄露和溢出
Memory leak:内存泄漏,程序申请内存后,无法释放已申请的内存空间,内存泄漏的堆积终将导致内存溢出。
Memory overflow:内存溢出,没有足够的内存提供申请者使用。
最好的方式就是将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,可以防止内存泄露。
https://www.jianshu.com/p/98b68c97df9b
线程池的实现
1.消除频繁创建和销毁线程的系统资源开销,2.面对过量任务的提交能够平缓的劣化。
interface ThreadPool<T extends Runnable> { void execute(T job); void shutDown(); void addWorkers(int num); void removeWorkers(int num); int getJobSize(); } class DefaultThreadPool<T extends Runnable> implements ThreadPool { //最大任务数 private static final int MaxJobs = 10; private static final int DefaultJobs = 5; private static final int minJobs = 1; //任务列表 private LinkedList<Runnable> jobs = new LinkedList<>(); //执行者表 private List<Worker> workers = Collections.synchronizedList(new ArrayList<Worker>()); //执行者数量 private int workerNum = DefaultJobs; private AtomicLong threadId = new AtomicLong(); public DefaultThreadPool() { initWorkers(DefaultJobs); } public DefaultThreadPool(int num) { workerNum = num > MaxJobs ? MaxJobs : num < minJobs ? minJobs : num; initWorkers(workerNum); } @Override public void execute(Runnable job) { if (job != null) { synchronized (jobs) { jobs.addLast(job); jobs.notify(); } } } @Override public void shutDown() { for (Worker worker : workers) { worker.shutdown(); } } @Override public void addWorkers(int num) { synchronized (jobs) { if (num + this.workerNum > MaxJobs) { num = MaxJobs - this.workerNum; } initWorkers(workerNum); this.workerNum += num; } } @Override public void removeWorkers(int num) { synchronized (jobs){ if(num>=this.workerNum){ throw new IllegalArgumentException(""); } int count=0; while (count<num){ Worker worker=workers.get(count); if(workers.remove(worker)){ worker.shutdown(); count++; } } this.workerNum-=count; } } @Override public int getJobSize() { return jobs.size(); } private void initWorkers(int num){ for(int i=0;i<num;i++){ Worker worker=new Worker(); workers.add(worker); Thread thread=new Thread(worker,"thread-pool-worker-"+threadId.incrementAndGet()); thread.start(); } } class Worker implements Runnable{ private volatile boolean running=true; @Override public void run() { while (running){ Runnable job=null; synchronized (jobs){ while (jobs.isEmpty()){ try { jobs.wait(); }catch (Exception e){ Thread.currentThread().interrupt(); return; } } job = jobs.removeFirst(); } if(job!=null){ try { job.run(); }catch (Exception e){ } } } } public void shutdown(){ running=false; } } }