java内存模型
Java内存模型定义了一种多线程访问Java内存的规范。
- Java内存模型将内存分为了主内存和工作内存。类的状态也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去
- 定义了几个原子操作,用于操作主内存和工作内存中的变量
- 定义了volatile变量的使用规则
- happens-before即先行发生原则,定义了操作A必然先行发生于操作B的一些规则,比如在同一个线程内控制流前面的代码一定先行发生于控制流后面的代码、一个释放锁unlock的动作一定先行发生于后面对于同一个锁进行锁定lock的动作等等,只要符合这些规则,则不需要额外做同步措施,如果某段代码不符合所有的happens-before规则,则这段代码一定是线程非安全的
Java内存模型JMM规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。
volatile
volatile在主存中修改,只能用于修改成员属性
- 保证可见性:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存,使其他线程立即可见
- 保证有序性:当变量被修饰为 volatile 时,JMM 会禁止读写该变量前后语句的大部分重排序优化,以保证变量赋值操作的顺序与程序中的执行顺序一致
- 部分原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性
不建议用stop方法
可以通过其它方式实现线程的停止
可以通过设置标志值,到标志值直接退出或者break
public class Test2 {
public static void main(String[] args) throws Exception {
Thread t1=new Thread(()->{
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread()+":"+i);
}
});
t1.start();
TimeUnit.MILLISECONDS.sleep(1);
t1.stop();//不让用stop
MyThread mt=new MyThread();
mt.start();
Thread.sleep(1);
mt.setFlag(false);
}
}
//可以通过其它方式实现线程的停止
//可以通过设置标志值,到标志值直接退出或者break
class MyThread extends Thread {
private boolean flag = true;
@Override
public void run() {
for (int i = 0; i < 100 && flag; i++)
System.out.println(Thread.currentThread() + "--" + i);
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
suspend()方法用于暂停线程的执行,该方法容易导致死锁,因为该线程在暂停的时候仍然占有该资源,这会导致其他需要该资源的线程与该线程产生环路等待,从而造成死锁
resume()方法用于恢复线程的执行。suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态对线程执行状态进行检查的工具:
如果项目在生产环境中运行,不可能频繁调用Thread#getState()方法去监测线程的状态变化。JDK本身提供了一些监控线程状态的工具,还有一些开源的轻量级工具如阿里的Arthasjstack
jstack是JDK自带的命令行工具,功能是用于获取指定PID的Java进程的线程栈信息。
例如本地运行的一个IDEA实例的PID是11376,那么只需要输入:jstack 11376
sleep和wait方法
Thread.sleep(0);//含义在于让出CPU,使当前线程从运行态转向就绪态
当线程进入休眠态,它会定时结束休眠。如果需要提前唤醒,则需要通过interrupt方法实现。所谓的interrupt方法实际上会产生一个异常InterruptedException不释放已获取的锁资源,如果sleep方法在同步上下文中调用,那么其他线程是无法进入到当前同步块或者同步方法中的。
wait();//可以无限等待 wait()等价于wait(0),没有超时时长,一直等待到被唤醒
wait(long)一直等待到超时为止或者提前唤醒
- wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用此对象的notify()方法或notifyAll()方法,当前线程被唤醒,进入就绪状态
import java.util.Date;
public class Test1_1 {
public static void main(String[] args) {
Thread t1=new Thread() {
@Override
public void run() {
System.out.println("begin"+new Date().getTime());
try {
Thread.sleep(0);//含义在于让出CPU,使当前线程从运行态转向就绪态
// Thread.sleep(-100); IllegalArgumentException
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end"+new Date().getTime());
}
};
t1.start();
}
}
import java.util.Date;
public class Test1_2 {
public static void main(String[] args) {
Test1_2 t2=new Test1_2();
Thread t1=new Thread() {
@Override
public void run() {
t2.pp();
}
};
t1.start();
}
public synchronized void pp() {
System.out.println("begin"+new Date().getTime());
try {
wait();//可以无限等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end"+new Date().getTime());
}
}
sleep方法使得当前线程休眠
Thread类中定义的是
public static native void sleep(long millis) throws InterruptedException
让当前线程休眠指定时间。休眠时间的准确性依赖于系统时钟和CPU调度机制。如果需要可以通过调用interrupt()方法来唤醒休眠线程
其他写法
TimeUnit.SECONDS.sleep(1);具体实现细节Thread.sleep(1000);
当线程进入休眠态,它会定时结束休眠。如果需要提前唤醒,则需要通过interrupt方法实现。所谓的interrupt方法实际上会产生一个异常InterruptedException不释放已获取的锁资源,如果sleep方法在同步上下文中调用,那么其他线程是无法进入到当前同步块或者同步方法中的。
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test1 {
public static void main(String[] args) throws Exception {
// Thread t1=new Thread() {
// public void run() {
// while(true) {
// Date mdate = new Date(2022 - 1900, 8 - 1, 19);
// Date now = new Date();
// long kk = (mdate.getTime() - now.getTime()) / 1000;
// if(kk<1)
// break;
// System.out.println(kk + "秒");
// //System.out.println(kk / 60 + "分钟");
// //System.out.println(kk / 60 / 60 + "小时");
// //System.out.println(kk / 60 / 60 / 24 + "天");
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// };
// t1.start();
// Thread.sleep(200);// 唤醒处于休眠状态的线程,在子线程中会导致InterruptedException
// t1.interrupt();
// System.out.println("main.....");
DateFormat df=new SimpleDateFormat("yyyy-MM-ddE HH:mm:ss");
Thread t1=new Thread(()->{
while(true) {
Date now=new Date();
System.out.println(df.format(now));
try {
Thread.sleep(10000);// 子线程进入休眠阻塞状态,当超时后自动进入就绪状态,等待下次调度执行
} catch (InterruptedException e) {
e.printStackTrace();
// break;
}
}
});
t1.start();
}
}
wait()/wait(long ms) 是在java.lang.Object类中定义的方法
wait()等价于wait(0),没有超时时长,一直等待到被唤醒
wait(long)一直等待到超时为止或者提前唤醒
- wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用此对象的notify()方法或notifyAll()方法,当前线程被唤醒,进入就绪状态
- notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
- wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
让当前线程进入等待状态,当别的其他线程调用notify()或者notifyAll()方法时,当前线程进入就绪状态要求:wait方法必须在同步上下文中调用,例如:同步方法块或者同步方法中,这也就意味着如果你想要调用wait方法,前提是必须获取对象上的锁资源
当wait方法调用时,当前线程将会释放已获取的对象锁资源,并进入等待队列,其他线程就可以尝试获取对象上的锁资源。
import java.util.Date;
public class Test2 {
public static void main(String[] args) {
Test2 t2 = new Test2();
Thread t1 = new Thread() {
@Override
public void run() {
t2.pp();
}
};
t1.start();
}
public void bb() {
// 创建锁对象,保证唯一性
Object obj = new Object();
new Thread() {
public void run() {
// 保证等待和唤醒只能执行一个,需要使用同步技术
synchronized (obj) {
System.out.println("点外卖");
// 调用wait()方法,放弃CPU的执行权,进入WAITING状态(无限等待)
try {
obj.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后的代码
System.out.println("外卖已到达");
}
}
}.start();
}
public synchronized void pp() {
System.out.println("begin..." + new Date().getTime());
try {
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end..." + new Date().getTime());
}
}