1 /** 2 * 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式 3 * 4 * 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题 5 * 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。 6 * 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他 7 * 线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。 8 * 9 * 10 * 4.在Java中,我们通过同步机制,来解决线程的安全问题。 11 * 12 * 方式一:同步代码块 13 * 14 * synchronized(同步监视器){ 15 * //需要被同步的代码 16 * 17 * } 18 * 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。 19 * 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。 20 * 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。 21 * 要求:多个线程必须要共用同一把锁。 22 * 23 * 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。 24 * 方式二:同步方法。 25 * 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。 26 * 27 * 28 * 5.同步的方式,解决了线程的安全问题。---好处 29 * 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 ---局限性 30 * 31 */ 32 class Window1 implements Runnable{ 33 34 private int ticket = 100; 35 // Object obj = new Object(); 36 // Dog dog = new Dog(); 37 @Override 38 public void run() { 39 // Object obj = new Object(); 40 while(true){ 41 synchronized (this){//此时的this:唯一的Window1的对象,若使用继承的方式创建线程则不能用this //方式二:synchronized (dog) { 42 43 if (ticket > 0) { 44 45 try { 46 Thread.sleep(100); 47 } catch (InterruptedException e) { 48 e.printStackTrace(); 49 } 50 51 System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); 52 53 54 ticket--; 55 } else { 56 break; 57 } 58 } 59 } 60 } 61 } 62 63 public class WindowTest1 { 64 public static void main(String[] args) { 65 Window1 w = new Window1(); 66 67 Thread t1 = new Thread(w); 68 Thread t2 = new Thread(w); 69 Thread t3 = new Thread(w); 70 71 t1.setName("窗口1"); 72 t2.setName("窗口2"); 73 t3.setName("窗口3"); 74 75 t1.start(); 76 t2.start(); 77 t3.start(); 78 } 79 }
1 package com.atguigu.java; 2 3 /** 4 * 使用同步方法解决实现Runnable接口的线程安全问题 5 * 6 * 7 * 关于同步方法的总结: 8 * 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。 9 * 2. 非静态的同步方法,同步监视器是:this 10 * 静态的同步方法,同步监视器是:当前类本身 11 * 12 */ 13 14 15 class Window3 implements Runnable { 16 17 private int ticket = 100; 18 19 @Override 20 public void run() { 21 while (true) { 22 23 show(); 24 } 25 } 26 27 private synchronized void show(){//同步监视器:this 28 //synchronized (this){ 29 30 if (ticket > 0) { 31 32 try { 33 Thread.sleep(100); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 38 System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); 39 40 ticket--; 41 } 42 //} 43 } 44 } 45 46 47 public class WindowTest3 { 48 public static void main(String[] args) { 49 Window3 w = new Window3(); 50 51 Thread t1 = new Thread(w); 52 Thread t2 = new Thread(w); 53 Thread t3 = new Thread(w); 54 55 t1.setName("窗口1"); 56 t2.setName("窗口2"); 57 t3.setName("窗口3"); 58 59 t1.start(); 60 t2.start(); 61 t3.start(); 62 } 63 64 }
1 /** 2 * 使用同步方法处理继承Thread类的方式中的线程安全问题 3 */ 4 class Window4 extends Thread { 5 6 7 private static int ticket = 100; 8 9 @Override 10 public void run() { 11 12 while (true) { 13 14 show(); 15 } 16 17 } 18 private static synchronized void show(){//同步监视器:Window4.class 19 //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的 20 if (ticket > 0) { 21 22 try { 23 Thread.sleep(100); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 28 System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); 29 ticket--; 30 } 31 } 32 } 33 34 35 public class WindowTest4 { 36 public static void main(String[] args) { 37 Window4 t1 = new Window4(); 38 Window4 t2 = new Window4(); 39 Window4 t3 = new Window4(); 40 41 42 t1.setName("窗口1"); 43 t2.setName("窗口2"); 44 t3.setName("窗口3"); 45 46 t1.start(); 47 t2.start(); 48 t3.start(); 49 50 } 51 }
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,可以指定为this或类名.class
注意:
1. 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全 2. 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)