1.线程生命周期
-
新建:线程被new出来。
-
就绪:线程具有执行的资格,即线程调用了start(),没有执行的权利,就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行。
-
运行:具备执行的资格和执行的权利,当就绪的线程被调用并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能。
-
阻塞:没有执行的资格和执行的权利,在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后,线程就处于阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll方法。唤醒的线程不会立刻执行run方法,它们需要再次等待CPU分配资源进入运行状态。
-
销毁/死亡:线程释放资源,如果线程正常执行完毕后或线程被提前强制性的终止或者出现异常导致结束,那么线程就要被销毁,释放资源 。
2.synchronized
使用多线程提高了效率,但是也带来一个问题,当多个线程共享资源的时候,会出现线程不安全问题。比如几个窗口同时卖100张电影票,这100张电影票是共享的,会造成两个窗口同时读取到同一张票的可能。
synchronized关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
存在线程安全问题的写法
// 存在线程安全问题的写法
public class SellTicket extends Thread {
// 使用static保存100张票
private static int tickets = 100;
@Override
public void run() {
while (true) {
if(tickets <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第几" + tickets-- + "号票");
try {
Thread.sleep(100); // 模拟延时,买票是需要时间的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread thread1 = new SellTicket();
Thread thread2 = new SellTicket();
// 同时开启两个窗口买票,发现结果有重复的票号
thread1.start();
thread2.start();
}
}
使用synchronized修饰方法体
public class SellTicket extends Thread {
private static int tickets = 100;
// 创建锁
private static Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object) {
if(tickets <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第几" + tickets-- + "号票");
try {
Thread.sleep(100); // 模拟延时,买票是需要时间的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread thread1 = new SellTicket();
Thread thread2 = new SellTicket();
thread1.start();
thread2.start();
}
}
使用synchronized修饰方法
public class SellTicket extends Thread {
private static int tickets = 100;
@Override
public void run() {
while (true) {
if(sell()) {
break;
}
}
}
public synchronized static boolean sell() {
boolean sign = false;
if(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第几" + tickets-- + "号票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
sign = true;
}
return sign;
}
public static void main(String[] args) {
Thread thread1 = new SellTicket();
Thread thread2 = new SellTicket();
thread1.start();
thread2.start();
}
}
3.死锁
定义
当两个或两个以上的线程因竞争相同资源而处于无限期的等待,这样就导致了多个线程的阻塞,出现程序无法正常运行和终止的情况。
假设场景:有一个羽毛球场和一个网球场,小明占用了羽毛球场,小红占用了网球场。小明在打羽毛球的过程中就想如果网球场空闲了,他就去打网球;同时小红也在想,如果羽毛球场空闲了,她就去打羽毛球。但是他们去另一个球场的前提是另一个球场空闲了出来,这时候他们保持着占用现有资源,然后又想获取对象的占用资源,这时候就造成死锁了。
public static void main(String[] args) {
String str1 = "羽毛球场";
String str2 = "网球场";
Thread thread1 = new Thread(){
@Override
public void run() {
synchronized (str1) {
System.out.println("小明占用了" + str1);
// 小明占用羽毛球场的同时,还想去尝试获取网球场
synchronized (str2) {
System.out.println("小明占用了" + str2);
}
}
}
};
Thread thread2 = new Thread(){
@Override
public void run() {
synchronized (str2) {
System.out.println("小红占用了" + str2);
// 小红占用网球场的同时,还想去尝试获取羽毛球场
synchronized (str1) {
System.out.println("小红占用了" + str1);
}
}
}
};
thread1.start();
thread2.start();
}
运行结果:程序一直处于运行状态,进入了死锁,卡住了。
产生死锁的必要条件:
-
互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用。即一个资源每次只能被一个进程使用。
-
不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
-
请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
-
循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
修复死锁:
-
预防死锁:通过设置某些限制条件,去破坏死锁的四个必要条件中的一个或者几个,来进行预防。
-
避免多锁:尽量避免使用多个锁,并且只有需要时才持有锁。否则嵌套的synchronized非常容易出现问题。
-
设计锁的顺序:如果能确保所有的线程都是按照相同的顺序获取锁,那就不会出现锁问题。
以上就是本文内容!