Java线程学习之线程常见问题
一、线程安全问题
当多个线程同时运行时,极有可能会产生线程安全问题。
例如:三个线程同时卖10张票
public class TestThread {
public static void main(String[] args) {
//三个线程同时卖票
Runnable ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
//初始票数
private int count = 10;
@Override
public void run() {
while (count > 0){
//卖票
System.out.println("开始准备卖票");
//睡眠1秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count --;
System.out.println("出票成功" + "余票为:" + count);
}
}
}
}
执行结果如下所示,票数出现了负数,产生了线程安全问题
解决线程安全的方式
-
使用synchronized关键字的同步代码块方式
public class TestThread { public static void main(String[] args) { //三个线程同时卖票 Runnable ticket = new Ticket(); new Thread(ticket).start(); new Thread(ticket).start(); new Thread(ticket).start(); } static class Ticket implements Runnable{ //初始票数 private int count = 10; Object o = new Object(); @Override public void run() { while (true){ synchronized (o){ if(count > 0){ //卖票 System.out.println("开始准备卖票"); //睡眠1秒 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } count --; System.out.println(Thread.currentThread().getName()+"出票成功" + "余票为:" + count); }else { break; } } } } }
执行结果如下:
-
使用synchronized关键字的同步方法方式
public class TestThread { public static void main(String[] args) { //三个线程同时卖票 Runnable ticket = new Ticket(); new Thread(ticket).start(); new Thread(ticket).start(); new Thread(ticket).start(); } static class Ticket implements Runnable{ //初始票数 private int count = 10; @Override public void run() { while (true){ boolean flag = sale(); if(!flag){ break; } } } public synchronized boolean sale(){ if(count > 0){ //卖票 System.out.println("开始准备卖票"); //睡眠1秒 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } count --; System.out.println(Thread.currentThread().getName()+"出票成功," + "余票为:" + count); return true; } return false; } }
执行结果如下:
-
使用显示锁Lock的方式
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) {
//三个线程同时卖票
Runnable ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
//初始票数
private int count = 10;
//显示锁
Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
//上锁
l.lock();
if(count > 0){
//卖票
System.out.println("开始准备卖票");
//睡眠1秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count --;
System.out.println(Thread.currentThread().getName()+"出票成功," + "余票为:" + count);
}else {
break;
}
//释放锁
l.unlock();
}
}
public synchronized boolean sale(){
return false;
}
}
}
执行结果如下:
二、线程死锁问题
线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
产生线程死锁的条件
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免线程死锁
- 破坏互斥条件 :这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
- 破坏请求与保持条件 :一次性申请所有的资源。
- 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。