进程
正在运行的程序.称为进程. 一个应用程序在内存中占用的资源,才是进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K9ntUP6V-1613745479546)(images/进程概念.png)]
线程
线程 : 是进程中的某一功能,开启了一条让CPU来执行路径,路径就是线程
线程就是一条路,CPU去内存中取出数据执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BEqqQns2-1613745479549)(images/线程概念.jpg)]
线程的调度模型
每个线程都是具有优先级,优先级越高的线程,相对的会占用CPU的资源会更多.每个线程都是采用默认的优先级,轮流的使用CPU的资源,平均分配
设置线程的优先级,抢占
Java中实现线程程序(一)
线程也是对象,对象都是由类创建
java.lang.Thread
类,线程对象类
继承Thread类
实现步骤:
- 定义类继承Thread
- 子类重写方法run
- 创建子类的对象
- 调用方法start()启动线程
public class MyThread extends Thread{
public void run() {
for(int i = 0 ; i < 100; i++) {
System.out.println("子类重写方法run..."+i);
}
}
}
public static void main(String[] args) {
//创建Thread类的子类对象
MyThread my = new MyThread();
//对象调用方法start()启动线程
my.start();
for(int i = 0 ; i < 100; i++) {
System.out.println("main方法..."+i);
}
}
线程的随机性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4CiFQu7q-1613745479552)(images/线程的随机性.jpg)]
线程的内存图
每次启动一个新的线程,就会创建出新的栈内存,运行方法run()
开启的线程越多,内存消耗越大. 栈内存(线程栈,私有)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0uGX8A8-1613745479557)(images/线程内存图.jpg)]
线程创建的问题
- 为什么要继承Thread类
- Thread是线程对象,继承Thread,我们自己定义的类也是线程对象
- 为什么要重写run方法
- 线程每次要运行什么程序,这是未知的,比如,程序运行循环,运行创建对象,运行IO复制
- sun公司 : 无论你要做什么事情,线程要完成的事情,都写在run()中
- 启动线程为什么不调用run(),调用start()
- start()启动线程, JVM调用run()
- start()方法最终执行的是start0(),是本地方法,C++写出来,和操作系统交互,启动线程是操作系统的事情
Java中实现线程程序(二)
实现接口的方式
实现步骤:
- 定义类实现接口Runnable
- 重写方法run()
- 创建Thread类对象
- 构造方法 Thread(Runnable target)传递Runnable接口实现类
- 调用Thread类方法strat()启动线程
public class MyRunnable implements Runnable {
public void run() {
for(int i = 0 ; i < 100 ; i++) {
System.out.println("实现类重写方法run..."+i);
}
}
}
public static void main(String[] args) {
Runnable r = new MyRunnable();
//创建Thread类对象,传递Runnable接口实现类对象
Thread thread = new Thread(r);
//Thread类方法start()启动线程
thread.start();
for(int i = 0 ; i < 100 ; i ++) {
System.out.println("main..."+i);
}
}
两种实现线程的方式区别
- 单继承,继承方式实现线程,有局限性
- 接口实现,可以多实现,避免了单继承的局限性
- 继承方式,耦合性比较强,线程和线程要执行的任务耦合一起
- 接口实现,线程和线程任务的分离效果,低耦合,面向对象的思想
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1RSuQw0T-1613745479561)(images/实现线程方式的区别.jpg)]
线程名字的操作
获取名字
每个线程都有自己的名字,默认的
Thread类方法 String getName()
线程的默认名字是: Thread-0, Thread-1
public class MyThread extends Thread{
public void run() {
//直接调用父类的方法getName()
System.out.println("继承重写方法.."+super.getName());
}
}
Thread类方法 :
static Thread cuurentThread()
返回当前线程对象- 当前线程 : 静态方法写在哪个线程里面,哪个就是对当前线程
Thread t = Thread.currentThread(); t.getName()
获取线程名字的通用方式
设置名字
Thread类的方法setName(String 线程名)
不建议修改线程名字
线程安全
开发的是多线程的程序,如果出现了线程的安全问题,程序人员是最大的责任人.
安全问题产生 : 必须是多线程程序,争夺同一个资源
售票案例
电影票源是固定值,常量. 网络售票,现场自动售票机,人工窗口
现场有3个人工窗口,同时售票
public class Ticket implements Runnable {
//定义100张
private int tickets = 100;
//线程调用的方法,售票工作
public void run() {
//票数是0的时候,停止了
while(true) {
if(tickets > 0) {
System.out.println("出售第"+tickets-- +"张");
}
}
}
}
public static void main(String[] args) {
//创建接口实现类对象
Ticket ticket = new Ticket();
//创建3个线程
Thread t0 = new Thread( ticket );
Thread t1 = new Thread( ticket );
Thread t2 = new Thread( ticket );
//启动线程
t0.start();
t1.start();
t2.start();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxlF6amA-1613745479564)(images/线程安全隐患的分析.jpg)]
Thread.sleep(long 毫秒) 模拟出了,产生错误票的过程
public void run() {
//票数是0的时候,停止了
while(true) {
if(tickets > 0) {
//线程休眠,Thread类的静态方法sleep
try {
Thread.sleep(50);
}catch(Exception ex) {}
System.out.println("出售第"+tickets-- +"张");
}
}
}
解决安全问题
思想 : 当一个线程没有完成售票工作之前,其他的线程不能参与卖票
同步技术 : 保证线程安全的解决办法
-
同步技术:
- 关键字 synchronized
- 任意对象
- 线程操作的共享资源
-
同步的写法格式 :
synchronized( 任意的对象 ){ 写的是线程操作的所有共享资源 } 同步代码块
private Object obj = new Object(); //线程调用的方法,售票工作 public void run() { //票数是0的时候,停止了 while(true) { synchronized( obj ) { if(tickets > 0) { //线程休眠,Thread类的静态方法sleep try { Thread.sleep(5); }catch(Exception ex) {} System.out.println(Thread.currentThread().getName()+ "出售第"+tickets-- +"张"); } } } }
同步工作原理
就是把对象锁 ( 唯一性 )
线程判断锁,获取锁,释放锁, 一旦线程安全,运行速度下降. 必须牺牲速度,保证数据安全性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wjla703s-1613745479566)(images/同步的原理.jpg)]
方法的优化
public void run() {
//调用售票的方法
while(true) {
saleTicket();
}
}
//定义方法,专门处理售票 tickets--
public void saleTicket() {
synchronized( obj ) {
if(tickets > 0) {
//线程休眠,Thread类的静态方法sleep
try {
Thread.sleep(5);
}catch(Exception ex) {}
System.out.println(Thread.currentThread().getName()+ "出售第"+tickets-- +"张");
}
}
方法saleTicket()出现了一个情况,整个的方法里面,所有程序,都是线程操作的共享数据.不在使用同步的代码块了,可以同步整个的方法
//方法的修饰符位置,使用同步关键字
public synchronized void saleTicket() {
if(tickets > 0) {
//线程休眠,Thread类的静态方法sleep
try {
Thread.sleep(5);
}catch(Exception ex) {}
System.out.println(Thread.currentThread().getName()+ "出售第"+tickets-- +"张");
}
}
同步方法中也存在对象锁吗
同步方法中的锁,是当前对象的引用 : this
静态的同步方法中,锁肯定不是this
是当前类的对象, Ticket.class文件也是对象
静态的同步方法中,锁对象是当前类的class文件对象.
任何一个数据类型,都会有一个静态的属性,属性的名字就是class
public static synchronized void saleTicket() {
//synchronized(Ticket.class) {
if(tickets > 0) {
//线程休眠,Thread类的静态方法sleep
try {
Thread.sleep(5);
}catch(Exception ex) {}
System.out.println(Thread.currentThread().getName()+ "出售第"+tickets-- +"张");
}
//}
}
死锁案例
多个线程,在争抢同一个锁对象的时候,造成程序的假死现象,卡住
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qTGAHU3r-1613745479567)(images/死锁案例.jpg)]
public class DeadLock implements Runnable{
//定义成员变量,控制线程的走向
private boolean flag;
public DeadLock(boolean flag) {
this.flag = flag;
}
/*
* 实现死锁程序
*/
public void run() {
while(true) {
//判断flag=true
//线程先进去A锁同步,在进入B锁同步
if(flag) {
synchronized ( LockA.locka ) {
System.out.println("if..locka锁");
synchronized( LockB.lockb ) {
System.out.println("if..lockb锁");
}
}
}
//线程先进去B锁同步,在进去A锁同步
else {
synchronized( LockB.lockb ) {
System.out.println("esle..lockb锁");
synchronized( LockA.locka) {
System.out.println("else..locka锁");
}
}
}
}
}
}
// A类对象,做为锁来使用
public class LockA {
//静态的成员变量,返回对象
public static LockA locka = new LockA();
}
public class LockB {
public static LockB lockb = new LockB();
}
public static void main(String[] args) {
//创建接口Runnable实现类,成员变量设置为true
DeadLock d1 = new DeadLock(true);
//创建接口Runnable实现类,成员变量设置为false
DeadLock d2 = new DeadLock(false);
new Thread(d1).start();
new Thread(d2).start();
}
匿名内部类实现线程
public static void main(String[] args) {
// Runnable r = new Runnable() {
// public void run() {
// System.out.println("线程开启");
// }
// };
new Thread( new Runnable() {
public void run() {
System.out.println("线程开启");
}
} ).start();
new Thread() {
public void run() {
System.out.println("子类继承父类");
}
}.start();
}
Lock接口
JDK1.5出现的接口
java.util.concurrent.locks.Lock
替代synchronized,更加的灵活
- Lock接口的方法
void lock()
获取锁void unlock()
释放锁- 实现类ReentrantLock
public class Ticket implements Runnable {
//定义100张
private int tickets = 100;
//创建Lock接口实现类对象
private Lock lock = new ReentrantLock();
public void run() {
//调用售票的方法
while(true) {
//获取锁
lock.lock();
if(tickets > 0) {
//线程休眠,Thread类的静态方法sleep
try {
Thread.sleep(5);
}catch(Exception ex) {}
System.out.println(Thread.currentThread().getName()+ "出售第"+tickets-- +"张");
}
//释放锁
lock.unlock();
}
}
}
线程的生命周期,线程的状态
线程在他的一生中,创建到死亡,经过多个状态.
线程在某一个时刻,只能处于一种状态
线程共有 6 个状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bCUYeKUB-1613745479569)(images/线程生命周期 - 副本.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkxuKA1I-1613745479571)(images/线程状态图 - 副本.png)]
生产者与消费者(线程通信)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EJ6f6zH1-1613745511666)(images/生产者与消费者案例.jpg)]
实现生产者和消费者案例(一)
public class Resource {
//定义布尔类型的成员,标志位,指示线程该做什么
//false没有,需要生产, true需要消费
boolean flag = false;
int count ;//包子的计数器
}
public class Product implements Runnable {
//创建资源对象
Resource r = new Resource();
public void run() {
while(true) {
//对象资源进行操作,判断变量
if(r.flag == true) {
//没有被消费,不能生产,等待
try{wait();}catch(Exception ex) {ex.printStackTrace();}
}
//可以生产
r.count++;
System.out.println("生产了第"+r.count+"个");
//修改标志位
r.flag = true;//可以消费
//唤醒消费的线程
notify();
}
}
}
public class Customer implements Runnable{
//创建资源对象
Resource r = new Resource();
public void run() {
while(true) {
//判断标志位
if(r.flag == false) {
//需要生产,不能消费,等待
try{wait();}catch(Exception ex) {ex.printStackTrace();}
}
//可以消费
System.out.println("消费了第"+r.count+"个");
//修改标志位,已经消费,可以生产
r.flag = false;
//唤醒生产线程
notify();
}
}
}
以上程序启动线程后,抛出异常 java.lang.IllegalMonitorStateException(运行时期的异常) 异常称为无效的监视器状态异常. 使用了线程的方法,来自于Object类: wait() notify() , 方法必须出现在同步中
解决异常问题,使用同步代码块
实现生产者和消费者案例(二)
添加了同步代码块后,程序居然停止不动了
分析原因 : notify()方法的时候,没有唤醒任何一个线程,导致的生产和消费的两个线程都处于wait()状态
两个方向的线程,使用的锁不是一个
解决办法 : 生产者线程和消费者线程,使用同一个对象锁, wait() notify()方法调用者,必须是锁对象
public class Product implements Runnable {
//创建资源对象
Resource r ;
public Product(Resource r) {
this.r = r;
}
public void run() {
while(true) {
synchronized(r) {
//对象资源进行操作,判断变量
if(r.flag == true) {
//没有被消费,不能生产,等待
try{r.wait();}catch(Exception ex) {ex.printStackTrace();}
}
//可以生产
r.count++;
System.out.println("生产了第"+r.count+"个");
//修改标志位
r.flag = true;//可以消费
//唤醒消费的线程
r.notify();
}
}
}
}
public class Customer implements Runnable{
//创建资源对象
Resource r ;
public Customer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
synchronized(r) {
//判断标志位
if(r.flag == false) {
//需要生产,不能消费,等待
try{r.wait();}catch(Exception ex) {ex.printStackTrace();}
}
//可以消费
System.out.println("消费了第"+r.count+"....个");
//修改标志位,已经消费,可以生产
r.flag = false;
//唤醒生产线程
r.notify();
}
}
}
}
线程的方法问题:
Object类的方法wait(), Thread类的方法sleep()
- 问题 : 为什么等待唤醒的方法,定义在了Object类中,而不是Thread
- 同步锁导致,任何对象都能做为锁,保证任何一个锁对象都能调用等待与唤醒
- wait()和sleep()方法区别
- wait()只能出现同步中,必须是锁对象调用
- sleep()方法可以随时使用,比依赖同步
- wait()方法释放同步锁,被唤醒后,重写获取锁
- sleep()方法不释放同步锁
实现生产者和消费者案例(三)
私有修饰成员变量,提供方法访问
public class Resource {
//定义布尔类型的成员,标志位,指示线程该做什么
//false没有,需要生产, true需要消费
private boolean flag = false;
private int count ;//包子的计数器
//提供方法,生产
public synchronized void proruct() {
//判断变量=true,不能生产,等待
if(flag == true) {
try{this.wait();}catch(Exception ex) {ex.printStackTrace();}
}
count++;
System.out.println("...生产了第"+count+"个");
//修改标志位
flag = true;
//唤醒消费线程
this.notify();
}
//提供方法,消费
public synchronized void customer() {
//判断变量=false,不能消费,等待
if(flag == false) {
try{this.wait();}catch(Exception ex) {ex.printStackTrace();}
}
System.out.println("消费了第"+count+"个");
//修改标志位
flag = false;
//唤醒生产线程
this.notify();
}
}
public class Product implements Runnable {
//创建资源对象
private Resource r ;
public Product(Resource r) {
this.r = r;
}
public void run() {
while(true)
r.proruct();
}
}
public class Customer implements Runnable{
//创建资源对象
private Resource r ;
public Customer(Resource r) {
this.r = r;
}
public void run() {
while(true)
r.customer();
}
}
多生产者与多消费者(线程通信)
notify() 方法唤醒的时候,往往是最先等待的
我们需要什么效果 : 生产线程唤醒的是消费线程,消费线程唤醒生产线程. 现在唤醒的有可能是本方线程(NO).
但是,线程是一个独立的方法栈,CPU去里面数据运行,线程之间不认识谁是本方,谁是对方
使用Object类的方法 notifyAll() 唤醒所有的线程
线程全部唤醒后,依然没有达到需要的结果,因为线程唤醒后会直接就运行了,不会在判断flag标志是什么,线程已经判断过了
线程全部唤醒后也不能立刻就执行,再次判断flag标志,允许生产再生产,如果不允许生产,继续等
public class Resource {
//定义布尔类型的成员,标志位,指示线程该做什么
//false没有,需要生产, true需要消费
private boolean flag = false;
private int count ;//包子的计数器
//提供方法,生产
public synchronized void proruct() {
//判断变量=true,不能生产,等待
while(flag == true) {
try{this.wait();}catch(Exception ex) {ex.printStackTrace();}
}
count++;
System.out.println("...生产了第"+count+"个");
//修改标志位
flag = true;
//唤醒全部的线程
this.notifyAll();
}
//提供方法,消费
public synchronized void customer() {
//判断变量=false,不能消费,等待
while(flag == false) {
try{this.wait();}catch(Exception ex) {ex.printStackTrace();}
}
System.out.println("消费了第"+count+"个");
//修改标志位
flag = false;
//唤醒全部的线程
this.notifyAll();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KN0ZITlw-1613745511671)(images/多生产和多消费_2.JPG)]
多生产者与多消费者问题分析
- notifyAll()唤醒的全部的线程,资源的浪费
- 能不能只唤醒对方的一个
- wait(),notify(),notifyAll() 都是本地方法,C++编写方法,和操作系统交互
- 通知操作系统,将线程挂起不要允许, 通知操作系统,让线程继续允许
- 操作系统找CPU来实现线程的等待和唤醒
- 频繁的和操作系统交互,降低程序的效率
多生产者与多消费者(线程通信)改进
JDK1.5出现接口Lock, 方法lock(),unlock()
接口出现目的取代synchronized
Lock接口
接口中定义方法 :
Condition newCondition()
返回一个Condition的接口类型- Condition接口取代Object类的监视器方法
Condition接口
作用 : 线程的阻塞队列,本身是一个容器,存储的是线程.
进入到队列的线程,释放同步锁
一个锁Lock接口对象,可以产生出多个线程的阻塞队列.让线程释放同步锁后,进入到队列
需要唤醒线程的时候,指定唤醒哪一个队列中的线程
好处 : 不会全部唤醒, 不会和操作系统的进行交互,提高的效率
Condition接口方法 :
void await()
线程释放锁,并进去到线程的阻塞队列 , 取代了Object类的方法wait()void singal()
线程从阻塞队列出来,获取锁在运行 , 取代了Object类的方法notify()
public class Resource {
private boolean flag = false;
private int count ;
//创建Lock接口的实现类对象,做为锁,去掉synchronized
private Lock lock = new ReentrantLock();
//通过这个锁对象,产生出2个存储线程的容器 (阻塞队列)
//Lock接口方法 newCondition()
private Condition proCondition = lock.newCondition();//线程的阻塞队列,生产队列
private Condition customerCondition = lock.newCondition(); //线程的阻塞队列,消费队列
//提供方法,生产
public void proruct() {
//获取锁
lock.lock();
//判断变量=true,不能生产,等待
while(flag == true) {
//线程释放锁,到阻塞队列中呆着
try{proCondition.await();}catch(Exception ex) {ex.printStackTrace();}
}
count++;
System.out.println("...生产了第"+count+"个");
//修改标志位
flag = true;
//唤醒线程,必须要唤醒消费队列中的线程
customerCondition.signal();
//释放锁
lock.unlock();
}
//提供方法,消费
public void customer() {
//获取锁
lock.lock();
//判断变量=false,不能消费,等待
while(flag == false) {
//线程等待,释放锁,进入队列
try{customerCondition.await();}catch (Exception ex) {ex.printStackTrace();}
}
System.out.println("消费了第"+count+"个");
//修改标志位
flag = false;
//唤醒线程,生产队列中的线程
proCondition.signal();
lock.unlock();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wvqYcQXb-1613745511674)(images/线程的阻塞队列.JPG)]
线程池
概念 : 缓冲池是为了提高效率使用的. 线程池也是缓冲池的一种.(连接池,对象池)
为什么要出现线程池技术 : 创建线程是要和操作系统进行交互的,线程允许完毕( run()方法结束 ).频繁的创建线程,大量的浪费操作系统的资源. 为了解决资源消耗和提高效率问题,人们设计出了线程池.
线程池的思想 : 创建一些线程,存储在一个容器中,不要让线程销毁,需要的时候拿一个线程出来,线程执行完任务的时候,放回到容器中. 存储线程的容器,线程池
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MjRhpbm5-1613745511676)(images/线程池的实现原理.jpg)]
JDK内置线程池
java.util.concurrent.Executors
工厂类,创建对象, 创建线程池对象
创建线程池对象的方法 :
static ExecutorService newFixedThreadPool(int n)
创建具有固定个数的线程池,返回的是接口类型,实现类就是创建的线程池的对象
java.util.ExecutorService
接口中的方法submit( Runnable r ) 提交线程执行的任务
java.util.concurrent.Callable
接口,视为Runnable接口的扩展
接口的抽象方法: V call() 具有返回值的方法,而且还能抛出异常. 此方法做为线程的任务使用,可以获取到方法的返回值
线程池提交线程任务方法 submit(Callable c)传递接口实现类, 提交任务的方法submit有返回值
返回的是Futrue的接口类型,接口的实现类,就是线程允许的结果返回值
public class MyCallable implements Callable<String>{
public String call() throws Exception{
return "线程的执行结果";
}
}
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newFixedThreadPool(2);
//提交线程任务 submit, 接收submit方法返回值,接口类型
Future<String> f = es.submit(new MyCallable());
//Futrue接口的方法 get()拿到线程的计算结果
String string = f.get();
System.out.println(string);
}
线程池的异步计算
public class MyCallable implements Callable<Integer>{
private int a;
public MyCallable(int a) {
this.a = a;
}
public Integer call() {
int sum = 0;
for(int i = 1 ; i <= a; i++) {
sum = sum + i;
}
return sum;
}
}
public static void main(String[] args) throws Exception{
//创建2个固定个数的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//提交任务
Future<Integer> f = es.submit(new MyCallable(100));
System.out.println(f.get());
//es.submit(task);
f = es.submit(new MyCallable(200));
System.out.println(f.get());
}
ConcurrentHashMap
java.util.concurrent.ConcurrentHashMap
实现Map接口,是键值对的集合
集合特点 :
- 底层哈希表结构
- 保证键的唯一性,键对象重写hashCode和equals
- 是线程安全的集合,半安全
- 不更改集合中的元素,不会锁定 (synchronized)
- 改变的元素,使用同步锁
- 操作的是哪个数组的索引,锁定当前的数组索引
- 不会出现并发修改异常
- 不能存储null值,null键
public static void main(String[] args) {
// HashMap<String, String> map = new HashMap<String, String>();
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
Set<String> set = map.keySet();
Iterator<String> it = set.iterator();
while(it.hasNext()) {
String key = it.next(); map.put("d", "4");
System.out.println(key + map.get(key));
}
}
public static void main(String[] args)throws Exception {
// HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<Integer, Integer>();
//集合存储2000个元素
for(int i = 1; i <= 2000; i++) {
map.put(i, 0);
}
//线程,删除Map集合中的前500
new Thread( new Runnable() {
public void run() {
for(int i = 1 ; i <= 500 ;i++) {
map.remove(i);
}
}
} ) .start();
//线程,删除Map集合,501-1000
new Thread( new Runnable() {
public void run() {
for(int i = 501 ; i <= 1000 ;i++) {
map.remove(i);
}
}
} ) .start();
Thread.sleep(2000);
System.out.println(map.size());
}
原子操作
原子操作,就是不可分割的操作
i++ 非原子操作,出现线程的安全问题
AtomicInteger
整数的原子类,实现对整数的操作保证线程的安全
public static void main(String[] args) throws InterruptedException {
//创建原子类对象, 内部变量默认是0
AtomicInteger atomicInteger = new AtomicInteger(100);
//变量自增
while(true) {
int i = atomicInteger.getAndIncrement();
System.out.println(i);
Thread.sleep(100);
}
}