一、基础篇
进程是系统进行资源分配和调动的基本单位,一个进程中至少有一个线程,进程中多个线程共享进程的资源。
线程是进程中的一个实体,线程是不会独立存在的,没有进程就没有线程。
对于CPU资源比较特殊,线程才是CPU分配的基本单位。
main函数启动->JVM进程->main函数线程称为主线程
内存与线程
内存与线程的关心,主要指JVM内存模型与线程之间的关系,它也是线程安全问题的主要诱因。
使用JDK工具观察线程
jcmd
jstack
jvisualVM
jconsole
线程创建的三种方法
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
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就无法继承其他类了
实现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执行权给其他线程,此时就是从当前线程的上下文切换到了其他线程。
当发生上下文切换的时候需要保存执行现场,等待下次执行时进行恢复。所以,频繁大量上下文切换会造成一定资源开销。
二、进阶篇
三、精通篇
四、Disruption高并发框架
五、RateLimiter高并发访问限流
来源:https://www.cnblogs.com/aidata/p/12305840.html