线程的状态转换图及常见的执行情况
1- 线程通信
/**
取钱的线程类。
*/
public class DrawThread extends Thread {
// 定义一个成员变量接收账户对象
private Account acc;
public DrawThread(String name , Account acc){
super(name);
this.acc = acc;
}
@Override
public void run() {
// 小明和小红来取钱。
while(true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
acc.drawMoney(10000);
}
}
}
/**
存钱的线程类。
*/
public class SaveThread extends Thread {
// 定义一个成员变量接收账户对象
private Account acc;
public SaveThread(String name , Account acc){
super(name);
this.acc = acc;
}
@Override
public void run() {
// 3个爸爸来反复的存钱。
while(true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
acc.saveMoney(10000);
}
}
}
/**
* 账户对象:
*/
public class Account {
private String cardId ;
private double money ; // 余额
// 亲爹 干爹 岳父
public synchronized void saveMoney(double money) {
try{
String name = Thread.currentThread().getName();
// 判断是否有钱
if(this.money == 0){
// 没钱,需要存钱
this.money += money ;
System.out.println(name + "来存钱" + money+"成功,剩余"+this.money);
// 钱已经取完了!!暂停自己
this.notifyAll(); // 唤醒其他线程!
this.wait(); // 等待自己
}else{
this.notifyAll(); // 唤醒其他线程!
this.wait(); // 等待自己
}
}catch (Exception e){
e.printStackTrace();
}
}
// 小明/小红
public synchronized void drawMoney(double money) {
try{
// 1.先拿到是谁来取钱:拿到当前线程的名字即可,名字是谁就是谁来取钱
String name = Thread.currentThread().getName();
// 2.判断余额是否足够 :
if (this.money >= money) {
// 钱够了
// 3.更新余额
this.money -= money;
System.out.println(name + "来取钱,余额足够,吐出:" + money+",剩余"+this.money);
// 钱已经取完了!!暂停自己
this.notifyAll(); // 唤醒其他线程!
this.wait(); // 等待自己
}else{
// 没钱了
this.notifyAll(); // 唤醒其他线程!
this.wait(); // 等待自己
}
}catch (Exception e){
e.printStackTrace();
}
}
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
/**
线程通信:多个线程因为在同一个进程中,所以互相通信比较容易。
线程通信的经典模型:生产者与消费者问题。
生产者负责生成商品,消费者负责消费商品。
生成不能不剩,消费不能没有,是一个同步模型。
线程通信必须先保证线程安全,否则代码会报出异常!!
线程通信的核心方法:
public void wait()` : 让当前线程进入到等待状态 此方法必须锁对象调用.
public void notify()` : 唤醒当前锁对象上等待状态的某个线程 此方法必须锁对象调用
public void notifyAll()` : 唤醒当前锁对象上等待状态的全部线程 此方法必须锁对象调用
*/
public class ThreadCommunication {
public static void main(String[] args) {
// 1.创建一个共享资源账户对象
Account acc = new Account("ISBC-110" , 0);
// 2.定义2个取钱线程代表小明和小红
new DrawThread("小明",acc).start();
new DrawThread("小红",acc).start();
// 3.定义3个存钱的线程代表 亲爸,岳父,干爹
new SaveThread("亲爸",acc).start();
new SaveThread("干爹",acc).start();
new SaveThread("岳父",acc).start();
}
}
2-线程休眠
/**
Thread.sleep(5000):参数是毫秒,让当前所在线程对象休眠5s。
*/
public class ThreadDemo {
public static void main(String[] args) {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println("输出:"+i);
if(i == 5){
try {
// 让当前线程休眠5s,休眠是不释放锁的。
// 项目经理让我加上这行代码,如果用户交钱了就注释掉。
Thread.sleep(5000); //
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
3-死锁
/**
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
java 死锁产生的四个必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:p1要p2的资源,p2要p1的资源。这样就形成了一个等待环路
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失实现死锁一般需要进行锁资源的嵌套才会出现死锁。
*/
public class ThreadDead {
// 至少要两个资源
public static Object resources1 = new Object();
public static Object resources2 = new Object();
public static void main(String[] args) {
// 至少要2个线程。
new Thread(new Runnable() {
@Override
public void run() {
synchronized (resources1){
System.out.println("线程对象1对资源1上锁占用资源1");
System.out.println("线程对象1开始请求资源2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resources2){
System.out.println("线程对象1对资源2上锁占用资源2");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (resources2){
System.out.println("线程对象2对资源2上锁占用资源2");
System.out.println("线程对象2开始请求资源1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resources1){
System.out.println("线程对象2对资源1上锁占用资源1");
}
}
}
}).start();
}
}
4-线程池的创建
/**
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建和销毁线程对象的操作,无需反复创建线程而消耗过多资源。
合理利用线程池能够带来三个好处
1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.提高响应速度
3.提高线程的可管理性(线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机)
线程池的核心思想:线程复用,同一个线程可以被重复使用线程池启动后是不会死亡的,因为后续还要重复使用的。void shutdown():会等全部线程执行完毕才关闭。比较友好!
List<Runnable> shutdownNow():立即关闭,不管是否执行完毕
*/
public class ThreadPoolsDemo02 {
public static void main(String[] args) {
// 1.创建一个线程池 : 线程池固定放置三个线程
ExecutorService pools = Executors.newFixedThreadPool(3);
// 2.给线程池提交任务,提交任务的时候会自动创建线程对象了。
Runnable target = new MyRunnable();
pools.submit(target); // 这里提交任务会自动创建线程对象,并自动启动!
pools.submit(target); // 这里提交任务会自动创建线程对象,并自动启动!
pools.submit(target); // 这里提交任务会自动创建线程对象,并自动启动!
pools.submit(target); // 这里不会再创建线程了,因为线程池已经满了,这里会复用之前的线程!
// pools.shutdown();
// pools.shutdownNow();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0 ; i < 3 ; i++ ){
System.out.println(Thread.currentThread().getName()+" => " +i);
}
}
}