经典案例卖车票
案例一
继承Thread
import java.util.ArrayList;
import java.util.List;
public class Test2 {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
SellTicketThread threadi = new SellTicketThread();
threadi.setName("线程"+i);
threadi.start();
}
}
}
class SellTicketThread extends Thread{
//static共享数据,也可以实现Runnable接口不用加
private static int tickets = 100;
// 卖票
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
继承Thread方式出现的问题:
问题一:
每个窗口不是共享100张票,而是每人都有100张票,共300张
解决办法:
使用static关键修饰共享数据
问题二: 卖出了同票
问题三: 卖出了负票
什么时候会出现线程安全问题?
1.存在多线程环境
2.存在多线程环境下操作共享数据
3.存在多条原子性语句操作共享数据
4.操作数据必须有对数据的修改而不是读取
如何来解决线程安全问题?
可以使用如下三种方法将多条操作共享数据的原子性语句包裹起来
实现方式:
1.同步代码块
2.同步方法
3.同步锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
public static void main(String[] args) {
SellTicketThread st = new SellTicketThread();
for (int i = 0; i < 4; i++) {
Thread t1i = new Thread(st);
t1i.setName("窗口"+i);
t1i.start();
}
}
}
//class SellTicketThread extends Thread {
//
// private static int tickets = 100;
//
// @Override
// public void run() {
// while (true) {
// if (tickets > 0) {
// // 使用线程休眠模拟网络延迟
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票" );
// } else {
// break;
// }
// }
// }
//}
class SellTicketThread implements Runnable {
private int tickets = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//方式一:同步代码块
// 锁对象是任意对象,但是一定要保证多个线程共享同一把锁
synchronized (MyLock.LOCK) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票" );
} else {
break;
}
}
// 同步方法
// if (sellTicket()) {
// break;
// }
// Lock锁方式
// 加锁
/*lock.lock();
if (tickets > 0) {
// 使用线程休眠模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票" );
} else {
break;
}
// 释放锁
lock.unlock();*/
}
}
/*public synchronized boolean sellTicket() {
if (tickets > 0) {
// 使用线程休眠模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票" );
} else {
return true;
}
return false;
}*/
}
class MyLock {
public static final Object LOCK = new Object();
}
synchronized与Lock的区别
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
死锁产生条件
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
案例代码死锁
public class Test3 {
public static void main(String[] args) {
DieLock t1 = new DieLock(true);
DieLock t2 = new DieLock(false);
t1.start();
t2.start();
}
}
class DieLock extends Thread {
private boolean flag;
public DieLock() {}
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.LOCKA) {
System.out.println("if语句中 LOCKA锁");
synchronized (MyLock.LOCKB) {
System.out.println("if语句中 LOCKB锁");
}
}
} else {
synchronized (MyLock.LOCKB) {
System.out.println("else语句中 LOCKB锁");
synchronized (MyLock.LOCKA) {
System.out.println("else语句中 LOCKA锁");
}
}
}
}
}
class MyLock {
public static final Object LOCKA = new Object();
public static final Object LOCKB = new Object();
}
死锁预防
破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
破坏”请求与保持条件“:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。
破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。