线程的生命周期
- 新建:线程实例的创建,即new Thread();
- 就绪:执行.start()方法之后
- 运行:run方法的代码开始执行
- 阻塞:run方法的代码暂停执行,卡住run方法,类似堵车。
- 死亡:自然死亡:程序执行完毕或者程序发生了异常导致程序结束
- 强制死亡:执行.stop()方法、断电、杀掉进程。
从下图可以明显地看出线程的生命周期:
线程同步
有如下场景:
同一个账户,可以通过支付宝转账,也可以通过微信转账,两个手机,一个手机打开支付宝,一个手机打开微信,假设账户上有3000,支付宝和微信同时提款2000,那么账户上成了-1000,这种情况是不可能出现的,出现就有问题了。
public class Test2 {
public static void main(String[] args) {
Account a = new Account();
Account a1 = new Account();
User user_weixin = new User(a, 2000);
User user_zhifubao = new User(a, 2000);
Thread weixin = new Thread(user_weixin, "微信");
Thread zhifubao = new Thread(user_zhifubao, "支付宝");
weixin.start();
zhifubao.start();
}
}
class Account {
public static int money = 3000;
/**
* 多线程调用这个方法就有问题,线程共享资源时,一个线程在执行这个方法没有完毕时,另一个线程又开始执行这个方法,导致共享数据的错误
* 解决思路:先让一个线程整体执行完这个方法,再让另一个线程执行。
* 通过synchronized同步锁完成
* 可以直接在方法上加上synchronized关键字
* 在普通方法上加同步锁,锁的是某一个对象,不是某一个方法,不同的对象就是不同的锁
* @param m
*/
public synchronized void Withdraw(int m) {
String name = Thread.currentThread().getName();
if (m > money) {
System.out.println(name + "操作:余额不足!" + money);
} else {
System.out.println(name + "操作:账户原有金额:" + money);
System.out.println(name + "操作:取款金额:" + m);
System.out.println(name + "操作:取款操作:原金额" + money + " - " + "取款金额" + m);
money = money - m;
System.out.println(name + "操作:余额:" + money);
}
}
/**
* 静态方法加synchronized,对于所有对象使用同一把锁。
* @param m
*/
public static synchronized void Withdraw1(int m) {
String name = Thread.currentThread().getName();
if (m > money) {
System.out.println(name + "操作:余额不足!" + money);
} else {
System.out.println(name + "操作:账户原有金额:" + money);
System.out.println(name + "操作:取款金额:" + m);
System.out.println(name + "操作:取款操作:原金额" + money + " - " + "取款金额" + m);
money = money - m;
System.out.println(name + "操作:余额:" + money);
}
}
/**
* 对代码块加入同步锁
* 想要根据不同对象有不同的锁,需要给synchronized (a)传递不同对象
* @param m
*/
public void Withdraw2(int m) {
//表示当前对象的代码块被加了synchronized同步锁
synchronized (this) {
String name = Thread.currentThread().getName();
if (m > money) {
System.out.println(name + "操作:余额不足!" + money);
} else {
System.out.println(name + "操作:账户原有金额:" + money);
System.out.println(name + "操作:取款金额:" + m);
System.out.println(name + "操作:取款操作:原金额" + money + " - " + "取款金额" + m);
money = money - m;
System.out.println(name + "操作:余额:" + money);
}
}
}
}
class User implements Runnable {
Account account;
int money;
public User(Account account, int money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
// if (Thread.currentThread().getName().equals("微信")) {
// account.Withdraw(money);
// } else {
// account.Withdraw1(money);
// }
//Account.Withdraw1(money);
account.Withdraw2(money);
}
}
线程死锁
**死锁是指多个进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象(互相挂起等待),若无外力作用,它们都将无法推进下去。**此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
举一个生活中的简单例子:小明和小红都想买一块橡皮,这块橡皮价值一块钱,但是他们俩每个人都只有五毛钱,小明说:你把你的五毛钱给我,让我买橡皮。小红说:你把你的五毛钱给我,让我买橡皮。这样,两个人互相僵持着,谁也不愿意低头,谁都买不到橡皮。
死锁图示:
线程A里有方法A1;线程B里有方法B1,现在线程A对B1加锁操作,操作没有完成又想操作A1,线程B对A1加锁操作,操作没有完成又想操作B1,线程A让线程B释放对A1的锁,而线程B让线程A释放对B1的锁,两者僵持不下,就造成死锁。
1.死锁产生的主要原因:
- 系统的资源不足。
- 进程(线程)推进的顺序不对。
- 资源的分配不当。
当系统的资源很充沛的时候,每个进程都可以申请到想要的资源,那么出现死锁的概率就很低,线程的调度顺序和速度不同,也会导致死锁问题。
2.死锁产生的四个必要条件:
- 互斥条件:进程(线程)申请的资源在一段时间中只能被一个进程(线程)使用。
- 请求与等待条件:进程(线程)已经拥有了一个资源,但是又申请新的资源,拥有的资源保持不变 。
- 不可剥夺条件:在一个进程(线程)没有用完,主动释放资源的时候,不能被抢占。
- 循环等待条件:多个进程(线程)之间存在资源循环链。
3.处理死锁的方法:
-
预防死锁:破坏死锁产生的四个条件之一,注意,互斥条件不能破坏。
-
避免死锁:
1、让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实。
2、设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量。
3、既然死锁的产生是两个线程无限等待对方持有的锁,那么只要等待时间有个上限不就好了。当然synchronized不具备这个功能,但是我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后便会返回一个失败信息。
-
检查死锁:利用专门的死锁机构检查死锁的发生,然后采取相应的方法。
-
解除死锁:发生死锁时候,采取合理的方法解决死锁。一般是强行剥夺资源。
4.如何打破四个产生条件
- 打破互斥条件:改造独占性资源为虚拟大资源,但是大部分资源无法改造,因此不建议使用这个方法。
- 打破请求与保持条件:在进程(线程)运行之前,就把需要申请的资源一次性申请到位,满足则运行,不满足就等待,这样就不会造成在占有资源的情况下,还要申请新资源。
- 打破不可剥夺条件:在占有资源并且还想要申请新资源的时候,归还已经占有的资源。
- 打破循环等待条件:实现资源的有序分配,即对所有的设备进行分类编号,只能以升序的方式来申请资源。
比如说进程P1,使用资源的顺序是R1,R2,进程P2,使用资源的顺序是R2,R1,如果采取动态分配的方式,就很有可能造成死锁。我们对设备进行分类编号,那么P1,P2只能以R1,R2的顺序来申请资源。就可以打破环形回路,避免死锁。
银行家算法
在避免死锁的方法中最有名的就是银行家算法,它是DIJKstra E.W于1968年提出来的。
为什么叫做银行家算法呢,是因为这有点向银行的“借贷”服务,假如银行只有有限多的资金供给客户进行贷款服务,那么为了保证银行能有足够的资金运转,它在借钱之前要审核客户是否有能够在指定时间内偿还贷款的能力。
在研究我们的操作系统的资源分配策略时,也会出现类似的问题,我们系统中的有限资源要分配给各种进程,那么就要事先考察此进程是否有在指定期限内归还资源的能力。必须要保证它能在有限的时间内进行归还,拱其他进程使用。
大致实现方法:
- 当一个进程对资源的最大需求量不超过系统中的资源数时可以接纳该进程。
- 进程可以分期请求资源,当请求的总数不能超过最大需求量。
- 当系统现有的资源不能满足进程尚需资源数时,对进程的请求可以推迟分配,但总能使进程在有限的时间里得到资源。
- 当系统现有的资源能满足进程尚需资源数时,必须测试系统现存的资源能否满足该进程尚需的最大资源数,若能满足则按当前的申请量分配资源,否则也要推迟分配。
银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
线程通信
wait()、notify()和notifyAll()
对上面的代码加入新的需求:
如果是微信操作的,先不执行,等支付宝操作,当支付宝操作完,微信再继续操作。
public class Test2 {
public static void main(String[] args) {
Account a = new Account();
User user_weixin = new User(a, 2000);
User user_zhifubao = new User(a, 2000);
Thread weixin = new Thread(user_weixin, "微信");
Thread zhifubao = new Thread(user_zhifubao, "支付宝");
weixin.start();
zhifubao.start();
}
}
class Account {
public static int money = 3000;
public void Withdraw3(int m, Account account) {
//表示当前对象的代码块被加了synchronized同步锁
synchronized (account) {
String name = Thread.currentThread().getName();
if (name.equals("微信")) {
try {
account.wait(); //当前线程进入等待的阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (m > money) {
System.out.println(name + "操作:余额不足!" + money);
} else {
System.out.println(name + "操作:账户原有金额:" + money);
System.out.println(name + "操作:取款金额:" + m);
System.out.println(name + "操作:取款操作:原金额" + money + " - " + "取款金额" + m);
money = money - m;
System.out.println(name + "操作:余额:" + money);
}
if (name.equals("支付宝")) {
account.notify(); //唤醒当前优先级最高的线程,进入就绪状态
account.notifyAll(); //唤醒当前所有的线程,进入就绪状态
}
}
}
}
class User implements Runnable {
Account account;
int money;
public User(Account account, int money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
account.Withdraw3(money, account);
}
}
控制台输出:
支付宝操作:账户原有金额:3000
支付宝操作:取款金额:2000
支付宝操作:取款操作:原金额3000 - 取款金额2000
支付宝操作:余额:1000
微信操作:余额不足!1000
可以看出,先执行支付宝线程,等支付宝线程操作完成后,微信线程再执行。
注意:wait()、notify()和notifyAll()这三个方法只能用在有同步锁的方法或代码块中。
生产者与消费者
/**
* 生产者与消费者
*/
public class Test3 {
public static void main(String[] args) {
Clerk c = new Clerk();
//生产者
new Thread(new Runnable() {
@Override
public void run() {
synchronized (c) {
while (true) { //无限循环代表无限生产
if (Clerk.productNum == 0) {
System.out.println("商品数为0,开始生产");
while (Clerk.productNum < 20) {
Clerk.productNum ++;
System.out.println("库存:" + Clerk.productNum);
}
System.out.println("商品数为:" + Clerk.productNum + ",生产结束");
c.notify(); //唤醒消费者
} else {
try {
c.wait(); //让生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}, "生产者").start();
//消费者
new Thread(new Runnable() {
@Override
public void run() {
synchronized (c) {
while (true) { //无限循环代表无限消费
if (Clerk.productNum == 20) {
System.out.println("商品数为20,开始消费");
while (Clerk.productNum > 0) {
Clerk.productNum --;
System.out.println("库存:" + Clerk.productNum);
}
System.out.println("商品数为:" + Clerk.productNum + ",消费结束");
c.notify(); //唤醒生产者
} else {
try {
c.wait(); //让消费者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}, "消费者").start();
}
}
class Clerk {
public static int productNum = 0;
}