Java并发编程
一、基础篇
进程是系统进行资源分配和调动的基本单位,一个进程中至少有一个线程,进程中多个线程共享进程的资源。
线程是进程中的一个实体,线程是不会独立存在的,没有进程就没有线程。
对于CPU资源比较特殊,线程才是CPU分配的基本单位。
main函数启动->JVM进程->main函数线程称为主线程
内存与线程
内存与线程的关心,主要指JVM内存模型与线程之间的关系,它也是线程安全问题的主要诱因。
使用JDK工具观察线程
jcmd
jstack
jvisualVM
jconsole
线程创建的三种方法
- 继承Thread类
-
实现Runnable接口
-
实现Callable接口
Account类
package com.aidata.concurrency; public class Account { private String accountNo; private String accountName; private boolean valid; public Account(){} public Account(String accountNo, String accountName, boolean valid){ this.accountNo = accountNo; this.accountName = accountName; this.valid = valid; } public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public String getAccountName() { return accountName; } public void setAccountName(String accountName) { this.accountName = accountName; } public boolean isValid() { return valid; } public void setValid(boolean valid) { this.valid = valid; } @Override public String toString() { return "Account{" + "accountNo='" + accountNo + '\'' + ", accountName='" + accountName + '\'' + ", valid=" + valid + '}'; } }
继承Thread
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
package com.aidata.concurrency; // 1.继承Thread public class CreateThreadExtendsThread extends Thread { private Account account; public CreateThreadExtendsThread(){} public CreateThreadExtendsThread(Account account){ this.account = account; } public void setAccount(Account account){ this.account = account; } // 2.覆写run方法 public void run(){ System.out.println(this.getName() + " Account's information:" + this.account +"," + this.getState()); } public static void main(String[] args) { Account account = new Account("999999", "Wang", true); CreateThreadExtendsThread thread0 = new CreateThreadExtendsThread(); // 线程传参方式1 thread0.setAccount(account); // 线程没有调用start,状态是NEW System.out.println(thread0.getState()); // 调用start()方法会执行线程中的run()方法,状态是RUNNABLE thread0.start(); try { // 休眠一秒钟,保证thread0执行完 Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } //TERMINATED System.out.println(thread0.getState()); // 线程传参方式2 CreateThreadExtendsThread thread1 = new CreateThreadExtendsThread(account); thread1.start(); } }
结果
NEW Thread-0 Account's information:Account{accountNo='999999', accountName='Wang', valid=true},RUNNABLE TERMINATED Thread-1 Account's information:Account{accountNo='999999', accountName='Wang', valid=true},RUNNABLE
如果注释掉上面休眠的代码,结果
NEW
RUNNABLE
Thread-1 Account's information:Account{accountNo='999999', accountName='Wang', valid=true},RUNNABLE
Thread-0 Account's information:Account{accountNo='999999', accountName='Wang', valid=true},RUNNABLE
优点:简单,Thread类中有大量的方法,可以通过this获得线程的丰富信息
缺点:Java是单继承的,继承了Thread就无法继承其他类了
多线程实现图片下载
package com.aidata.thread; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; public class TestThread extends Thread{ private String url; private String fileName; public TestThread(String url, String fileName){ this.url = url; this.fileName = fileName; } @Override public void run() { PicDownloader picDownloader = new PicDownloader(); picDownloader.downloader(this.url, this.fileName); System.out.println("下载了图片,名为:" + fileName); } public static void main(String[] args) { TestThread t1 = new TestThread("https://img2018.cnblogs.com/i-beta/1755628/202002/1755628-20200213225816807-610773901.png", "1.png"); TestThread t2 = new TestThread("https://img2018.cnblogs.com/i-beta/1755628/202002/1755628-20200213230257947-343445373.png", "2.png"); TestThread t3 = new TestThread("https://img2018.cnblogs.com/i-beta/1755628/202002/1755628-20200214161105678-97811026.png", "3.png"); t1.start(); t2.start(); t3.start(); } } class PicDownloader{ public void downloader(String url, String fileName){ try { FileUtils.copyURLToFile(new URL(url), new File(fileName)); } catch (IOException e) { e.printStackTrace(); } } }
结果
实现Runnable接口
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
实现接口
package com.aidata.concurrency; public class CreateThreadImplementsRunnable implements Runnable{ private Account account; public CreateThreadImplementsRunnable(){} public CreateThreadImplementsRunnable(Account account){ this.account = account; } public void setAccount(Account account) { this.account = account; } public void run() { // 接口中只有run()方法,要获取名称使用下面的方法 System.out.println(Thread.currentThread().getName() + " Account's information: " + this.account); } public static void main(String[] args) { final Account account = new Account("888888", "Wang", true); CreateThreadImplementsRunnable thread0 = new CreateThreadImplementsRunnable(); // 创建线程方式1 thread0.setAccount(account); new Thread(thread0).start(); // 创建线程方式2 CreateThreadImplementsRunnable thread1 = new CreateThreadImplementsRunnable(account); new Thread(thread1).start(); // 创建线程方式3 接口方便之处是可以使用匿名内部类来实现 new Thread(new Runnable() { public void run() { System.out.println(account); } }).start(); } }
接口中没有start()方法,必须借助Thread类才能启动,以及获取线程相关的信息。
实现Callable接口
@FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
实现接口
package com.aidata.concurrency; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class CreateThreadImplementsCallable implements Callable<Account> { private Account account; public CreateThreadImplementsCallable(){} public CreateThreadImplementsCallable(Account account){ this.account = account; } public void setAccount(Account account) { this.account = account; } // 返回值和泛型设置有关 public Account call() throws Exception { System.out.println(Thread.currentThread().getName() + " Account's information: " + this.account); this.account.setValid(false); Thread.sleep(3000); return this.account; } public static void main(String[] args) { Account account = new Account("66666666", "Wang", true); CreateThreadImplementsCallable call = new CreateThreadImplementsCallable(); call.setAccount(account); // 异步阻塞模型 FutureTask<Account> ft = new FutureTask<Account>(call); new Thread(ft).start(); try { // get得到的值类型和泛型有关 Account result = ft.get(); System.out.println("result:" + result); } catch (Exception e){ e.printStackTrace(); } } }
结果
Thread-0 Account's information: Account{accountNo='66666666', accountName='Wang', valid=true}
result:Account{accountNo='66666666', accountName='Wang', valid=false}
三秒后,才会打印result,因为
Account result = ft.get();
是阻塞的,线程中的执行完,即休眠三秒后,才继续执行下面的
JOIN等待线程执行终止
使用场景:等待线程执行终止之后,继续执行
易混淆知识点:join方法为Thread类直接提供的方法,而wait和notify为Object类中的方法
可以使用CountDownLatch达到同样的效果
IDEA 双击shift,搜索Objec,使用快捷键Alt+7
Thread的三个join方法
public final void join() throws InterruptedException { join(0); } 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; }
// 还是使用的Object的wait方法 wait(delay); now = System.currentTimeMillis() - base; } } }
// nanos 纳秒值,四舍五入的概念,0-500000之间不管,只用毫秒,500000-999999就毫秒加1 public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); }
例子
package com.aidata.concurrency; public class JoinDemo { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { public void run() { try { Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " run over!"); } }); Thread t2 = new Thread(new Runnable() { public void run() { try{ Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " run over!"); } }); t1.start(); t2.start(); System.out.println(Thread.currentThread().getName() + " wait " + t1.getName() + " and " + t2.getName() + " run over!"); // 打开和关闭此段注释观察执行效果来理解join的用途 try{ t1.join(); t2.join(); }catch (InterruptedException e){ e.printStackTrace(); } // 打开和关闭此段注释观察执行效果来理解join的用途 // try{ // t1.join(1000); // t2.join(1000); // // t1.join(1000, 500); // // t2.join(1000, 500); // }catch (InterruptedException e){ // e.printStackTrace(); // } System.out.println("final " + t1.getName() + " and " + t2.getName() + " run over!"); System.out.println("t1's state: " + t1.getState()); System.out.println("t2's state: " + t2.getState()); } }
结果
main wait Thread-0 and Thread-1 run over! Thread-0 run over! Thread-1 run over! final Thread-0 and Thread-1 run over! t1's state: TERMINATED t2's state: TERMINATED
运行到
t1.join();
t2.join();
时,主线程会阻塞,等待t1、t2执行完,再执行
System.out.println("final " + t1.getName() + " and " + t2.getName() + " run over!");
如果注释掉上面的两个join,结果为
main wait Thread-0 and Thread-1 run over! final Thread-0 and Thread-1 run over! t1's state: TIMED_WAITING t2's state: TIMED_WAITING Thread-0 run over! Thread-1 run over!
t1、t2不会阻塞主线程,由于休眠,线程里的内容最后才执行完,TIMED_WAITING带有时间的等待,即在执行sleep
sleep方法解析
Thread类中的一个静态方法,暂时让出执行权,不参与CPU调度,但是不释放锁。时间到了就进入就绪状态,一旦获取到CPU时间片,则继续执行。
public static native void sleep(long millis) throws InterruptedException; /** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds plus the specified * number of nanoseconds, subject to the precision and accuracy of system * timers and schedulers. The thread does not lose ownership of any * monitors. * * @param millis * the length of time to sleep in milliseconds * * @param nanos * {@code 0-999999} additional nanoseconds to sleep * * @throws IllegalArgumentException * if the value of {@code millis} is negative, or the value of * {@code nanos} is not in the range {@code 0-999999} * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis); }
一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。 "A native method is a Java method whose implementation is provided by non-java code." 在定义一个native method时,并不提供实现体(有些像定义一个java interface),因为其实现体是由非java语言在外面实现的。
用异常打断sleep
package com.aidata.concurrency; public class SleepDemo { public static void main(String[] args) { final Object lock = new Object(); Thread t1 = new Thread(new Runnable() { public void run() { synchronized (lock){ System.out.println(Thread.currentThread().getName() + " get Lock, sleeping"); } try{ Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " sleep over and run over!"); } }); Thread t2 = new Thread(new Runnable() { public void run() { synchronized (lock){ System.out.println(Thread.currentThread().getName() + " get Lock, sleeping"); } try{ Thread.sleep(2000); }catch (Exception e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " sleep over and run over!"); } }); t1.start(); t2.start(); t1.interrupt(); } }
结果
Thread-0 get Lock, sleeping Thread-1 get Lock, sleeping java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.aidata.concurrency.SleepDemo$1.run(SleepDemo.java:14) at java.lang.Thread.run(Thread.java:748) Thread-0 sleep over and run over! Thread-1 sleep over and run over!
Thread-0没有sleep,而是抛出异常后直接打印了 Thread-0 sleep over and run over!
没有interrupt结果
Thread-0 get Lock, sleeping Thread-1 get Lock, sleeping Thread-0 sleep over and run over! Thread-1 sleep over and run over!
yield方法
不建议使用
Thread类中的静态native方法,让出剩余的时间片,本身进入就绪状态,CPU再次调度还可能调度到本线程。
易混淆知识点:sleep是在一段时间内进入阻塞状态,CPU不会调度它。而yield是让出执行权,本身处于就绪状态,CPU还可能立即调度它。
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();
例子
package com.aidata.concurrency; public class YieldDmo0 extends Thread{ @Override public void run() { System.out.println(this.getName() + " yield"); this.yield(); System.out.println(this.getName() + " run over"); } public static void main(String[] args) { for (int i=0; i<1000; i++){ YieldDmo0 demo = new YieldDmo0(); demo.start(); } } }
会出现这种连城一片的形式
... Thread-671 yield Thread-673 yield Thread-672 yield Thread-673 run over Thread-671 run over Thread-672 run over ...
加锁后
package com.aidata.concurrency; // yield 让出执行权,但是不释放锁 public class YieldDmo1 extends Thread{ // 共享锁 public static Object lock = new Object(); @Override public void run() { synchronized (lock){ System.out.println(this.getName() + " yield"); this.yield(); System.out.println(this.getName() + " run over"); } } public static void main(String[] args) { for (int i=0; i<1000; i++){ YieldDmo1 demo = new YieldDmo1(); demo.start(); } } }
成对出现
Thread-0 yield Thread-0 run over Thread-5 yield Thread-5 run over ...
yield 不释放锁,每个线程都执行完才能执行另外的线程
wait会使线程进入阻塞状态,且是释放锁的。wait方法结合synchronized关键字、notify方法使用。
package com.aidata.concurrency; // yield 让出执行权,但是不释放锁 public class YieldDmo1 extends Thread{ // 共享锁 public static Object lock = new Object(); @Override public void run() { synchronized (lock){ System.out.println(this.getName() + " yield"); // this.yield(); // 使用wait方法来做对比,查看释放锁与不释放锁的区别 try{ lock.wait(); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(this.getName() + " run over"); } } public static void main(String[] args) { for (int i=0; i<1000; i++){ YieldDmo1 demo = new YieldDmo1(); demo.start(); } // 配合wait使用看效果 synchronized (lock){ lock.notifyAll(); } } }
上面全是yield,执行notifyAll()后下面全是run over
... Thread-994 yield Thread-995 yield Thread-996 yield Thread-999 yield Thread-996 run over Thread-995 run over Thread-994 run over Thread-993 run over Thread-992 run over Thread-991 run over ...
每次线程执行,执行第一个打印后,执行wait()方法都会阻塞,并释放锁给另一个线程,另一个线程也执行第一句打印,执行wait()释放锁给另一个线程...
直到执行notifyAll()方法,所有线程结束阻塞,开始执行剩下的语句。
线程中断方法
Interrupt相关方法
线程中断是线程间的一种协作模式,通过设置线程中断标志来实现,线程根据这个标志来自行处理。
Thread中相关方法
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(); } /** * 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); // true,改变中断标记 } public boolean isInterrupted() { return isInterrupted(false); // false,返回原有的中断标记,不改变,true就是true,false就是false } /** * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. */ private native boolean isInterrupted(boolean ClearInterrupted); private native void interrupt0();
例子
package com.aidata.concurrency; // isInterrupted和interrupt的使用 public class TnterruptDemo0 { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { public void run() { // 2.开始执行循环 for (int i=0; i<99999; i++){ // 3.判断是否为中断状态,如果是中断则退出循环 if (Thread.currentThread().isInterrupted()){ System.out.println(Thread.currentThread().getName() + " interrupted"); break; } System.out.println(Thread.currentThread().getName() + i + " is running"); } } }); // 1.启动 t1.start(); // 4.调用中断,是否会中断死循环? t1.interrupt(); // 把t1线程的中断标记设置成了true try { t1.join(); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(t1.getState()); } }
结果
Thread-0 interrupted
TERMINATED
也就是说线程里执行了
if (Thread.currentThread().isInterrupted()){ System.out.println(Thread.currentThread().getName() + " interrupted"); break; }
如果去掉上面的判断,则
... Thread-099996 is running Thread-099997 is running Thread-099998 is running TERMINATED
也就是说,会不会被打断是取决于上面的判断的,而非
// 4.调用中断,是否会中断死循环? t1.interrupt(); // 把t1线程的中断标记设置成了true
它只是为t1打了一个中断标签,并没有真的去打断,你可以捕获并实现相关逻辑
public static boolean interrupted() { return currentThread().isInterrupted(true); // true,改变中断标记 } public boolean isInterrupted() { return isInterrupted(false); // false,返回原有的中断标记,不改变,true就是true,false就是false }
isInterrupted() 会返回中断标记,不会进行修改
interrupted() 会修改中断标记
package com.aidata.concurrency; public class InterruptDemo1 { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { public void run() { // 3.条件为!true=false退出循环 while (!Thread.currentThread().interrupted()){ } // 4.这里输出的是什么true还是false System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().isInterrupted()); } }); // 1.开始 t1.start(); // 2.中断标记设置为true t1.interrupt(); try{ t1.join(); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("main is run over"); } }
结果
Thread-0:false main is run over
没有进入while循环,则 Thread.currentThread().interrupted() 为true
而下面打印中 Thread.currentThread().isInterrupted() 结果为false,说明interrupted() 会修改当前线程的中断标记
while循环的写法应该是
while (!Thread.interrupted()){ }
因为interrupted是静态方法,只对当前的线程生效,所以结果是一样的
while判断更换为 !Thread.currentThread().isInterrupted()
package com.aidata.concurrency; public class InterruptDemo1 { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { public void run() { // 3.条件为!true=false退出循环 // 5.如果这里更换为Thread.currentThread().isInterrupted() while (!Thread.currentThread().isInterrupted()){ } // 4.这里输出的是什么true还是false // 6.这里输出的是什么true还是false System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().isInterrupted()); } }); // 1.开始 t1.start(); // 2.中断标记设置为true t1.interrupt(); try{ t1.join(); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("main is run over"); } }
结果为
Thread-0:true main is run over
isInterrupted() 会返回中断标记,不会进行修改
中断异常
join方法和sleep一样会抛出中断异常
Thread.interrupt()方法中断线程时,join方法的异常只能在自身线程才能被捕获,在其它线程调用时无法被捕获:
package com.aidata.concurrency; public class JoninDemo2 { public static void main(String[] args) { final Thread t0 = new Thread(new Runnable() { public void run() { System.out.println("t0 is running"); System.out.println(Thread.currentThread().getName() + " run over!"); } }); Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("t1 is runnng"); try { t0.start(); Thread.currentThread().interrupt(); t0.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " run over!"); } }); t1.start(); } }
t0线程运行在t1线程中,t1线程interrupt后,遇到t0.join()会抛出异常,也就是这个t0.join()没有运行,即没有阻塞等待t0完成
结果:
t1 is runnng t0 is running Thread-0 run over! java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Thread.join(Thread.java:1252) at java.lang.Thread.join(Thread.java:1326) at com.aidata.concurrency.JoninDemo2$2.run(JoninDemo2.java:20) at java.lang.Thread.run(Thread.java:748) Thread-1 run over!
package com.aidata.concurrency; public class JoinDemo1 { public static void main(String[] args) { // 创建t0线程 final Thread t0 = new Thread(new Runnable() { public void run() { System.out.println("t0 will sleep 00000000000000000000000"); try { Thread.sleep(2000*20); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("sleep的异常"); } System.out.println(Thread.currentThread().getName() + " run over!"); } }); // 创建main线程 final Thread mainThread = Thread.currentThread(); Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("in t1 now 1111111111111111111"); // 调用主线程的interrupt方法,开启中断标记,会影响主线中的join方法抛出异常,但是并不会阻碍t0线程的运行 mainThread.interrupt(); // 修改为t0.interrupt();观察效果,会影响t0线程的sleep方法抛出异常,让t0线程结束 // t0.interrupt(); System.out.println(mainThread.getName() + " interrupt!"); System.out.println(Thread.currentThread().getName() + " run over!"); } }); t0.start(); t1.start(); System.out.println(Thread.currentThread().getName() + " wait " + t0.getName() + " and " + t1.getName() + " run over!"); try { System.out.println("will run t0.join()-----------"); t0.join(); System.out.println("running t0.join()==========="); }catch (InterruptedException e){ e.printStackTrace(); System.out.println("t0.join的异常"); } System.out.println("final: " + t0.getName() + " and " + t1.getName() + " run over!"); System.out.println("t0's state: " + t0.getState()); System.out.println("t1's state: " + t1.getState()); System.out.println("main's state: " + mainThread.getState()); } }
结果
main wait Thread-0 and Thread-1 run over! will run t0.join()----------- in t1 now 1111111111111111111 t0 will sleep 00000000000000000000000 main interrupt! Thread-1 run over! java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Thread.join(Thread.java:1252) at java.lang.Thread.join(Thread.java:1326) at com.aidata.concurrency.JoinDemo1.main(JoinDemo1.java:42) t0.join的异常 final: Thread-0 and Thread-1 run over! t0's state: TIMED_WAITING t1's state: TERMINATED main's state: RUNNABLE
可知
先打印了
System.out.println(Thread.currentThread().getName() + " wait " + t0.getName() + " and " + t1.getName() + " run over!");
System.out.println("will run t0.join()-----------");
然后,线程t0和t1开始执行,to进行sleep,t1让主线程interrupt,t1执行完后,主线程的t0.join()开始执行,因为主线程被interrupt了,主线程中的子线程调用join会导致异常,但是该异常不会停止t0线程,to线程仍然执行,只是不join()了,也就是不阻塞执行,因此主线程中下面的打印语句都执行了,一直等到t0休眠结束,整个程序结束。
还可以t0.interrupt(),这会导致sleep抛出异常,导致t0结束
package com.aidata.concurrency; public class JoinDemo1 { public static void main(String[] args) { // 创建t0线程 final Thread t0 = new Thread(new Runnable() { public void run() { System.out.println("t0 will sleep 00000000000000000000000"); try { Thread.sleep(2000*20); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("sleep的异常"); } System.out.println(Thread.currentThread().getName() + " run over!"); } }); // 创建main线程 final Thread mainThread = Thread.currentThread(); Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("in t1 now 1111111111111111111");// 修改为t0.interrupt();观察效果,会影响t0线程的sleep方法抛出异常,让t0线程结束 t0.interrupt(); System.out.println(mainThread.getName() + " interrupt!"); System.out.println(Thread.currentThread().getName() + " run over!"); } }); t0.start(); t1.start(); System.out.println(Thread.currentThread().getName() + " wait " + t0.getName() + " and " + t1.getName() + " run over!"); System.out.println("final: " + t0.getName() + " and " + t1.getName() + " run over!"); System.out.println("t0's state: " + t0.getState()); System.out.println("t1's state: " + t1.getState()); System.out.println("main's state: " + mainThread.getState()); } }
结果
main wait Thread-0 and Thread-1 run over! t0 will sleep 00000000000000000000000 in t1 now 1111111111111111111 final: Thread-0 and Thread-1 run over! main interrupt! Thread-1 run over! t0's state: TIMED_WAITING t1's state: TERMINATED main's state: RUNNABLE java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.aidata.concurrency.JoinDemo1$1.run(JoinDemo1.java:12) at java.lang.Thread.run(Thread.java:748) sleep的异常 Thread-0 run over!
线程安全
可见性
安全性
package com.aidata.concurrency; class User{ private String name; private String pass; public User(String name, String pass){ this.name = name; this.pass = pass; } public void set(String name, String pass){ this.name = name; try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.pass = pass; System.out.println(Thread.currentThread().getName() + " -name=" + this.name + " pass=" + this.pass); } } /** * servlet是单利多线程的,Servlet本身并不是一个单例实现,只是容器加载的时候只实例化一次,所造成的单例现象 */ class UserServlet{ private User user; public UserServlet(){ user = new User("w", "123"); } public void setPass(String name, String pass){ user.set(name, pass); } } public class DemoThread00 { public static void main(String[] args) { final UserServlet us = new UserServlet(); new Thread(new Runnable() { public void run() { us.setPass("李四", "777"); } }).start(); new Thread(new Runnable() { public void run() { us.setPass("王五", "888"); } }).start(); } }
结果
Thread-0 -name=王五 pass=777
Thread-1 -name=王五 pass=888
两个都是王五,原因:
两个线程,操作同一个对象us
第一个线程
us.setPass("李四", "777");
实际调用了User的
public void set(String name, String pass){ this.name = name; try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.pass = pass; System.out.println(Thread.currentThread().getName() + " -name=" + this.name + " pass=" + this.pass); }
首先设定名字为李四,然后就休眠了5秒,此时第二个线程也操作us,设定名字为王五,也就是说进行了覆盖。5秒后,第一个线程继续工作,密码改为777,进行打印。
第二个线程睡眠5秒后,把密码改为888,进行打印。
线程安全与Synchronized
当多个线程访问某一个类、对象或方法时,这个类、对象或方法都能表现出与单线程执行时一致的行为,那么这个类、对象或方法是线程安全的。
线程安全问题是由全局变量和静态变量引起的。
若每个线程中全局变量、静态变量只有读操作,而无些操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都是需要考虑线程同步,否则的话就可能影响线程安全。
Synchronized的作用是加锁,所有的Synchronized方法都会顺序执行,这里指占用CPU的顺序。
Synchronized 方法执行方式:
- 首先尝试获得锁
- 如果获得锁,则执行Synchronized的方法体内容
- 如果无法获得锁则等待,并且不断的尝试去获得锁,一旦锁释放,则多个线程会同时去尝试获得锁,造成锁竞争问题
锁竞争问题,在高并发、线程数量高时会引起CPU占用居高不下,或直接宕机。
package com.aidata.concurrency; class User{ private String name; private String pass; public User(String name, String pass){ this.name = name; this.pass = pass; } public synchronized void set(String name, String pass){ this.name = name; try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } this.pass = pass; System.out.println(Thread.currentThread().getName() + " -name=" + this.name + " pass=" + this.pass); } } /** * servlet是单利多线程的,Servlet本身并不是一个单例实现,只是容器加载的时候只实例化一次,所造成的单例现象 */ class UserServlet{ private User user; public UserServlet(){ user = new User("w", "123"); } public void setPass(String name, String pass){ user.set(name, pass); } } public class DemoThread00 { public static void main(String[] args) { final UserServlet us = new UserServlet(); new Thread(new Runnable() { public void run() { us.setPass("李四", "777"); } }).start(); new Thread(new Runnable() { public void run() { us.setPass("王五", "888"); } }).start(); } }
加锁后,线程安全
Thread-0 -name=李四 pass=777
Thread-1 -name=王五 pass=888
对象锁和类锁
没有锁
package com.aidata.concurrency; public class DemoThread02 { private /*static*/ int count = 0; // 如果是static变量会怎么样? public /*synchronized*/ /*static*/ void add(){ count++; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ">count=" + count); } public static void main(String[] args) { final DemoThread02 thread0 = new DemoThread02(); final DemoThread02 thread1 = new DemoThread02(); Thread t0 = new Thread(new Runnable() { public void run() { thread0.add(); // 1.同一个对象,同一把锁 } }, "thread0"); Thread t1 = new Thread(new Runnable() { public void run() { thread0.add(); } }, "thread1"); t0.start(); t1.start(); } }
结果:
thread0>count=2
thread1>count=2
两个线程调用同一个对象的add方法
实现第一个线程,加1,count变为1,休眠1秒,第二个线程加1,count变为2,第一线程休眠完打印,第二个线程休眠完打印。
Synchronized作用在非静态方法上代表的对象锁,一个对象一个锁
package com.aidata.concurrency; public class DemoThread02 { private /*static*/ int count = 0; // 如果是static变量会怎么样? public synchronized /*static*/ void add(){ count++; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ">count=" + count); } public static void main(String[] args) { final DemoThread02 thread0 = new DemoThread02(); final DemoThread02 thread1 = new DemoThread02(); Thread t0 = new Thread(new Runnable() { public void run() { thread0.add(); // 1.同一个对象,同一把锁 } }, "thread0"); Thread t1 = new Thread(new Runnable() { public void run() { thread0.add(); // thread1.add(); // 1.同一个对象,同一把锁 } }, "thread1"); t0.start(); t1.start(); } }
同一个对象,对象锁,一个线程运行完,锁才给另一个线程,同一把锁互斥
结果
thread0>count=1
thread1>count=2
多个对象之间不会发送锁竞争
package com.aidata.concurrency; public class DemoThread02 { private /*static*/ int count = 0; // 如果是static变量会怎么样? public synchronized /*static*/ void add(){ count++; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ">count=" + count); } public static void main(String[] args) { final DemoThread02 thread0 = new DemoThread02(); final DemoThread02 thread1 = new DemoThread02(); Thread t0 = new Thread(new Runnable() { public void run() { thread0.add(); // 1.同一个对象,同一把锁 } }, "thread0"); Thread t1 = new Thread(new Runnable() { public void run() { thread1.add(); // 1.同一个对象,同一把锁 } }, "thread1"); t0.start(); t1.start(); } }
两个对象,分别加锁,不是同一把锁,并不会产生锁的互斥,没有关系
结果
thread1>count=1
thread0>count=1
Synchronized作用在静态方法上则升级为类锁,所有对象共享一把锁,存在锁竞争
package com.aidata.concurrency; public class DemoThread02 { private static int count = 0; // 如果是static变量会怎么样? public synchronized static void add(){ count++; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ">count=" + count); } public static void main(String[] args) { final DemoThread02 thread0 = new DemoThread02(); final DemoThread02 thread1 = new DemoThread02(); Thread t0 = new Thread(new Runnable() { public void run() { thread0.add(); // 1.同一个对象,同一把锁 } }, "thread0"); Thread t1 = new Thread(new Runnable() { public void run() { thread1.add(); // 1.同一个对象,同一把锁 } }, "thread1"); t0.start(); t1.start(); } }
类锁,所有对象共享一把锁,互斥,静态变量两个线程共享
结果
thread0>count=1
thread1>count=2
对象锁的同步和异步
同步:必须等待方法执行完毕,才能向下执行,共享资源访问的时候,为了保证线程安全,必须同步。
异步:不用等待其他方法执行完毕,即可立即执行,例如Ajax异步。
package com.aidata.concurrency; public class DemoThread03 { // 同步执行 public synchronized void print1(){ System.out.println(Thread.currentThread().getName() + ">hello!"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } // 异步执行 public void print2(){ System.out.println(Thread.currentThread().getName()+">hello!"); } public static void main(String[] args) { final DemoThread03 thread = new DemoThread03(); Thread t0 = new Thread(new Runnable() { public void run() { thread.print1(); } }, "Thread0"); Thread t1 = new Thread(new Runnable() { public void run() { thread.print2(); } }, "Thread1"); t0.start(); t1.start(); } }
对象锁只针对synchronized修饰的方法生效、对象中所有synchronized方法都会同步执行 ,非synchronized方法异步执行
因此上面,没有等待三秒,t1线程直接运行了线程里的打印语句
脏读
由于同步和异步方法的执行个性,如果不从全局上进行并发设计很可能引起数据的不一致,也就是所谓脏读。
多个线程访问同一个资源,在一个线程修改数据的过程中,有另外的线程来读取数据,就好引起脏读:
private String name = "张三"; private String address = "大兴"; public synchronized void setVal(String name, String address){ this.name = name; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } this.address = address; System.out.println("setVal最终结果:username = " + name + " ,address = " + address); } public void getVal(){ System.out.println("getVal方法得到:username = " + name + " ,address = " + address); } public static void main(String[] args) throws InterruptedException { final DemoThread04 dr = new DemoThread04(); Thread t0 = new Thread(new Runnable() { public void run() { dr.setVal("李四", "昌平"); } }); t0.start(); Thread.sleep(1000); dr.getVal(); } }
setVal加锁了,但是getVal没有加锁,t0线程执行,修改name后会休眠两秒,而主线程休眠一秒后就调用了getVal()方法,此时只改了name,address还没改,产生了脏读。
结果
getVal方法得到:username = 李四 ,address = 大兴
setVal最终结果:username = 李四 ,address = 昌平
为了避免脏读,我们一定要保证数据修改操作的原子性,并对读取操作也要进行同步控制。
即不能让其他线程写,也不能让其他线程读,将getVal()也加上synchronized关键字即可。
结果为
setVal最终结果:username = 李四 ,address = 昌平
getVal方法得到:username = 李四 ,address = 昌平
synchronized锁重入
同一个线程得到了一个对象的锁之后,再次请求此对象时可以再次获得该对象的锁。同一个对象内的多个synchronized方法可以锁重入。
package com.aidata.concurrency; public class DemoThread05 { public synchronized void run1(){ System.out.println(Thread.currentThread().getName() + ">run1..."); // 调用同类中的synchronized方法不会引起死锁 run2(); } public synchronized void run2(){ System.out.println(Thread.currentThread().getName() + ">run2..."); } public static void main(String[] args) { final DemoThread05 demoThread05 = new DemoThread05(); Thread thread = new Thread(new Runnable() { public void run() { demoThread05.run1(); } }); thread.start(); } }
结果
Thread-0>run1...
Thread-0>run2...
父子锁可以重入
package com.aidata.concurrency; public class DemoThread06 { public static void main(String[] args) { Thread t0 = new Thread(new Runnable() { public void run() { Child sub = new Child(); sub.runChild(); } }); t0.start(); } } class Parent{ public int i = 10; public synchronized void runParent(){ try { i--; System.out.println("Parent>>>i=" + i); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } class Child extends Parent{ public synchronized void runChild(){ try{ while (i > 0){ i--; System.out.println("Child>>>i=" + i); Thread.sleep(100); // 调用父类的synchronized方法不会引起死锁 this.runParent(); } }catch (InterruptedException e){ e.printStackTrace(); } } }
结果
Child>>>i=9 Parent>>>i=8 Child>>>i=7 Parent>>>i=6 Child>>>i=5 Parent>>>i=4 Child>>>i=3 Parent>>>i=2 Child>>>i=1 Parent>>>i=0
抛出异常释放锁
一个线程在获得锁之后执行操作,发送错误抛出异常,则自动释放锁。
while死循环,我们想在第十次释放锁:
package com.aidata.concurrency; public class DemoThread07 { private int i = 0; public synchronized void run(){ while (true){ i++; System.out.println(Thread.currentThread().getName() + "-run>i=" +i); try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } if (i == 10){ throw new RuntimeException(); } } } public synchronized void get(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-get>i=" +i); } public static void main(String[] args) throws InterruptedException { final DemoThread07 demoThread07 = new DemoThread07(); new Thread(new Runnable() { public void run() { demoThread07.run(); } }, "t0").start(); // 保证t1线程先执行 Thread.sleep(1000); new Thread(new Runnable() { public void run() { demoThread07.get(); } }, "t1").start(); } }
结果
t0-run>i=1 t0-run>i=2 t0-run>i=3 t0-run>i=4 t0-run>i=5 t0-run>i=6 t0-run>i=7 t0-run>i=8 t0-run>i=9 t0-run>i=10 Exception in thread "t0" java.lang.RuntimeException at com.aidata.concurrency.DemoThread07.run(DemoThread07.java:18) at com.aidata.concurrency.DemoThread07$1.run(DemoThread07.java:36) at java.lang.Thread.run(Thread.java:748) t1-get>i=10
总结:
1.可以利用抛出异常,主动释放锁
2.程序异常时防止资源被死锁、无法释放
3.异常释放锁可能导致数据不一致
Synchronized代码块和锁失效问题
synchronized代码块
可以达到更细粒度的控制
- 当前对象锁
- 类锁
- 任意锁
package com.aidata.concurrency; public class DemoThread08 { public void run1(){ synchronized (this){ try { System.out.println(Thread.currentThread().getName() + ">当前对象锁..."); Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } } } public void run2(){ synchronized (DemoThread08.class){ try { System.out.println(Thread.currentThread().getName() + ">类锁..."); Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } } } private Object objectLock = new Object(); public void run3(){ synchronized (objectLock){ try { System.out.println(Thread.currentThread().getName() + ">任意锁..."); Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } } } // 测试方法 public static void test(final int type){ if (type == 1){ System.out.println("当前对象锁测试..."); }else if (type == 2){ System.out.println("类锁测试..."); }else { System.out.println("任意对象锁测试..."); } final DemoThread08 demo1 = new DemoThread08(); final DemoThread08 demo2 = new DemoThread08(); Thread t0 = new Thread(new Runnable() { public void run() { if (type == 1){ demo1.run1(); }else if (type == 2){ demo1.run2(); }else { demo1.run3(); } } }, "t0"); Thread t1 = new Thread(new Runnable() { public void run() { if (type == 1){ demo2.run1(); }else if (type == 2){ demo2.run2(); }else { demo2.run3(); } } }, "t1"); t0.start(); t1.start(); } public static void main(String[] args) { test(1); // test(2); // test(3); } }
同类型锁之间互斥,不同类型的锁之间互不干扰
package com.aidata.concurrency; public class DemoThread08 { public void run1(){ synchronized (this){ try { System.out.println(Thread.currentThread().getName() + ">当前对象锁..."); Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } } } public void run2(){ synchronized (DemoThread08.class){ try { System.out.println(Thread.currentThread().getName() + ">类锁..."); Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } } } private Object objectLock = new Object(); public void run3(){ synchronized (objectLock){ try { System.out.println(Thread.currentThread().getName() + ">任意锁..."); Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } } }public static void main(String[] args) { final DemoThread08 demo1 = new DemoThread08(); final DemoThread08 demo2 = new DemoThread08(); Thread t1 = new Thread(new Runnable() { public void run() { demo1.run2(); } }, "t1"); t1.start(); // 保证t1先运行 try { Thread.sleep(100); }catch (InterruptedException e){ e.printStackTrace(); } Thread t2 = new Thread(new Runnable() { public void run() { demo2.run1(); } }, "t2"); t2.start(); } }
没有等待,瞬间完成,也就是不同类型的锁之间互不干扰
t1>类锁...
t2>当前对象锁...
不要在线程内修改对象锁的引用
引用被改变会导致锁失效
package com.aidata.concurrency; public class DemoThread09 { private String lock = "lock handler"; private void method(){ synchronized (lock){ try { System.out.println("当前线程:" + Thread.currentThread().getName() + "开始"); // 锁的引用被改变,则其他线程可获得锁,导致并发问题 lock = "change lock handler"; Thread.sleep(2000); System.out.println("当前线程:" + Thread.currentThread().getName() + "结束"); }catch (InterruptedException e){ e.printStackTrace(); } } } public static void main(String[] args) { final DemoThread09 changeLock = new DemoThread09(); Thread t1 = new Thread(new Runnable() { public void run() { changeLock.method(); } }, "t1"); Thread t2 = new Thread(new Runnable() { public void run() { changeLock.method(); } }, "t2"); t1.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 由于锁的引用被改变,所有t2线程也进入到method方法内执行 t2.start(); } }
结果线程不安全:
当前线程:t1开始
当前线程:t2开始
当前线程:t1结束
当前线程:t2结束
线程A修改了对象锁的引用,则线程B实际得到了新的对象锁,而不是锁被释放了,不是同一个锁了,因此引发了线程安全问题
在线程中修改了锁对象的属性,而不修改引用则不会引起锁失效,不会产生线程安全问题
并发与死锁
指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在相关等待的进程称为死锁进程。
package com.aidata.concurrency; import org.omg.Messaging.SYNC_WITH_TRANSPORT; public class DemoThread10 { private Object lock1 = new Object(); private Object lock2 = new Object(); public void execute1(){ // 尝试获得lock1的锁 synchronized (lock1){ System.out.println("线程" + Thread.currentThread().getName() + " 获得lock1执行execute1开始"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // 尝试或得lock2锁 synchronized (lock2){ System.out.println("线程" + Thread.currentThread().getName() + " 获得lock2执行execute1开始"); } } } public void execute2(){ synchronized (lock2){ System.out.println("线程" + Thread.currentThread().getName() + " 获得lock2执行execute2开始"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1){ System.out.println("线程" + Thread.currentThread().getName() + " 获得lock1执行execute2开始"); } } } public static void main(String[] args) { final DemoThread10 demo = new DemoThread10(); new Thread(new Runnable() { public void run() { demo.execute1(); } }, "t1").start(); new Thread(new Runnable() { public void run() { demo.execute2(); } }, "t2").start(); } }
死锁,结果
线程t1 获得lock1执行execute1开始
线程t2 获得lock2执行execute2开始
线程t1在等lock2锁,线程2在等lock1锁
线程之间通讯
每个线程都是独立运行的个体,线程通讯能让多个线程之间协同工作
package com.aidata.concurrency; import java.util.ArrayList; import java.util.List; // while方式 public class DemoThread11 { private volatile List<String> list = new ArrayList<String>(); private volatile boolean canGet = false; public void put(){ for (int i=0; i<10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } list.add("A"); System.out.println("线程" + Thread.currentThread().getName() + "添加第" + i + "个元素"); if (i==5){ // 循环到第i次则通知其他线程开始获取数据进行处理 canGet = true; System.out.println("线程" + Thread.currentThread().getName() + " 发出通知"); } } } public void get(){ while (true){ if (canGet){ for (String s: list){ System.out.println("线程" + Thread.currentThread().getName() + "获取元素:" + s); } break; } } } public static void main(String[] args) { final DemoThread11 demo = new DemoThread11(); new Thread(new Runnable() { public void run() { demo.put(); } }, "t1").start(); new Thread(new Runnable() { public void run() { demo.get(); } }, "t2").start(); } }
结果
线程t1添加第0个元素
线程t1添加第1个元素
线程t1添加第2个元素
线程t1添加第3个元素
线程t1添加第4个元素
线程t1添加第5个元素
线程t1 发出通知
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t1添加第6个元素
线程t1添加第7个元素
线程t1添加第8个元素
线程t1添加第9个元素
Object类中的wait/notify方法可以实现线程间通讯
wait/notify必须与synchronized一通使用
wait释放锁,notify不释放锁
锁池和等待池
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
Reference:java中的锁池和等待池
然后再来说notify和notifyAll的区别
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。
有了这些理论基础,后面的notify可能会导致死锁,而notifyAll则不会的例子也就好解释了
package com.aidata.concurrency; import java.util.ArrayList; import java.util.List; // wait/notify方式 public class DemoThread12 { private volatile List<String> list = new ArrayList<String>(); private Object lock = new Object(); public void put(){ synchronized (lock){ for (int i=0; i<10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } list.add("A"); System.out.println("线程" + Thread.currentThread().getName() + "添加第" + i + "个元素"); if (list.size()==5){ // 数据准备好了,发出唤醒通知,但是不释放锁 lock.notify(); System.out.println("线程" + Thread.currentThread().getName() + " 发出通知"); } } } } public void get(){ synchronized (lock){ try { System.out.println("线程" + Thread.currentThread().getName() + "业务处理,发现需要的数据没准备好,则发起等待"); System.out.println("线程" + Thread.currentThread().getName() + " wait"); // wait操作释放锁,否则其他线程无法进入put方法 lock.wait(); System.out.println("线程" + Thread.currentThread().getName() + "被唤醒"); for (String s: list){ System.out.println("线程" + Thread.currentThread().getName() + "获取元素:" + s); } }catch (InterruptedException e){ e.printStackTrace(); } } } public static void main(String[] args) { final DemoThread12 demo = new DemoThread12(); new Thread(new Runnable() { public void run() { demo.get(); } }, "t1").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Runnable() { public void run() { demo.put(); } }, "t2").start(); } }
结果:
线程t1业务处理,发现需要的数据没准备好,则发起等待
线程t1 wait
线程t2添加第0个元素
线程t2添加第1个元素
线程t2添加第2个元素
线程t2添加第3个元素
线程t2添加第4个元素
线程t2 发出通知
线程t2添加第5个元素
线程t2添加第6个元素
线程t2添加第7个元素
线程t2添加第8个元素
线程t2添加第9个元素
线程t1被唤醒
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
线程t1获取元素:A
上面的高亮可知,wait释放锁给t2,notify不释放锁,t2继续添加元素
notify只会通知一个wait中的线程,并把锁给他,不会产生锁竞争问题,但是该线程处理完毕后必须再次notify或notifyAll,完成类似链式操作。
notifyAll会通知所有wait中的线程,会产生锁竞争问题。
package com.aidata.concurrency; public class DemoThread13 { public synchronized void run1(){ System.out.println("进入run1方法..."); // this.notifyAll(); this.notify(); System.out.println("run1执行完毕,通知完毕..."); } public synchronized void run2(){ try { System.out.println("进入run2方法..."); this.wait(); System.out.println("run2执行完毕,通知完毕..."); this.notify(); System.out.println("run2发出通知..."); }catch (InterruptedException e){ e.printStackTrace(); } } public synchronized void run3(){ try { System.out.println("进入run3方法..."); this.wait(); System.out.println("run3执行完毕,通知完毕..."); this.notify(); System.out.println("run3发出通知..."); }catch (InterruptedException e){ e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { final DemoThread13 demo = new DemoThread13(); new Thread(new Runnable() { public void run() { demo.run2(); } }).start(); new Thread(new Runnable() { public void run() { demo.run3(); } }).start(); Thread.sleep(1000); new Thread(new Runnable() { public void run() { demo.run1(); } }).start(); } }
结果
进入run2方法...
进入run3方法...
进入run1方法...
run1执行完毕,通知完毕...
run2执行完毕,通知完毕...
run2发出通知...
run3执行完毕,通知完毕...
run3发出通知...
notify只能通知一个,必须链式的顺序通知,t2通知t3,t3通知t1
打开t1的notifyAll,关闭t2、t3的notify
package com.aidata.concurrency; public class DemoThread13 { public synchronized void run1(){ System.out.println("进入run1方法..."); this.notifyAll(); System.out.println("run1执行完毕,通知完毕..."); } public synchronized void run2(){ try { System.out.println("进入run2方法..."); this.wait(); System.out.println("run2执行完毕,通知完毕..."); }catch (InterruptedException e){ e.printStackTrace(); } } public synchronized void run3(){ try { System.out.println("进入run3方法..."); this.wait(); System.out.println("run3执行完毕,通知完毕..."); }catch (InterruptedException e){ e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { final DemoThread13 demo = new DemoThread13(); new Thread(new Runnable() { public void run() { demo.run2(); } }).start(); new Thread(new Runnable() { public void run() { demo.run3(); } }).start(); Thread.sleep(1000); new Thread(new Runnable() { public void run() { demo.run1(); } }).start(); } }
notifyAll可以通知所有线程,会有锁竞争
进入run2方法...
进入run3方法...
进入run1方法...
run1执行完毕,通知完毕...
run3执行完毕,通知完毕...
run2执行完毕,通知完毕...
Object中的wait
/** * Causes the current thread to wait until either another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object, or a * specified amount of time has elapsed. * <p> * The current thread must own this object's monitor. * <p> * This method causes the current thread (call it <var>T</var>) to * place itself in the wait set for this object and then to relinquish * any and all synchronization claims on this object. Thread <var>T</var> * becomes disabled for thread scheduling purposes and lies dormant * until one of four things happens: * <ul> * <li>Some other thread invokes the {@code notify} method for this * object and thread <var>T</var> happens to be arbitrarily chosen as * the thread to be awakened. * <li>Some other thread invokes the {@code notifyAll} method for this * object. * <li>Some other thread {@linkplain Thread#interrupt() interrupts} * thread <var>T</var>. * <li>The specified amount of real time has elapsed, more or less. If * {@code timeout} is zero, however, then real time is not taken into * consideration and the thread simply waits until notified. * </ul> * The thread <var>T</var> is then removed from the wait set for this * object and re-enabled for thread scheduling. It then competes in the * usual manner with other threads for the right to synchronize on the * object; once it has gained control of the object, all its * synchronization claims on the object are restored to the status quo * ante - that is, to the situation as of the time that the {@code wait} * method was invoked. Thread <var>T</var> then returns from the * invocation of the {@code wait} method. Thus, on return from the * {@code wait} method, the synchronization state of the object and of * thread {@code T} is exactly as it was when the {@code wait} method * was invoked. * <p> * A thread can also wake up without being notified, interrupted, or * timing out, a so-called <i>spurious wakeup</i>. While this will rarely * occur in practice, applications must guard against it by testing for * the condition that should have caused the thread to be awakened, and * continuing to wait if the condition is not satisfied. In other words, * waits should always occur in loops, like this one: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(timeout); * ... // Perform action appropriate to condition * } * </pre> * (For more information on this topic, see Section 3.2.3 in Doug Lea's * "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, * 2000), or Item 50 in Joshua Bloch's "Effective Java Programming * Language Guide" (Addison-Wesley, 2001). * * <p>If the current thread is {@linkplain java.lang.Thread#interrupt() * interrupted} by any thread before or while it is waiting, then an * {@code InterruptedException} is thrown. This exception is not * thrown until the lock status of this object has been restored as * described above. * * <p> * Note that the {@code wait} method, as it places the current thread * into the wait set for this object, unlocks only this object; any * other objects on which the current thread may be synchronized remain * locked while the thread waits. * <p> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @param timeout the maximum time to wait in milliseconds. * @throws IllegalArgumentException if the value of timeout is * negative. * @throws IllegalMonitorStateException if the current thread is not * the owner of the object's monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The <i>interrupted * status</i> of the current thread is cleared when * this exception is thrown. * @see java.lang.Object#notify() * @see java.lang.Object#notifyAll() */ public final native void wait(long timeout) throws InterruptedException; /** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object, or * some other thread interrupts the current thread, or a certain * amount of real time has elapsed. * <p> * This method is similar to the {@code wait} method of one * argument, but it allows finer control over the amount of time to * wait for a notification before giving up. The amount of real time, * measured in nanoseconds, is given by: * <blockquote> * <pre> * 1000000*timeout+nanos</pre></blockquote> * <p> * In all other respects, this method does the same thing as the * method {@link #wait(long)} of one argument. In particular, * {@code wait(0, 0)} means the same thing as {@code wait(0)}. * <p> * The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until either of the * following two conditions has occurred: * <ul> * <li>Another thread notifies threads waiting on this object's monitor * to wake up either through a call to the {@code notify} method * or the {@code notifyAll} method. * <li>The timeout period, specified by {@code timeout} * milliseconds plus {@code nanos} nanoseconds arguments, has * elapsed. * </ul> * <p> * The thread then waits until it can re-obtain ownership of the * monitor and resumes execution. * <p> * As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(timeout, nanos); * ... // Perform action appropriate to condition * } * </pre> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @param timeout the maximum time to wait in milliseconds. * @param nanos additional time, in nanoseconds range * 0-999999. * @throws IllegalArgumentException if the value of timeout is * negative or the value of nanos is * not in the range 0-999999. * @throws IllegalMonitorStateException if the current thread is not * the owner of this object's monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The <i>interrupted * status</i> of the current thread is cleared when * this exception is thrown. */ public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } /** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object. * In other words, this method behaves exactly as if it simply * performs the call {@code wait(0)}. * <p> * The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until another thread * notifies threads waiting on this object's monitor to wake up * either through a call to the {@code notify} method or the * {@code notifyAll} method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution. * <p> * As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(); * ... // Perform action appropriate to condition * } * </pre> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of the object's monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The <i>interrupted * status</i> of the current thread is cleared when * this exception is thrown. * @see java.lang.Object#notify() * @see java.lang.Object#notifyAll() */ public final void wait() throws InterruptedException { wait(0); }
阻塞式线程安全队列
package com.aidata.concurrency; import java.util.ArrayList; import java.util.List; class MQueue { private List<String> list = new ArrayList<String>(); private int maxSize; private Object lock = new Object(); public MQueue(int maxSize){ this.maxSize = maxSize; System.out.println("线程" + Thread.currentThread().getName() + "已初始化长度为" + this.maxSize + "队列"); } public void put(String element){ synchronized (lock){ if (this.list.size() == this.maxSize){ try { System.out.println("线程" + Thread.currentThread().getName() + "当前队列已满put等待..."); lock.wait(); }catch (InterruptedException e){ e.printStackTrace(); } } this.list.add(element); System.out.println("线程"+Thread.currentThread().getName()+"向队列中加入元素:"+element); lock.notify(); // 通知可以取数据 } } public String take(){ synchronized (lock){ if (this.list.size()==0){ try { System.out.println("线程"+Thread.currentThread().getName()+"队列为空take等待..."); // 空,需要wait lock.wait(); }catch (InterruptedException e){ e.printStackTrace(); } } String result = list.get(0); list.remove(0); System.out.println("线程"+Thread.currentThread().getName()+"获取数据:"+result); lock.notify(); // 通知可以加入线程 return result; } } } public class ThreadDemo13 { public static void main(String[] args) { final MQueue q = new MQueue(5); new Thread(new Runnable() { public void run() { q.put("1"); q.put("2"); q.put("3"); q.put("4"); q.put("5"); q.put("6"); } }, "t1").start(); new Thread(new Runnable() { public void run() { q.put("11"); q.put("21"); q.put("31"); q.put("41"); q.put("51"); q.put("61"); } }, "t2").start(); new Thread(new Runnable() { public void run() { q.take(); q.take(); q.take(); q.take(); q.take(); } }, "t3").start(); new Thread(new Runnable() { public void run() { q.take(); q.take(); q.take(); q.take(); q.take(); } }, "t4").start(); } }
结果:
线程main已初始化长度为5队列 线程t1向队列中加入元素:1 线程t1向队列中加入元素:2 线程t1向队列中加入元素:3 线程t1向队列中加入元素:4 线程t1向队列中加入元素:5 线程t1当前队列已满put等待... 线程t2当前队列已满put等待... 线程t3获取数据:1 线程t3获取数据:2 线程t3获取数据:3 线程t3获取数据:4 线程t3获取数据:5 线程t1向队列中加入元素:6 线程t2向队列中加入元素:11 线程t2向队列中加入元素:21 线程t2向队列中加入元素:31 线程t2向队列中加入元素:41 线程t2当前队列已满put等待... 线程t4获取数据:6 线程t4获取数据:11 线程t4获取数据:21 线程t4获取数据:31 线程t4获取数据:41 线程t2向队列中加入元素:51 线程t2向队列中加入元素:61
守护线程和用户线程
线程分类:daemon线程和user线程
main函数所在线程就是一个用户线程
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
- 最后一个user线程结束时,JVM会正常退出,不管是否有守护线程正在运行。反过来说,只要有一个用户线程还没结束,JVM进程就不会结束。
- 父线程结束后,子线程还可以继续存活,子线程的生命周期不受父线程影响。
package com.aidata.concurrency; public class DaemonAndUserThreadDemo { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { public void run() { while (true){} } }); thread.start(); // 输出线程是否为守护线程 System.out.println(thread.getName() + " is daemon" + thread.isDaemon()); System.out.println(Thread.currentThread().getName() + " is daemon? " + Thread.currentThread().isDaemon()); System.out.println("main is over"); } }
即使父线程结束,不会运行结束,子线程继续存活:
设为守护线程
package com.aidata.concurrency; public class DaemonAndUserThreadDemo { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { public void run() { while (true){} } }); thread.setDaemon(true); thread.start(); // 输出线程是否为守护线程 System.out.println(thread.getName() + " is daemon" + thread.isDaemon()); System.out.println(Thread.currentThread().getName() + " is daemon? " + Thread.currentThread().isDaemon()); System.out.println("main is over"); } }
结果:
Thread-0 is daemontrue main is daemon? false main is over
线程上下文切换
当前线程使用完时间片后会进入就绪状态,让出CPU执行权给其他线程,此时就是从当前线程的上下文切换到了其他线程。
当发生上下文切换的时候需要保存执行现场,等待下次执行时进行恢复。所以,频繁大量上下文切换会造成一定资源开销。
二、进阶篇
- volatile
- Actomic
- ThreadLocal
- 同步类容器
- 并发类容器
- 并发无阻塞式队列
- 并发阻塞式队列
volatile关键字
作用:强制线程到共享内存中读取数据,而不从线程工作内存中读取数据,从而使变量在多个线程间可见。
public class Test1 { private List<String> list = new ArrayList<String>(); private boolean canGet = false; public void put(){ for (int i=0;i<10;i++){ try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } list.add("A"); System.out.println("线程"+Thread.currentThread().getName()+"添加第"+i+"个元素"); if (i==5){ canGet = true; System.out.println("线程"+Thread.currentThread().getName()+"发出通知"); } } } public void get(){ while (true){ if (canGet){ for (String s: list){ System.out.println("线程"+Thread.currentThread().getName()+"获取元素:"+s); } break; } } } public static void main(String[] args) { final Test1 demo = new Test1(); new Thread(new Runnable() { public void run() { demo.put(); } }, "t1").start(); new Thread(new Runnable() { public void run() { demo.get(); } }, "t2").start(); } }
结果
线程t1添加第0个元素
线程t1添加第1个元素
线程t1添加第2个元素
线程t1添加第3个元素
线程t1添加第4个元素
线程t1添加第5个元素
线程t1发出通知
线程t1添加第6个元素
线程t1添加第7个元素
线程t1添加第8个元素
线程t1添加第9个元素
程序会一直在运行
使用volatile关键字
public class Test1 { private List<String> list = new ArrayList<String>(); private volatile boolean canGet = false; public void put(){ for (int i=0;i<10;i++){ try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } list.add("A"); System.out.println("线程"+Thread.currentThread().getName()+"添加第"+i+"个元素"); if (i==5){ canGet = true; System.out.println("线程"+Thread.currentThread().getName()+"发出通知"); } } } public void get(){ while (true){ if (canGet){ for (String s: list){ System.out.println("线程"+Thread.currentThread().getName()+"获取元素:"+s); } break; } } } public static void main(String[] args) { final Test1 demo = new Test1(); new Thread(new Runnable() { public void run() { demo.put(); } }, "t1").start(); new Thread(new Runnable() { public void run() { demo.get(); } }, "t2").start(); } }
结果
线程t1添加第0个元素
线程t1添加第1个元素
线程t1添加第2个元素
线程t1添加第3个元素
线程t1添加第4个元素
线程t1添加第5个元素
线程t1发出通知
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t2获取元素:A
线程t1添加第6个元素
线程t1添加第7个元素
线程t1添加第8个元素
线程t1添加第9个元素
不使用volatile关键字,各个线程都使用的其工作内存 信息,因此get线程中的canGet会一直是false。
使用了volatile关键字,各个线程都从共享内存中获取信息,但put修改了共享内存中的canGet后,put线程开源马上探测到。
无法保证原子性,volatile属于轻量级同步,性能比synchronized强很多(不加锁),但是只保证线程见的可见性,并不能替代synchronized的同步功能,netty框架中大量使用了volatile。
public class Test1 implements Runnable{ public static volatile int sum = 0; // 简单的将sum加10000次 public static void add(){ System.out.println(Thread.currentThread().getName()+"初始sum="+sum); for(int i=0;i<10000;i++){ sum++; } System.out.println(Thread.currentThread().getName()+"计算后sum="+sum); } public void run() { add(); } public static void main(String[] args) { //如果volatile具有原子性,那么10个线程并发调用,最终结果应该为100000 ExecutorService es = Executors.newFixedThreadPool(10); for(int i=0;i<10;i++){ es.submit(new Test1()); } es.shutdown(); while(true){ if(es.isTerminated()){ System.out.println("sum最终="+sum); if(sum==100000){ System.out.println(sum+"=ok"); }else{ System.out.println(sum+"=no"); } break; } } } }
结果
pool-1-thread-1初始sum=0 pool-1-thread-1计算后sum=10000 pool-1-thread-2初始sum=10000 pool-1-thread-3初始sum=10239 pool-1-thread-2计算后sum=21682 pool-1-thread-3计算后sum=24318 pool-1-thread-4初始sum=24318 pool-1-thread-4计算后sum=34318 pool-1-thread-5初始sum=34318 pool-1-thread-5计算后sum=44318 pool-1-thread-6初始sum=44318 pool-1-thread-6计算后sum=54318 pool-1-thread-7初始sum=54318 pool-1-thread-7计算后sum=64318 pool-1-thread-8初始sum=64318 pool-1-thread-8计算后sum=74318 pool-1-thread-9初始sum=74318 pool-1-thread-9计算后sum=84318 pool-1-thread-10初始sum=84318 pool-1-thread-10计算后sum=94318 sum最终=94318 94318=no
多个线程同时操作了sum,如两个线程当sum是3的时候同时加1,两次相加结果都是4
Static保证唯一性, 不保证一致性,多个实例共享一个静态变量。
Volatile保证一致性,不保证唯一性,多个实例有多个volatile变量。
Atomic类的原子性
使用AtomicIntege等原子类可以保证共享变量的原子性
public class Test1 implements Runnable{ public static AtomicInteger sum = new AtomicInteger(0); // 简单的将sum加10000次 public static void add(){ System.out.println(Thread.currentThread().getName()+"初始sum="+sum); for(int i=0;i<10000;i++){ sum.addAndGet(1); } System.out.println(Thread.currentThread().getName()+"计算后sum="+sum); } public void run() { add(); } public static void main(String[] args) { //如果volatile具有原子性,那么10个线程并发调用,最终结果应该为100000 ExecutorService es = Executors.newFixedThreadPool(10); for(int i=0;i<10;i++){ es.submit(new Test1()); } es.shutdown(); while(true){ if(es.isTerminated()){ System.out.println("sum最终="+sum); if(sum.get()==100000){ System.out.println(sum+"=ok"); }else{ System.out.println(sum+"=no"); } break; } } } }
结果
pool-1-thread-1初始sum=0 pool-1-thread-1计算后sum=10000 pool-1-thread-2初始sum=10000 pool-1-thread-2计算后sum=20000 pool-1-thread-3初始sum=20000 pool-1-thread-3计算后sum=30000 pool-1-thread-4初始sum=30000 pool-1-thread-4计算后sum=40000 pool-1-thread-5初始sum=40000 pool-1-thread-5计算后sum=50000 pool-1-thread-6初始sum=50000 pool-1-thread-6计算后sum=60000 pool-1-thread-7初始sum=60000 pool-1-thread-7计算后sum=70000 pool-1-thread-8初始sum=70000 pool-1-thread-8计算后sum=80000 pool-1-thread-9初始sum=80000 pool-1-thread-9计算后sum=90000 pool-1-thread-10初始sum=90000 pool-1-thread-10计算后sum=100000 sum最终=100000 100000=ok
只能保证成员变量,Atomic类不能保证成员方法的原子性
public class Test1 implements Runnable{ //原子类 private static AtomicInteger sum = new AtomicInteger(0); //如果add方法是原子性的,那么每次的结果都是10的整数倍 /*synchronized*/ public static void add(){ sum.addAndGet(1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } sum.addAndGet(9); System.out.println(sum); } public void run() { add(); } public static void main(String[] args) { //10个线程调用,每个线程得到10的倍数, 最终结果应该为100,才是正确的 ExecutorService es = Executors.newFixedThreadPool(10); for(int i=0;i<10;i++){ es.submit(new Test1()); } es.shutdown(); } }
结果
19 28 46 37 55 64 73 82 91 100
最终结果虽然是100,但是方法执行不是10的倍数,不是原子性的
public class Test1 implements Runnable{ //原子类 private static AtomicInteger sum = new AtomicInteger(0); //如果add方法是原子性的,那么每次的结果都是10的整数倍 /*synchronized*/ public synchronized static void add(){ sum.addAndGet(1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } sum.addAndGet(9); System.out.println(sum); } public void run() { add(); } public static void main(String[] args) { //10个线程调用,每个线程得到10的倍数, 最终结果应该为100,才是正确的 ExecutorService es = Executors.newFixedThreadPool(10); for(int i=0;i<10;i++){ es.submit(new Test1()); } es.shutdown(); } }
结果
10 20 30 40 50 60 70 80 90 100
Actomic采用了CAS这种非锁机制
CAS原理
JDK提供的非阻塞原子操作,通过硬件保证了比较、更新操作的原子性
JDK的Unsafe类提供了一系列的compareAndSwap*方法来支持CAS操作
本质是乐观锁
上述方法貌似解决了问题,但实际上还存在问题。
ABA问题
引入时间戳(版本号)
1.如果程序按照1-5的顺序执行,依然是成功的,然后线程1修改x的值时,其实已经从x=A->B->A。
2.由于变量的值产生了环形转换,从A变B又变回了A。如果不存在环形转换也就不存在ABA问题。
怎么办?
1.给变量分配时间戳、版本来解决ABA问题。
2.JDK中使用java.util.concurrent.atomic.AtomicStampedReference类给每个变量的状态都分配一个时间戳,避免ABA问题产生。
例子
public class CaseDemo0 { private static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<Integer>(100, 0); public static void main(String[] args) throws InterruptedException { Thread t0 = new Thread(new Runnable() { public void run() { try { TimeUnit.SECONDS.sleep(1); boolean success = atomic.compareAndSet(100, 101, atomic.getStamp(), atomic.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "set 100>101: " + success); // 101改回100 success = atomic.compareAndSet(101, 100, atomic.getStamp(), atomic.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "set 101>100: " + success); }catch (InterruptedException e){ e.printStackTrace(); } } }); t0.start(); // t0会先sleep,先执行的是t1 Thread t1 = new Thread(new Runnable() { public void run() { try { int stamp = atomic.getStamp(); System.out.println(Thread.currentThread().getName() + "修改之前:" + stamp); // 等待两秒后,t0已经修改了时间戳 TimeUnit.SECONDS.sleep(2); int stamp1 = atomic.getStamp(); System.out.println(Thread.currentThread().getName() + "等待两秒之后,版本被t0线程修改为:" + stamp1); // 以下两次修改都不会成功,因为版本不符,虽然期待值是相同的,因此解决了ABA问题 boolean success = atomic.compareAndSet(100, 101, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + "set 100>101 使用错误的时间戳:" + success); success = atomic.compareAndSet(101, 100, stamp, stamp+1); System.out.println(Thread.currentThread().getName() + "set 101>100 使用错误的时间戳:" + success); // 以下修改是成功的,因为使用了正确的时间戳 success = atomic.compareAndSet(100, 101, stamp1, stamp1+1); System.out.println(Thread.currentThread().getName() + "set 100>101 使用正确的时间戳:" + success); }catch (InterruptedException e){ e.printStackTrace(); } } }); t1.start(); t0.join(); t1.join(); System.out.println("main is over."); } }
结果
Thread-1修改之前:0 Thread-0set 100>101: true Thread-0set 101>100: true Thread-1等待两秒之后,版本被t0线程修改为:2 Thread-1set 100>101 使用错误的时间戳:false Thread-1set 101>100 使用错误的时间戳:false Thread-1set 100>101 使用正确的时间戳:true main is over.
源码

/** * An {@code AtomicStampedReference} maintains an object reference * along with an integer "stamp", that can be updated atomically. * * <p>Implementation note: This implementation maintains stamped * references by creating internal objects representing "boxed" * [reference, integer] pairs. * * @since 1.5 * @author Doug Lea * @param <V> The type of object referred to by this reference */ public class AtomicStampedReference<V> { private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; /** * Creates a new {@code AtomicStampedReference} with the given * initial values. * * @param initialRef the initial reference * @param initialStamp the initial stamp */ public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); } /** * Returns the current value of the reference. * * @return the current value of the reference */ public V getReference() { return pair.reference; } /** * Returns the current value of the stamp. * * @return the current value of the stamp */ public int getStamp() { return pair.stamp; } /** * Returns the current values of both the reference and the stamp. * Typical usage is {@code int[1] holder; ref = v.get(holder); }. * * @param stampHolder an array of size of at least one. On return, * {@code stampholder[0]} will hold the value of the stamp. * @return the current value of the reference */ public V get(int[] stampHolder) { Pair<V> pair = this.pair; stampHolder[0] = pair.stamp; return pair.reference; } /** * Atomically sets the value of both the reference and stamp * to the given update values if the * current reference is {@code ==} to the expected reference * and the current stamp is equal to the expected stamp. * * <p><a href="package-summary.html#weakCompareAndSet">May fail * spuriously and does not provide ordering guarantees</a>, so is * only rarely an appropriate alternative to {@code compareAndSet}. * * @param expectedReference the expected value of the reference * @param newReference the new value for the reference * @param expectedStamp the expected value of the stamp * @param newStamp the new value for the stamp * @return {@code true} if successful */ public boolean weakCompareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { return compareAndSet(expectedReference, newReference, expectedStamp, newStamp); } /** * Atomically sets the value of both the reference and stamp * to the given update values if the * current reference is {@code ==} to the expected reference * and the current stamp is equal to the expected stamp. * * @param expectedReference the expected value of the reference * @param newReference the new value for the reference * @param expectedStamp the expected value of the stamp * @param newStamp the new value for the stamp * @return {@code true} if successful */ public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } /** * Unconditionally sets the value of both the reference and stamp. * * @param newReference the new value for the reference * @param newStamp the new value for the stamp */ public void set(V newReference, int newStamp) { Pair<V> current = pair; if (newReference != current.reference || newStamp != current.stamp) this.pair = Pair.of(newReference, newStamp); } /** * Atomically sets the value of the stamp to the given update value * if the current reference is {@code ==} to the expected * reference. Any given invocation of this operation may fail * (return {@code false}) spuriously, but repeated invocation * when the current value holds the expected value and no other * thread is also attempting to set the value will eventually * succeed. * * @param expectedReference the expected value of the reference * @param newStamp the new value for the stamp * @return {@code true} if successful */ public boolean attemptStamp(V expectedReference, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && (newStamp == current.stamp || casPair(current, Pair.of(expectedReference, newStamp))); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); } static long objectFieldOffset(sun.misc.Unsafe UNSAFE, String field, Class<?> klazz) { try { return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field)); } catch (NoSuchFieldException e) { // Convert Exception to corresponding Error NoSuchFieldError error = new NoSuchFieldError(field); error.initCause(e); throw error; } } }
ThreadLocal类
使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
public class DemoThread21 { public static void main(String[] args) throws InterruptedException { final ThreadLocal<Integer> th = new ThreadLocal<Integer>(); new Thread(new Runnable() { @Override public void run() { try { th.set(100); System.out.println("t1 set th="+th.get()); Thread.sleep(2000); System.out.println("t1 get th="+th.get()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); Thread.sleep(1000); new Thread(new Runnable() { @Override public void run() { Integer ele = th.get(); System.out.println("t2 get th="+ele); th.set(200); System.out.println("t2 get th="+th.get()); } }).start(); } }
来一个线程,创建一个副本
结果
t1 set th=100 t2 get th=null t2 get th=200 t1 get th=100
开始t1线程先执行,把th改为100,睡眠
t2线程执行,获取th,打印,打印结果为null,说明和t1的th不一样
t2修改为200
t1输出还是100
两个线程各自有一个副本
源码

/* * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang; import java.lang.ref.*; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; /** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). * * <p>For example, the class below generates unique identifiers local to each * thread. * A thread's id is assigned the first time it invokes {@code ThreadId.get()} * and remains unchanged on subsequent calls. * <pre> * import java.util.concurrent.atomic.AtomicInteger; * * public class ThreadId { * // Atomic integer containing the next thread ID to be assigned * private static final AtomicInteger nextId = new AtomicInteger(0); * * // Thread local variable containing each thread's ID * private static final ThreadLocal<Integer> threadId = * new ThreadLocal<Integer>() { * @Override protected Integer initialValue() { * return nextId.getAndIncrement(); * } * }; * * // Returns the current thread's unique ID, assigning it if necessary * public static int get() { * return threadId.get(); * } * } * </pre> * <p>Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist). * * @author Josh Bloch and Doug Lea * @since 1.2 */ public class ThreadLocal<T> { /** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys, * searched via threadLocalHashCode. This is a custom hash code * (useful only within ThreadLocalMaps) that eliminates collisions * in the common case where consecutively constructed ThreadLocals * are used by the same threads, while remaining well-behaved in * less common cases. */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } /** * Returns the current thread's "initial value" for this * thread-local variable. This method will be invoked the first * time a thread accesses the variable with the {@link #get} * method, unless the thread previously invoked the {@link #set} * method, in which case the {@code initialValue} method will not * be invoked for the thread. Normally, this method is invoked at * most once per thread, but it may be invoked again in case of * subsequent invocations of {@link #remove} followed by {@link #get}. * * <p>This implementation simply returns {@code null}; if the * programmer desires thread-local variables to have an initial * value other than {@code null}, {@code ThreadLocal} must be * subclassed, and this method overridden. Typically, an * anonymous inner class will be used. * * @return the initial value for this thread-local */ protected T initialValue() { return null; } /** * Creates a thread local variable. The initial value of the variable is * determined by invoking the {@code get} method on the {@code Supplier}. * * @param <S> the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @return a new thread local variable * @throws NullPointerException if the specified supplier is null * @since 1.8 */ public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } /** * Creates a thread local variable. * @see #withInitial(java.util.function.Supplier) */ public ThreadLocal() { } /** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ 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(); } /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * {@code initialValue} method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } /** * Factory method to create map of inherited thread locals. * Designed to be called only from Thread constructor. * * @param parentMap the map associated with parent thread * @return a map containing the parent's inheritable bindings */ static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } /** * Method childValue is visibly defined in subclass * InheritableThreadLocal, but is internally defined here for the * sake of providing createInheritedMap factory method without * needing to subclass the map class in InheritableThreadLocal. * This technique is preferable to the alternative of embedding * instanceof tests in methods. */ T childValue(T parentValue) { throw new UnsupportedOperationException(); } /** * An extension of ThreadLocal that obtains its initial value from * the specified {@code Supplier}. */ static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { private final Supplier<? extends T> supplier; SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override protected T initialValue() { return supplier.get(); } } /** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */ 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; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; /** * The number of entries in the table. */ private int size = 0; /** * The next size value at which to resize. */ private int threshold; // Default to 0 /** * Set the resize threshold to maintain at worst a 2/3 load factor. */ private void setThreshold(int len) { threshold = len * 2 / 3; } /** * Increment i modulo len. */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } /** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } /** * Construct a new map including all Inheritable ThreadLocals * from given parent map. Called only by createInheritedMap. * * @param parentMap the map associated with parent thread. */ private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } /** * Get the entry associated with key. This method * itself handles only the fast path: a direct hit of existing * key. It otherwise relays to getEntryAfterMiss. This is * designed to maximize performance for direct hits, in part * by making this method readily inlinable. * * @param key the thread local object * @return the entry associated with key, or null if no such */ private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } /** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key's hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */ private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; 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; } /** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } /** * Remove the entry for key. */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } /** * Replace a stale entry encountered during a set operation * with an entry for the specified key. The value passed in * the value parameter is stored in the entry, whether or not * an entry already exists for the specified key. * * As a side effect, this method expunges all stale entries in the * "run" containing the stale entry. (A run is a sequence of entries * between two null slots.) * * @param key the key * @param value the value to be associated with key * @param staleSlot index of the first stale entry encountered while * searching for key. */ private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Find either the key or trailing null slot of run, whichever // occurs first for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); // If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } /** * Expunge a stale entry by rehashing any possibly colliding entries * lying between staleSlot and the next null slot. This also expunges * any other stale entries encountered before the trailing null. See * Knuth, Section 6.4 * * @param staleSlot index of slot known to have null key * @return the index of the next null slot after staleSlot * (all between staleSlot and this slot will have been checked * for expunging). */ private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot 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; } /** * Heuristically scan some cells looking for stale entries. * This is invoked when either a new element is added, or * another stale one has been expunged. It performs a * logarithmic number of scans, as a balance between no * scanning (fast but retains garbage) and a number of scans * proportional to number of elements, that would find all * garbage but would cause some insertions to take O(n) time. * * @param i a position known NOT to hold a stale entry. The * scan starts at the element after i. * * @param n scan control: {@code log2(n)} cells are scanned, * unless a stale entry is found, in which case * {@code log2(table.length)-1} additional cells are scanned. * When called from insertions, this parameter is the number * of elements, but when from replaceStaleEntry, it is the * table length. (Note: all this could be changed to be either * more or less aggressive by weighting n instead of just * using straight log n. But this version is simple, fast, and * seems to work well.) * * @return true if any stale entries have been removed. */ private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; } /** * Re-pack and/or re-size the table. First scan the entire * table removing stale entries. If this doesn't sufficiently * shrink the size of the table, double the table size. */ private void rehash() { expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); } /** * Double the capacity of the table. */ private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; } /** * Expunge all stale entries in the table. */ private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } } } }
三、精通篇
四、Disruption高并发框架
五、RateLimiter高并发访问限流