未来可期  。一位正行走在编程世界中的小白 ,希望能遇到更多正在努力中的小伙伴。
线程同步机制 一、背景 我以生活中的例子来打开这个问题,例如:我们做火车买票为例子。 创建个窗口,总票数为 100 张,使用实现 Runable 接口的方式
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class  Window1  implements  Runnable {         private int  ticket = 100 ;     @Override     public void  run ()  {         while (true ){             if (ticket > 0 ){                 System.out.println(Thread.currentThread().getName()+":卖票,票号为:" +ticket);                 ticket--;             }else {                 break ;             }         }     } } public class  WindowTest1  {     public static  void  main (String[] args)  {         Window1 w1 = new Window1();         Thread t1 = new Thread(w1);         Thread t2 = new Thread(w1);         Thread t3 = new Thread(w1);         t1.setName("窗口一" );         t2.setName("窗口二" );         t3.setName("窗口三" );         t1.start();         t2.start();         t3.start();     } } 
运行结果
在卖票的过程中,通过上面的代码运行的代码,在最后出现了重票、错票,从而出现了线程的安全问题。 出现线程安全的原因,就是当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,一起来操作车票。 出现了问题,总要有解决的办法,在 java 多线程就给我们提供了这个解决办法方法。 我先简单的描述一下解决问题的方法,当一个线程 a 在操作 ticket 的时候,其他线程不能参与进来。直到线程 a 操作完 ticket 时,其他线程才可以开始操作 ticket.这种情况即使线程 a 出现了阻塞,也不能被改变。 二、解决方法 方式一:同步代码块 1 2 3 4 语法: synchronized(同步监视器){ 	 } 
说明:
操作共享数据的代码,即为需要被同步的代码 –> 不能包含代码多了,也不能包含的代码少了。 共享数据,多个数据共同操作的变量,比如:ticket 就是共享数据。 同步监视器俗称 锁 。任何一个类的对象,都可以充当锁。 要求:多个线程必须要用同一把锁。 ① 解决继承 Thread 类线程安全问题 代码示例: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class  Window2  extends  Thread {	     private static  int  ticket = 100 ;     @Override     public void  run () {                               while (true ){                 synchronized(Window2.class){                     if (ticket > 0 ){                         try {                             Thread.sleep(100 );                         } catch (InterruptedException e) {                             e.printStackTrace();                         }                         System.out.println(getName()+":卖票,票号为:" +ticket);                         ticket--;                     }else {                         break ;                     }                 }             }     } } public class  Window2Test  {     public static  void  main (String[] args)  {         Window2 t1 = new Window2();         Window2 t2 = new Window2();         Window2 t3 = new Window2();         t1.setName("窗口一" );         t2.setName("窗口二" );         t3.setName("窗口三" );         t1.start();         t2.start();         t3.start();     } } 
运行结果:
② 解决实现 Runable 接口线程安全 代码示例: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class  Window1  implements  Runnable {         private int  ticket = 100 ;     @Override     public void  run ()  {         while (true ){                                       synchronized(this){                 if (ticket > 0 ){                     System.out.println(Thread.currentThread().getName()+":卖票,票号为:" +ticket);                     ticket--;                 }else {                     break ;                 }             }         }     } } public class  WindowTest1  {     public static  void  main (String[] args)  {         Window1 w1 = new Window1();         Thread t1 = new Thread(w1);         Thread t2 = new Thread(w1);         Thread t3 = new Thread(w1);         t1.setName("窗口一" );         t2.setName("窗口二" );         t3.setName("窗口三" );         t1.start();         t2.start();         t3.start();     } } 
运行结果:补充: 
在实现 Runable 接口创建多线程的方式中,我们可以考虑使用this 充当同步监视器。 在继承 Thread 类创建多线程的方式中,慎用 this 充当同步监视器,考虑使用当前类充当同步监视器。
方式二:同步方法 如果操作共享数据的代码完整的声明在一个方法中,我们不妨碍将此方法声明同步的
① 使用同步方法解决 Thread 类的线程安全问题 代码示例: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class  Window3  extends  Thread {    private static   int  ticket = 100 ;     @Override     public void  run () {       while (true ){           show();       }     }     private static  synchronized  void  show () {          if (ticket > 0 ){             try {                 Thread.sleep(100 );             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println(Thread.currentThread().getName()+":卖票,票号为:" +ticket);             ticket--;         }     } } public class  WindowTest3  {     public static  void  main (String[] args)  {         Window3 w1 = new Window3();         Window3 w2 = new Window3();         Window3 w3 = new Window3();         w1.setName("窗口一" );         w2.setName("窗口二" );         w3.setName("窗口三" );         w1.start();         w2.start();         w3.start();     } } 
运行结果:
② 使用同步方法解决实现 Runable 接口线程安全 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class  Window4  implements  Runnable {    private int  ticket = 100 ;     @Override     public void  run ()  {         while  (true ){             show();         }     }     private synchronized void  show () {          if (ticket > 0 ){             System.out.println(Thread.currentThread().getName()+":卖票,票号为:" +ticket);             ticket--;         }     } } public class  WindowTest4  {     public static  void  main (String[] args)  {         Window4 w = new Window4();         Thread t1 = new Thread(w);         Thread t2 = new Thread(w);         Thread t3 = new Thread(w);         t1.setName("窗口一" );         t2.setName("窗口二" );         t3.setName("窗口三" );         t1.start();         t2.start();         t3.start();     } } 
关于同步方法的总结: 
同步方法仍然涉及到同步监视器只是不需要我们显示的声明。 非静态的同步方法,同步监视器是:this  静态的同步方法,同步监视器是: 当前类本身 方式三:Lock 锁 – JDK5.0 新增 代码示例: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class  Window5  implements  Runnable {         ReentrantLock lock = new ReentrantLock();     private int  ticket = 100 ;     @Override     public void  run ()  {         while (true ){             try {                 lock.lock();                                  if (ticket > 0 ){                     try {                         Thread.sleep(100 );                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                     System.out.println(Thread.currentThread().getName()+":售票,票号为:" +ticket);                     ticket--;                 }else {                     break ;                 }             }  finally {                                  lock.unlock();             }         }     } } public class  LockTest  {     public static  void  main (String[] args)  {         Window5 w = new Window5();         Thread t1 = new Thread(w);         Thread t2 = new Thread(w);         Thread t3 = new Thread(w);         t1.setName("窗口1" );         t2.setName("窗口2" );         t3.setName("窗口3" );         t1.start();         t2.start();         t3.start();     } } 
运行结果:
总结 使用的优先顺序 
1 Lock --> 同步代码块(已经进入方法体,分配了相对应资源) --> 同步方法(在方法体之外) 
利弊 
同步的方式,解决了线程的安全问题。–> 好处 操作同步代码时,只能一个线程参与,其他线程等待。就相当于是一个的单线程的过程,效率低。 synchronzied 与 Lock 的异同 
synchronzied 机制在执行完相对应的同步代码块后,自动的释放同步监视器 Lock 需要手动的启动(lock()),同时结束时也需要手动的实现(unlock()); 死锁 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
说明: 
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续 我们使用同步时,要避免出现死锁。 代码示例: 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 class  A  {    public synchronized void  foo (B b)  {          System.out.println("当前线程名: "  + Thread.currentThread().getName()                 + " 进入了A实例的foo方法" );          System.out.println("当前线程名: "  + Thread.currentThread().getName()                 + " 企图调用B实例的last方法" );          b.last();     }     public synchronized void  last ()  {         System.out.println("进入了A类的last方法内部" );     } } class  B  {    public synchronized void  bar (A a)  {         System.out.println("当前线程名: "  + Thread.currentThread().getName()                 + " 进入了B实例的bar方法" );          System.out.println("当前线程名: "  + Thread.currentThread().getName()                 + " 企图调用A实例的last方法" );          a.last();     }     public synchronized void  last ()  {         System.out.println("进入了B类的last方法内部" );     } } public class  DeadLock  implements  Runnable  {     A a = new A();     B b = new B();     public void  init ()  {         Thread.currentThread().setName("主线程" );                  a.foo(b);         System.out.println("进入了主线程之后" );     }     @Override     public void  run ()  {         Thread.currentThread().setName("副线程" );                  b.bar(a);         System.out.println("进入了副线程之后" );     }     public static  void  main (String[] args)  {         DeadLock dl = new DeadLock();         new Thread(dl).start();         dl.init();     } } 
运行结果:
线程通信 一、线程通信涉及到的三个方法 wait() :一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器。notify() :一旦执行方法,就会唤醒被 wait 的第一个线程,如果有多个线程被 wait,就唤醒优先级最高的那个。notifyAll() :一旦执行此方法,就会唤醒所有被 wait()的线程。
代码示例: 
线程通信的例子:使用两个线程打印 1-100。线程 1, 线程 2 交替打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class  Number  implements  Runnable {    private int  number = 1 ;     Object obj = new Object();     @Override     public void  run ()  {         while (true ){            synchronized(obj){                                obj.notify();                if (number <= 100 ){                    try {                        Thread.sleep(10 );                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName()+":"  +number);                    number++;                    try {                                                obj.wait();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }else {                    break ;                }            }         }     } } public class  CommunicationTest  {     public static  void  main (String[] args)  {         Number number = new Number();         Thread t1 = new Thread(number);         Thread t2 = new Thread(number);         t1.setName("线程1" );         t2.setName("线程2" );         t1.start();         t2.start();     } } 
运行结果:
二、说明 wait(),notify().notifyAll()**:三个方法必须用在同步代码块或同步方法中。 **wait(),notify(),notifyAll() :三个方法的调用者必须是同步代码块同步方法的同步监视器。否则会出现IllegalMonitorStateException 异常wait(),notify(),notifyAll()**:三个方法时定义在 java.lang.Object**类中。
三、释放锁的操作: ① 当前线程的同步方法、同步代码执行结束。 ② 当前线程在同步代码块、同步方法中遇到 break、return 终止该代码块、该方法的继承执行。 ③ 当前线程在同步代码块、同步方法中出现未处理的 Error 或 Exception,导致异常结束。 ④ 当前线程在同步代码块、同步方法中执行了线程对象的 wait()方法,当前线程暂停,并释放锁
四、不会释放锁的操作 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield();  方法暂停当前线程的执行 线程执行同步代码块时,其他线程调用了线程的 supend()方法将线程挂起,该线程不会释放锁(同步监视器) 五、小结 sleep()与 wait()的异同 
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。 不同点: <1>两个方法声明的位置不同:Thread 类中声明sleep()**, Object类中声明 wait()** <2> 调用的要求不同:sleep() 可以在任何需要的场景下调用。wait() 必须使用在同步代码块或同步方法中。 java 多线程基础到这里结束了,在写博客同时一边复习自己所学的知识点,给自己不断积累知识点。