一、线程同步机制synchronized的理解
二、synchronized的具体使用
下面可以通过同步机制,解决多线程卖票,出现的超卖问题,代码如下
public class SellTicket { public static void main(String[] args) { // SellTicket01 sellTicket01 = new SellTicket01(); // SellTicket01 sellTicket02 = new SellTicket01(); // SellTicket01 sellTicket03 = new SellTicket01(); // // //这里会出现超卖现象 // sellTicket01.start();//启动售票线程 // sellTicket02.start();//启动售票线程 // sellTicket03.start();//启动售票线程 /* 输出结果: 窗口 Thread-0 售出一张票剩余票数=2 窗口 Thread-1 售出一张票剩余票数=-1 售票结束 窗口 Thread-0 售出一张票剩余票数=-2 售票结束 窗口 Thread-2 售出一张票剩余票数=0 售票结束 */ // System.out.println("----使用实现接口的方式来售票----"); // SellTicket02 sellTicket02 = new SellTicket02(); // new Thread(sellTicket02).start();//第1个线程-窗口 // new Thread(sellTicket02).start();//第2个线程-窗口 // new Thread(sellTicket02).start();//第3个线程-窗口 /* 输出结果 窗口 Thread-0 售出一张票剩余票数=2 窗口 Thread-2 售出一张票剩余票数=1 窗口 Thread-2 售出一张票剩余票数=0 售票结束 窗口 Thread-0 售出一张票剩余票数=-1 售票结束 窗口 Thread-1 售出一张票剩余票数=-2 售票结束 */ System.out.println("----使用线程同步的方式来解决超卖票的情况----"); SellTicket03 sellTicket03 = new SellTicket03(); new Thread(sellTicket03).start();//第1个线程-窗口 new Thread(sellTicket03).start();//第2个线程-窗口 new Thread(sellTicket03).start();//第3个线程-窗口 /* 窗口 Thread-0 售出一张票剩余票数=6 窗口 Thread-0 售出一张票剩余票数=5 窗口 Thread-0 售出一张票剩余票数=4 窗口 Thread-0 售出一张票剩余票数=3 窗口 Thread-0 售出一张票剩余票数=2 窗口 Thread-0 售出一张票剩余票数=1 窗口 Thread-2 售出一张票剩余票数=0 售票结束... 售票结束... 售票结束... */ } } //使用Thread方式 class SellTicket01 extends Thread { private static int ticketNum = 100;//让多个线程共享 @Override public void run() { while (true) { //在判断这个条件的时候,三个线程会同时进来 当ticketNum等于1时, //三个线程都进来了,会出现超卖的情况 if (ticketNum <= 0) { System.out.println("售票结束"); break; } //休眠50毫秒,模拟 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + "剩余票数=" + (--ticketNum)); } } } //实现接口方式 class SellTicket02 implements Runnable { private int ticketNum = 100; @Override public void run() { while (true) { if (ticketNum <= 0) { System.out.println("售票结束"); break; } //休眠50毫秒,模拟 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + "剩余票数=" + (--ticketNum)); } } } //实现接口方式,使用synchronized实现线程同步 class SellTicket03 implements Runnable { private int ticketNum = 100; private boolean loop = true; public synchronized void sell() {//同步方法,在同一时刻,只能有一个线程来执行sell方法 if (ticketNum <= 0) { System.out.println("售票结束..."); loop = false; return; } //休眠50毫秒,模拟 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + "剩余票数=" + (--ticketNum)); } @Override public void run() { while (loop) { sell();//sell方法是一个同步方法 } } }
分析同步原理
三、互斥锁的介绍
下面演示在代码块中加锁,和方法上加锁,还是以上面的多线程卖票为例
//实现接口方式,使用synchronized实现线程同步 class SellTicket03 implements Runnable { private int ticketNum = 100; private boolean loop = true; Object object = new Object();//也可以用同一个对象,比如object,因为是三个线程共享一个object对象,满足三个线程共享一个对象 //同步方法(静态的)的锁为当前类本身 //1.public synchronized static void m1(){}锁 是加在SellTicket03.class //2.如果在静态方法中,实现一个同步代码块 /* synchronized (SellTicket03.class) { System.out.println("m2"); } */ public synchronized static void m1() { } public static void m2() { synchronized (SellTicket03.class) { System.out.println("m2"); } } //1. public synchronized void sell(){} 就是一个同步方法 //2.也可以在代码块上写synchronized ,同步代码块,互斥锁还是在this对象 public /*synchronized*/ void sell() {//同步方法,在同一时刻,只能有一个线程来执行sell方法 synchronized (/*this*/object) { if (ticketNum <= 0) { System.out.println("售票结束..."); loop = false; return; } //休眠50毫秒,模拟 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + "剩余票数=" + (--ticketNum)); } } @Override public void run() { while (loop) { sell();//sell方法是一个同步方法 } } }
互斥锁的注意细节如下
四、线程的死锁
public class DeadLock_ { public static void main(String[] args) { //模拟死锁现象 DeadLockDemo A = new DeadLockDemo(true); A.setName("A线程"); DeadLockDemo B = new DeadLockDemo(false); B.setName("B线程"); A.start(); B.start(); } } //线程 class DeadLockDemo extends Thread { static Object o1 = new Object();//保证多线程,共享一个对象,这里使用static static Object o2 = new Object(); boolean flag; public DeadLockDemo(boolean flag) { this.flag = flag; } @Override public void run() { //下面业务逻辑分析 //1.如果flag为true,线程A就会先得到/持有 o1 对象锁,然后尝试去获取o2对象锁 //2.如果线程A 得不到o2对象锁,就会Blocked //3.如果flag为false,线程B就会先得到/持有 o2 对象锁,然后尝试去获取o1对象锁 //2.如果线程B 得不到o1对象锁,就会Blocked if (flag) { synchronized (o1) {//对象互斥锁,下面是同步代码 System.out.println(Thread.currentThread().getName() + " 进入1"); synchronized (o2) {//这里获得li对象的监视权 System.out.println(Thread.currentThread().getName() + "进入2"); } } } else { synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入3"); synchronized (o1) {//这里获得li对象的监视权 System.out.println(Thread.currentThread().getName() + "进入4"); } } } } }
输出结果
B线程 进入3 A线程 进入1 之后就卡在这里了,写代码时一定要避免
下面操作会释放锁
下面操作不会释放锁
线程相关的练习题如下
代码如下
public class HomeWork01 { public static void main(String[] args) { A a = new A(); a.start(); B b = new B(a); b.start(); } } class A extends Thread { private boolean loop = true; public void setLoop(boolean loop) { this.loop = loop; } @Override public void run() { while (loop) { System.out.println((int) (Math.random() * 100 + 1)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("a线程退出..."); } } class B extends Thread { private A a; public B(A a) {//构造器中,传入A类对象 this.a = a; } @Override public void run() { while (true) { //接收到用户的输入 System.out.println("请输入命令"); Scanner scanner = new Scanner(System.in); char c = scanner.next().toUpperCase().charAt(0); if (c == 'Q') { //以通知的方式结束A线程 a.setLoop(false); break; } } System.out.println("b线程退出..."); } }
输出结果如下
85 请输入命令 8 41 79 81 75 41 29 Q b线程退出... a线程退出...
练习题二
代码如下
public class HomeWork02 { public static void main(String[] args) { Card card = new Card(); new Thread(card).start(); new Thread(card).start(); } } //编程取款的线程 //1.因为这里涉及到多个线程共享线程资源,所以我们使用实现Runnable方式 class Card implements Runnable { private boolean loop = true; private int balance = 10000; @Override public void run() { while (loop) { //解读: //1.这里使用synchronized实现了线程同步 //2.当多个线程执行到这里时,就会去争夺this对象锁 //3.哪个对象争夺到(获取)this对象锁,就执行synchronized代码块,执行完成后,会释放this对象锁 //4.争夺不到this对象锁,就blocked,准备继续争夺 //5.this对象锁 是非公平锁 synchronized (this) { if (balance < 1000) { System.out.println("余额不足.."); loop = false; return; } System.out.println(Thread.currentThread().getName() + " 取出1000 剩余余额为:" + (balance -= 1000)); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出结果如下
Thread-0 取出1000 剩余余额为:9000 Thread-1 取出1000 剩余余额为:8000 Thread-1 取出1000 剩余余额为:7000 Thread-0 取出1000 剩余余额为:6000 Thread-1 取出1000 剩余余额为:5000 Thread-0 取出1000 剩余余额为:4000 Thread-1 取出1000 剩余余额为:3000 Thread-0 取出1000 剩余余额为:2000 Thread-1 取出1000 剩余余额为:1000 Thread-0 取出1000 剩余余额为:0 余额不足.. 余额不足..