Java多线程(三)线程的同步
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源。或者使用yield()方法。
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能。当调用sleep() join() wait() suspend()方法和等待同步锁时会由运行到阻塞。
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态。当sleep()时间到、join()结束、获取同步锁、notify()/notifyAll()、resume()方法是由阻塞转为就绪。
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。
线程的同步:通过同步机制来解决线程安全问题
使用synchronized--代码同步块来实现线程同步
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 }
使用同步方法解决Runnable接口的线程安全
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 }
使用同步方法解决实现Runnable接口的线程安全问题
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 }
同步机制中的锁
在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
synchronized中的锁
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,可以指定为this或类名.class
注意:
1. 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
2. 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)
同步的范围
1、如何找问题,即代码是否存在线程安全?(非常重要)
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据
2、如何解决呢?(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。即所有操作共享数据的这些语句都要放在同步范围中。
3、切记:
范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能。
释放锁的操作
1. 当前线程的同步方法、同步代码块执行结束。
2. 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块或方法的继续执行。
3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

浙公网安备 33010602011771号