线程的五种状态
参考JDK帮助文档:
注:此图片摘自bilibili UP:狂神说Java
- 创建状态:Thread t = new Thread();
new一个线程,则此线程进入创建状态 - 就绪状态:调用start()方法,线程进入就绪状态,但不会被立即执行
- 执行状态:当线程被CPU调度时,线程进入运行状态
- 阻塞状态:sleep()、wait()或者同步锁等方法,使得线程进入阻塞状态。阻塞解除,线程进入就绪状态,等待CPU调度
- 死亡状态:线程中断(非自然死亡)或者结束(自然死亡),就进入死亡状态,无法再次启动。
线程的停止
不推荐使用JDK提供的stop()、destroy()等方法
定义标志位
使用自定义的stop 方法,改变标志位,来控制线程停止
具体实现:
package com.Thread.demo03;
public class TestStop implements Runnable{
private boolean flag=true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run......Thread"+i++);
}
}
//自己定义一个stop方法,使得标志位改变,来终止线程
public void stop(){
flag=false;
}
public static void main(String[] args) {
// new一个runnable接口的实现类的对象
TestStop testStop = new TestStop();
// new一个线程,然后丢入创建的接口实现类的对象,然后启动线程——>就绪状态,不会立刻执行,需要等待CPU调度
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if (i==520){
testStop.stop();
System.out.println("线程停止");
}
}
}
}
线程休眠
- sleep(时间)指定当前线程堵塞的毫秒数
- sleep存在异常InterruptedException;,需要抛出
- 时间达到之后进入就绪状态
- slee可以模拟网络延时和倒计时等等
- 每个对象都有自己的锁,sleep不会释放锁
实例:获取当前系统时间,休眠一秒
public static void main(String[] args) throws InterruptedException {
Date startime = new Date(System.currentTimeMillis());//获取当前时间
while (true){
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startime));
startime = new Date(System.currentTimeMillis());//更新当前时间
}
}
线程礼让
礼让不一定成功,因为CPU调度是随机的
package com.Thread.demo03;
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程结束");
}
}
执行结果:
线程强制执行
join ()——插队,其他线程阻塞,少用
package com.Thread.demo03;
//插队,其他线程阻塞
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("VIP氪金玩家"+i+"已进入游戏");
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 500; i++) {
if (i==100){
thread.join();
}
System.out.println("main"+i);
}
}
}
线程状态观测
Thread.State state = thread.getState();
System.out.println(state);
线程不安全案例
1.不安全的购票
创建一个类实现Runnable接口
重写run()
定义buy()方法,具体定义如何买票:
设置标志位,控制线程终止
每次进入判断票数是否为0,当票数为0的时候,标志位变为false
利用sleep()方法,使得线程阻塞,增加错误的发生性
输出每次抢票结果
package com.Thread.state;
public class UnsaftyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"DJ").start();
new Thread(station,"AI").start();
new Thread(station,"黄牛").start();
}
}
class BuyTicket implements Runnable{
private int ticketNum=10;
private boolean flag=true;
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
if (ticketNum==0){
flag=false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum--);
}
}
2.不安全的银行账户
情境:两人同时操作一个账户
因为线程会把账户余额信息,copy到自己的内存中,故此二者看到的账户余额是相同的,而二者都可以对账户进行操作,造成线程不安全
package com.Thread.state;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"结婚基金");
Drawing you =new Drawing(account,50,"你");
Drawing wife =new Drawing(account,100,"你老婆");
you.start();
wife.start();
}
}
/*1.首先定义一个账户
里面存储账户的钱数和账户名称
定义有参构造方法
*/
class Account{
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
/*
2.定义一个取钱方法继承Thread
属性包括:取走钱数、卡内余额,本人现有金额
定义有参构造方法
实现run方法
判断所剩余额是否够取
给取走钱数和卡内余额赋值
*/
class Drawing extends Thread{
Account account;
double drawMoney;
double nowMoney;
public Drawing(Account account,double drawMoney , String name){
super(name);
this.account=account;
this.drawMoney=drawMoney;
}
@Override
public void run() {
//如果银行存款-取款<0 说明账户内余额不足
if (account.money-drawMoney<0){
System.out.println("尊敬的客户:"+Thread.currentThread().getName()+",你的账户余额不足");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//余额=余额-取走的钱
account.money-=drawMoney;
//本人现有金额=原有金额+从银行取走的钱
nowMoney=nowMoney+drawMoney;
//Thread.currentThread().getName()
System.out.println(account.name+"余额为:"+account.money);
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里的钱::"+nowMoney);
}
}
3.不安全列表
- 两个线程都往一个位置添加数据,这样的话,后者会把前者已经添加的数据覆盖,导致线程不安全
package com.Thread.state;
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list=new ArrayList<String>(){};
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(2000);
System.out.println(list.size());
}
}