一、基础篇

进程是系统进行资源分配和调动的基本单位,一个进程中至少有一个线程,进程中多个线程共享进程的资源。
线程是进程中的一个实体,线程是不会独立存在的,没有进程就没有线程。
对于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