线程状态
创建
用new操作符创建一个线程。此时程序还没有开始运行线程中的代码。
就绪
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序来调度的。
运行
当线程抢到了CPU时间片段之后,他才进入了运行状态,开始执行run方法
阻塞
线程运行过程中,可能因为各种原因阻塞
- 线程通过调用sleep方法进入睡眠状态
- 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者
- 线程试图得到一个锁,而该锁正被其他线程持有
- 线程在等待某个触发条件
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
堵塞状态是前述四种状态中最有趣的,值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造成的:
- 调用sleep(毫秒数),使线程进入"睡眠"状态。在规定的时间内,这个线程是不会运行的。
- 用suspend()暂停了线程的执行。除非线程收到resume()消息,否则不会返回"可运行"状态
- 用wait()暂停了线程的执行。除非线程收到nofify()或者notifyAll()消息,否则不会变成"可运行"(是的,这看起来同原因2非常相象,但有一个明显的区别是我们马上要揭示的)
- 线程正在等候一些IO(输入输出)操作完成
- 线程试图调用另一个对象的"同步"方法,但那个对象处于锁定状态,暂时无法使用
死亡状态
有两个原因会导致线程死亡:
- run方法正常退出而自然死亡
- 一个未捕获的异常终止了run方法而使线程猝死
![]()
![]()
线程方法
![]()
停止线程
不推荐使用jdk提供的 stop()、destroy()方法,【已经废弃】
推荐让线程自己停下来
建议使用一个标志位进行终止标量,当flag=false ,则线程终止
例子
/**
* 使用标志位方法停止线程
*/
public class TestStop implements Runnable{
//创建标志位
private Boolean falg = true;
@Override
public void run() {
int i= 1;
while (falg){
System.out.println("线程"+Thread.currentThread().getName()+"正在运行----------->"+ i++);
}
}
//写一个stop 停止程序
public void stop(){
this.falg=false;
}
public static void main(String[] args) {
TestStop ts = new TestStop();
// new Thread(ts).start();
Thread t = new Thread(ts,"TestStop");
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
//主线程循环到900的时候就停止线程
if (i==900) {
ts.stop();
System.out.println("线程"+t.getName()+"停止了");
};
}
}
}
结果
main899
main900
线程TestStop正在运行----------->267
线程TestStop停止了
main901
main902
线程休眠
- sleep(时间)指当前线程进入阻塞状态时间(毫秒)
- sleep存在异常InterruptedException
- sleep时间达到后线程重新进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每个线程都有一个锁,sleep不会释放锁
![]()
案例
模拟倒计时
/**
* 模拟倒计时
*/
public class TestSleep {
public static void main(String[] args) {
int num =10;
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
每秒打印一次系统时间
/**
* 获取打印当前时间
*/
public class GetTime {
public static void main(String[] args) {
//获取当前系统时间
Date date = new Date(System.currentTimeMillis());
while (true){
System.out.println(new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让cpu重新调度,礼让并不一定成功,看cpu调度
/**
* 线程礼让
*/
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"小白").start();
new Thread(myYield,"小黑").start();
}
}
class MyYield implements Runnable {
@Override
public void run() {
System.out.println("线程开始---->"+Thread.currentThread().getName());
//礼让
Thread.yield();
System.out.println("线程结束---->"+Thread.currentThread().getName());
}
}
礼让成功
![]()
礼让失败
![]()
Join(线程强制执行)
Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞(可以想象成插队)
/**
* 测试线程强制执行(插队)
*/
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
VipJoin vipJoin = new VipJoin();
Thread t = new Thread(vipJoin);
t.start();
//主线程执行100次
for (int i = 0; i < 100; i++) {
//当主线程执行50次的时候,vip用户直接插队
if (i==50) {
t.join();
};
System.out.println("main---->"+i);
}
}
}
/**
* vip线程
*/
class VipJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("vip用户------------->"+i);
}
}
}
线程状态观测
Thread.State
线程可以处于一下状态:
- NEW: 尚未启动
- RUNNABLE: 在java虚拟机中执行
- **BLOCKED **: 被阻塞等待监视器锁定
- WAITING: 正在等另一个线程执行特定动作
- TIME WAIYING: 正在等另一个线程执行动作达到指定等待时间
- TERMINATED: 已退出的线程
一个线程可以在给定时间点处于一个状态。这些状态是不反应任何操作系统线程状态的虚拟机状态。
/**
* 监测线程状态
*/
public class TestState {
public static void main(String[] args) {
SeeState seeState = new SeeState();
Thread t = new Thread(seeState);
//获取线程状态并打印
Thread.State state = t.getState();
System.out.println(state);
t.start();
//获取启动后的线程状态
state = t.getState();
System.out.println(state);
//只要线程没有终止,就一直输出状态
while (state!=Thread.State.TERMINATED){
try {
//休眠一下,检查线程休眠后的阻塞状态
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程状态并打印
state = t.getState();
System.out.println(state);
}
}
}
class SeeState implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
}
}
}
结果
NEW
RUNNABLE
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TIMED_WAITING
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TIMED_WAITING
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TIMED_WAITING
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TIMED_WAITING
TIMED_WAITING
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TERMINATED
线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定先调度那个线程执行。
线程的优先级用数字表示,范围从0-10
使用getPriority()回去线程优先级,使用setPriority()改变优先级
优先级底只是意味着获得调度的概率低,并不是不会被调度,同样的,优先级高也只是增加优先执行的几率,但并不是一定会优先执行,一切都看CPU的调度
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待用户线程执行完毕
比如后台记录日志操作、监控内存、垃圾回收等等。
public class TestDanmon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread t = new Thread(god);
Thread t2 = new Thread(you);
t.setDaemon(true);
t.start();
t2.start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝一直保佑着你。。。。。");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你开心的活着...");
}
System.out.println("你没了====================================");
}
}
当用户线程结束了,守护线程也会自动结束。
线程同步
并发:同一个对象被多个线程同时操作
![]()
现实生活中,我们会遇到同一资源,多个人都想使用 的问题,比如食堂排队,打饭,每个人都想吃饭,最好的的办法就是排队一个个来。
处理多线程问题时,多个线程访问一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池排队,等前面的线程使用完毕,下一个线程在使用。
![]()
不安全案例
银行取钱
/**
* @author c
* @Package com.qc.testThread
* @date 2021/2/28 14:38
*
* 线程不安全案例 之 银行取钱
*/
public class TakeMoney implements Runnable{
//银行有小两口的账户
private Account account;
//要取的钱
private int takeMoney;
//手里有的钱
private int newMoney;
public TakeMoney(Account account, int takeMoney) {
this.account = account;
this.takeMoney = takeMoney;
}
//模拟取钱业务
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"来取款,卡内现有余额"+account.getMoney());
if (takeMoney<account.getMoney()){
System.out.println(Thread.currentThread().getName()+"来取款,余额不足,取款失败");
return;
}
//取款成功 存款减去取出的钱
account.setMoney(account.getMoney()-takeMoney);
//手里的钱加上取出来的钱
newMoney = newMoney + takeMoney;
System.out.println(Thread.currentThread().getName()+"取款成功,手里现有: "+newMoney);
System.out.println("卡内现余: "+account.getMoney());
}
public static void main(String[] args) {
Account account = new Account("结婚基金",100);
TakeMoney you = new TakeMoney(account,100);
TakeMoney girlfriend = new TakeMoney(account,50);
new Thread(you,"你").start();
new Thread(girlfriend,"girlfriend").start();
}
}
/**
* 小两口共同的账户
*/
class Account{
//账户名
private String name;
//账户余额
private int money;
public String getName() {
return name;
}
public int getMoney() {
return money;
}
public void setName(String name) {
this.name = name;
}
public void setMoney(int money) {
this.money = money;
}
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
线程不安全之 ArrayList
public class TestArrayList {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(() ->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
结果
按理说应该添加10000条,结果只添加了9991条,说明arrayList是线程不安全的
9991
线程同步
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized 关键字,它包括两种用法:
synchronized方法和synchronized块。
//同步方法
public synchronized void method(int args){}
synchronized方法控制对对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象锁才能执行,或者线程会阻塞,方法一旦执行,就独占该锁,知道方法返回才释放该锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺陷: 若将一个大的方法声明为synchronized 将会影响效率。
synchronized方法
public class TestSynchronized implements Runnable{
@Override
public synchronized void run() {
System.out.println("你");
System.out.println("是");
System.out.println("最");
System.out.println("棒");
System.out.println("的");
System.out.println("!");
}
}
class Test2{
public static void main(String[] args) {
TestSynchronized ts = new TestSynchronized();
new Thread(ts,"ts").start();
new Thread(ts,"ts2").start();
}
}
没加之前
你
你
是
是
最
最
棒
棒
的
的
!
!
加了之后
你
是
最
棒
的
!
你
是
最
棒
的
!
synchronized块
同步块:synchromized(obj){}
obj称之为同步监视器
- obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,应为同步方法的监视器就是this(对象本身),或者class
同步监视的执行过程:
- 第一个线程访问,锁同步监视器,执行其中代码
- 第二个线程访问,发现锁同步监视器被锁定,无法访问
- 第一个线程访问完毕,解放锁同步件事器
- 第二个线程访问,发现同步监视器没有锁,然后锁定访问
public class TestSynchronized implements Runnable{
@Override
public void run() {
synchronized(this) {
// synchronized (TestSynchronized.class){
System.out.println("你");
System.out.println("是");
System.out.println("最");
System.out.println("棒");
System.out.println("的");
System.out.println("!");
}
}
}
class Test2{
public static void main(String[] args) {
TestSynchronized ts = new TestSynchronized();
new Thread(ts,"ts").start();
new Thread(ts,"ts2").start();
}
}
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况,某一个同步块同时拥有两个以上的锁时,可能就会发生死锁的问题。
案例
/**
* @author c
* @Package com.qc.testThread
* @date 2021/2/28 18:00
*
* 死锁
*/
public class TestDeadlock{
public static void main(String[] args) {
Makeup makeup = new Makeup(0,"阿花");
Makeup makeup1 = new Makeup(1,"小婵");
new Thread(makeup).start();
new Thread(makeup1).start();
}
}
/**
口红
*/
class Lipstick{}
/**
镜子
*/
class Mirror{
}
/**
* 化妆
*/
class Makeup implements Runnable{
//只有一份资源,用static来保证
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
//选择
int choice;
//使用化妆品的人
String name;
public Makeup(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
//化妆
makeup();
}
//化妆,互相持有对方的锁,需要得到对方的资源
private void makeup(){
if (choice==0){
//获得口红的锁
synchronized (lipstick){
System.out.println(this.name+"获得了口红的锁");
System.out.println(this.name+"想获得镜子的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror){
System.out.println(this.name+"获得镜子的锁");
}
}
}else {
synchronized (mirror){
System.out.println(this.name+"获得了镜子的锁");
System.out.println(this.name+"想获得口红的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick){
System.out.println(this.name+"获得口红的锁");
}
}
}
}
}
结果:阿花和小婵都想获得对方的锁,但都不让步,因此僵持住了
小婵获得了镜子的锁
阿花获得了口红的锁
小婵想获得口红的锁
阿花想获得镜子的锁
lock锁
从jdk5.0开始,java提供了更强大的线程同步机制-------通过显式定义同步锁对象来实现同步,同步锁使用lock对象充当
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应先获得lock对象
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁。
/**
* @author c
* @Package com.qc.testThread
* @date 2021/2/28 18:34
*
* 测试ReetrantLock加锁
*
* //lock锁默认是非公平的 第一次获取锁的对象 第二次获取的概率比较大。
*
* 除非改成公平锁,或者让出时间片Thread.yield();
*/
public class TestLock implements Runnable{
//次数太少发现一个线程就跑完了
int num = 200;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//lock.lock()要放在try前面
lock.lock();
try {
if (num<=0){
break;
}
System.out.println(Thread.currentThread().getName()+" "+num--);
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
TestLock testLock = new TestLock();
new Thread(testLock,"1号").start();
new Thread(testLock,"2号").start();
new Thread(testLock,"3号").start();
}
}
经典之生产者消费者
![]()
生产者
package com.qc.testThread;
import java.util.ArrayList;
import java.util.List;
/**
* @author c
* @Package com.qc.testThread
* @date 2021/2/28 21:44
*
* 生产者
*/
public class Producers implements Runnable{
//生产者的产品列表
private static List<String> list = new ArrayList();
static {
list.add("苹果");
list.add("香蕉");
list.add("梨子");
list.add("桔子");
list.add("火龙果");
list.add("樱桃");
list.add("柚子");
list.add("木瓜");
list.add("葡萄");
list.add("西瓜");
}
//生产者为购物中心生产产品
private Mall mall;
public Producers(Mall mall) {
this.mall = mall;
}
//重写线程体,让生产者为购物中心生产商品
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+" 开始生产了。。。");
for (int i = 0; i <10; i++) {
//随机偷懒
try {
Thread.sleep((long) (1000*Math.random()));
} catch (InterruptedException e) {
e.printStackTrace();
}
//生产者很任性,随机生产产品
int ioc = (int) (list.size()* Math.random());
String goods = list.get(ioc);
//生产完了,直接送送货车
mall.setGoods(goods);
}
System.out.println(name+"完成了生产任务下班啦,喜大普奔。");
}
}
消费者
package com.qc.testThread;
import java.util.LinkedList;
import java.util.List;
/**
* @author c
* @Package com.qc.testThread
* @date 2021/2/28 21:46
*
* 消费者
*/
public class Consumers implements Runnable{
//消费者有事没事就去购物中心消费
private Mall mall;
public Consumers(Mall mall) {
this.mall = mall;
}
@Override
public void run() {
//土豪家的储物柜
List<String> list = new LinkedList<String>();
String name = Thread.currentThread().getName();
for (int i = 0; i <30; i++) {
//土豪心情不好,休息一下
try {
Thread.sleep((long) (1000* Math.random()));
} catch (InterruptedException e) {
e.printStackTrace();
}
//土豪心情不好,决定去购物---->开始购物
String goods = mall.getGoods();
//将商品存放到储物柜
list.add(goods);
}
System.out.println(name+"成功把商场买空,老板大气!");
System.out.println(name+"完成购物了,购买的所有商品如下=========>>>"+list);
}
}
购物天堂
package com.qc.testThread;
import java.util.LinkedList;
import java.util.List;
/**
* @author c
* @Package com.qc.testThread
* @date 2021/2/28 21:46
*
* 购物天堂
*/
public class Mall {
/**商场有货架*/
List<String> list = new LinkedList<String>();
//送货车
public synchronized void setGoods(String goods){
String name = Thread.currentThread().getName();
//判断是否能送货
while (list.size()==10){
try {
System.out.println(name+"试图送货,但货架满了,正在等待消费。。。。。。");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//将送来的货物放入货架
list.add(goods);
System.out.println(name+ "送来了一个"+goods+"到商场,可以消费了");
System.out.println("商品明细为===========>>>"+list);
//唤醒消费者
this.notifyAll();
}
//购物车,提供给消费者使用
public synchronized String getGoods(){
String name = Thread.currentThread().getName();
//判断是否可以购物
while (list.size()==0){
try {
System.out.println(name+"试图购物,但商场没货了,正在等待进货.....");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//土豪在货架随机购买
int ioc = (int) (list.size()*Math.random());
String goods = list.get(ioc);
//商品被购买,从货架移除
list.remove(ioc);
System.out.println(name+"购买了"+goods);
System.out.println("此时商品明细为"+list);
//唤醒生产者继续生产
this.notifyAll();
return goods;
}
}
测试类
package com.qc.testThread;
/**
* @author c
* @Package com.qc.testThread
* @date 2021/2/28 21:33
*
* 金典的生产者消费者模式
*/
public class Classic {
public static void main(String[] args) {
//购物中心
Mall mall = new Mall();
//工人1、2、3为土豪五福
Producers producers = new Producers(mall);
Producers producers2 = new Producers(mall);
Producers producers3 = new Producers(mall);
//土豪
Consumers consumers = new Consumers(mall);
new Thread(producers,"工人三号").start();
new Thread(producers2,"工人二号").start();
new Thread(producers3,"工人一号").start();
new Thread(consumers,"土豪").start();
}
}