一、线程安全
模拟4个窗口销售10张票,并使每次售票之后都休眠10毫秒
(如果不休眠,窗口1就把所有的票卖光了)
package cn.itcast.chapter10.example10;
/**
* 售票程序,通过实现 Runnable接口的方式来创建多线程
*/
public class Example10 {
public static void main(String[] args) {
TicketWindow task = new TicketWindow();// 创建线程的任务类对象
new Thread(task, "窗口1").start();// 创建线程并起名为窗口1, 开启线程
new Thread(task, "窗口2").start();// 创建线程并起名为窗口2, 开启线程
new Thread(task, "窗口3").start();// 创建线程并起名为窗口3, 开启线程
new Thread(task, "窗口4").start();// 创建线程并起名为窗口4, 开启线程
}
}
// 线程的任务类
class TicketWindow implements Runnable {
private int tickets = 10; // 10张票
@Override
public void run() {
while (tickets > 0) {
try {
Thread.sleep(10);// 线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---卖出的票"
+ tickets--);
}
}
}
窗口4---卖出的票10
窗口3---卖出的票9
窗口1---卖出的票8
窗口2---卖出的票7
窗口4---卖出的票6
窗口1---卖出的票5
窗口3---卖出的票6
窗口2---卖出的票4
窗口3---卖出的票3
窗口4---卖出的票1
窗口1---卖出的票3
窗口2---卖出的票2
出现重复售卖,线程安全问题
二、同步代码块
线程安全问题其实是由多个线程同时处理共享资源导致的,想要处理上例的线程安全问题,必须保证下面的处理共享资源的代码在任何时刻只能有一个线程访问,
while (tickets > 0) {
try {
Thread.sleep(10);// 线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---卖出的票"+ tickets--);
}
为了实现这种限制,java提供了同步机制,当多个线程使用同一个共享资源时,可以将处理共享资源的代码放在synchronized关键字来修饰的代码块中,即同步代码块中,
synchronized(lock){
共享资源代码块
}
lock是一个锁对象,他是同步代码块的关键
当某一个线程执行同步代码块时,其他线程将无法执行同步代码块,会发生阻塞,
当等到线程执行完同步代码块之后,所有的线程开始抢夺线程的执行权,
lock可以是任意对象
package cn.itcast.chapter10.example11;
/**
* 同步代码块
*/
public class Example11 {
public static void main(String[] args) {
TicketWindow task = new TicketWindow();// 创建线程的任务类对象
new Thread(task, "窗口1").start();// 创建线程并起名为窗口1, 开启线程
new Thread(task, "窗口2").start();// 创建线程并起名为窗口2, 开启线程
new Thread(task, "窗口3").start();// 创建线程并起名为窗口3, 开启线程
new Thread(task, "窗口4").start();// 创建线程并起名为窗口4, 开启线程
}
}
// 线程的任务类
class TicketWindow implements Runnable {
private int tickets = 10; // 10张票
Object lock = new Object();// 定义任意一个对象,用作同步代码块的锁
@Override
public void run() {
while (true) {
synchronized (lock) { // 定义同步代码块
try {
Thread.sleep(500);// 线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets > 0) {
System.out.println(Thread.currentThread().getName()
+ "---卖出的票" + tickets--);
} else {
break;
}
}
}
}
}
窗口1---卖出的票10
窗口1---卖出的票9
窗口1---卖出的票8
窗口1---卖出的票7
窗口1---卖出的票6
窗口1---卖出的票5
窗口3---卖出的票4
窗口3---卖出的票3
窗口4---卖出的票2
窗口2---卖出的票1
三、同步方法
package cn.itcast.chapter10.example12;
/**
* 同步方法
*/
public class Example12 {
public static void main(String[] args) {
TicketWindow task = new TicketWindow();// 创建线程的任务类对象
new Thread(task, "窗口1").start();// 创建线程并起名为窗口1, 开启线程
new Thread(task, "窗口2").start();// 创建线程并起名为窗口2, 开启线程
new Thread(task, "窗口3").start();// 创建线程并起名为窗口3, 开启线程
new Thread(task, "窗口4").start();// 创建线程并起名为窗口4, 开启线程
}
}
// 线程的任务类
class TicketWindow implements Runnable {
private int tickets = 10; // 10张票
@Override
public void run() {
while (true) {
sendTicket();
}
}
// 定义售票的方法
public synchronized void sendTicket() {
try {
Thread.sleep(500);// 线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "---卖出的票"
+ tickets--);
} else {
System.exit(0);
}
}
}
窗口1---卖出的票10
窗口1---卖出的票9
窗口1---卖出的票8
窗口1---卖出的票7
窗口2---卖出的票6
窗口4---卖出的票5
窗口3---卖出的票4
窗口3---卖出的票3
窗口3---卖出的票2
窗口4---卖出的票1
四、锁对象
1、同步代码块:自己定义的任意类型的对象
2、同步方法:当前调用该方法的对象
3、静态同步方法:
静态方法不需要创建对象,可以直接调用 “ 类名.方法名() ”
它的锁是该方法所在类的的class对象,该对象在创建该类时自动创建,该对象就可以直接用 类名.class的方式获取
注:同步代码块和同步方法解决多线程也有弊端,线程在执行同步代码块时,每次都会判断锁的状态,非常消耗资源,效率较低
五、死锁问题
package cn.itcast.chapter10.example13;
/**
* 死锁
*/
public class Example13 {
public static void main(String[] args) {
// 创建两个DeadLockThread对象
DeadLockThread d1 = new DeadLockThread(true);
DeadLockThread d2 = new DeadLockThread(false);
// 创建并开启两个线程
new Thread(d1, "Chinese").start(); // 创建开启线程Chinese
new Thread(d2, "American").start(); // 创建开启线程American
}
}
class DeadLockThread implements Runnable {
//必须用全局变量static , 否则每个线程使用的是自己的锁,会一直无限循环
static Object chopsticks = new Object(); // 定义Object类型的chopsticks锁对象
static Object knifeAndFork = new Object(); // 定义Object类型的knifeAndFork锁对象
private boolean flag; // 定义boolean类型的变量flag
DeadLockThread(boolean flag) { // 定义有参的构造方法
this.flag = flag;
}
public void run() {
if (flag) {
while (true) {
synchronized (chopsticks) { // chopsticks锁对象上的同步代码块
System.out.println(Thread.currentThread().getName() + "---if---chopsticks");
synchronized (knifeAndFork) { // knifeAndFork锁对象上的同步代码块
System.out.println(Thread.currentThread().getName() + "---if---knifeAndFork");
}
}
}
} else {
while (true) {
synchronized (knifeAndFork) { // knifeAndFork锁对象上的同步代码块
System.out.println(Thread.currentThread().getName() + "---else---knifeAndFork");
synchronized (chopsticks) { // chopsticks锁对象上的同步代码块
System.out.println(Thread.currentThread().getName() + "---else---chopsticks");
}
}
}
}
}
}
Chinese---if---chopsticks
Chinese---if---knifeAndFork
Chinese---if---chopsticks
American---else---knifeAndFork