Java多线程之线程同步
Posted zengblogs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程之线程同步相关的知识,希望对你有一定的参考价值。
一、多线程出现的安全问题:
1、问题的原因:
多个线程执行的不确定性引起执行结果的不稳定。当多条语句在操作同一个线程共享数据时,
一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
2、解决的办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
二、Synchronized的使用方法:
1、操作共享数据的代码,即为需要被同步的代码。不能包含代码多或者少。
2、共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3、同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
4、要求:多个线程必须要共用同一把锁。
三、同步机制中的锁:
1、同步锁机制:
对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。
防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。
第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,
就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
2、synchronized的锁是什么:
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class) 、非静态方法(this)
同步代码块:自己指定,很多时候也是指定为this或类名.class
3、注意事项:
必须确保使用同一个资源的多个线程共用一把锁,
这个非常重要,否则就无法保证共享资源的安全。
一个线程类中的所有静态方法共用同一把锁(类名.class) ,
所有非静态方法共用同一把锁(this) ,同步代码块( 指定需谨慎)
例子:创建三个窗口卖票,总票数为100张。使用实现Runnable接口的方式
1、问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题。
2、问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3、如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,
其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
4、在Java中,我们通过同步机制,来解决线程的安全问题。
1 class Window implements Runnable{ 2 3 private int ticket = 100; 4 @Override 5 public void run() { 6 7 while(true){ 8 synchronized (this){ 9 //此时的this:唯一的Window1的对象 10 if (ticket > 0) { 11 12 try { 13 Thread.sleep(100); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 18 System.out.println(Thread.currentThread().getName() 19 + ":卖票,票号为:" + ticket); 20 21 ticket--; 22 } else { 23 break; 24 } 25 } 26 } 27 } 28 } 29 30 31 public class WindowTest { 32 public static void main(String[] args) { 33 Window1 w = new Window1(); 34 35 Thread t1 = new Thread(w); 36 Thread t2 = new Thread(w); 37 Thread t3 = new Thread(w); 38 39 t1.setName("窗口1"); 40 t2.setName("窗口2"); 41 t3.setName("窗口3"); 42 43 t1.start(); 44 t2.start(); 45 t3.start(); 46 } 47 }
使用同步方法解决实现Runnable接口的线程安全问题。
关于同步方法的总结:
1、同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2、非静态的同步方法,同步监视器是:this。
3、静态的同步方法,同步监视器是:当前类本身。
1 class Window implements Runnable { 2 private int ticket = 100; 3 @Override 4 public void run() { 5 while (true) { 6 show(); 7 } 8 } 9 10 private synchronized void show(){ 11 //同步监视器:this 12 //synchronized (this){ 13 14 if (ticket > 0) { 15 16 try { 17 Thread.sleep(100); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 22 System.out.println(Thread.currentThread().getName() 23 + ":卖票,票号为:" + ticket); 24 25 ticket--; 26 } 27 } 28 } 29 30 31 public class WindowTest { 32 public static void main(String[] args) { 33 Window3 w = new Window3(); 34 35 Thread t1 = new Thread(w); 36 Thread t2 = new Thread(w); 37 Thread t3 = new Thread(w); 38 39 t1.setName("窗口1"); 40 t2.setName("窗口2"); 41 t3.setName("窗口3"); 42 43 t1.start(); 44 t2.start(); 45 t3.start(); 46 } 47 }
四、线程同步的范围:
1、如何找问题,即代码是否存在线程安全 (非常重要 )
(1)明确哪些代码是多线程运行的代码。
(2)明确多个线程是否有共享数据。
(3)明确多线程运行代码中是否有多条语句操作共享数据。
2、如何解决呢 (非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,
在执行过程中,其他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中。
3、切记:
范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能。
4、释放锁的操作:
●当前线程的同步方法、同步代码块执行结束。
●当前线程在同步代码块、同步方法中遇到break、returm终 止了该代码块、该方法的继续执行。
●当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
●当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
5、不会释放锁的操作:
●线程执行同步代码块或同步方法时,程序调用Thread. sleep()、Thread.yield()方法暂停当前线程的执行。
●线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,
该线程不会释放锁(同步监视器),应尽量避免使用suspend()和resume()来控制线程。
6、线程的死锁问题:
●死锁原因:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,
就形成线程死锁。出现死锁后,不会出现异常或提示,只是所有的线程都处于阻塞状态,无法继续。
●解决方法:
专门的算法或原则、尽量减少同步资源的定义、尽量避免嵌套同步。
五、Lock(锁):
1、从JDK 5.0开始,Java提供 了更强大的线程同步机制一通过显式定 义同
步锁对象来实现同步。同步锁使用Lock对象充当。
2、java.util.concurrent locks.Lock接口是控制多个线程对共享资源进行访问的
工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象
加锁,线程开始访问共享资源之前应先获得Lock对象。
3、Reentrantl _ock类实现了Lock ,它拥有与synchronized相同的并发性和
内存语义,在实现线程安全的控制中,比较常用的是Reentrantl _ock,可以
显式加锁、释放锁。
4、synchronized与Lock的对比:
(1)Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁, 出了作用域自动释放。
(2)Lock只有代码块锁,synchronized有 代码块锁和方法锁。
(3)使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
(4)优先使用顺序:Lock→同步代码块(已经进入了方法体,分配了相应资源)→同步方法(在方法体之外)。
以上是关于Java多线程之线程同步的主要内容,如果未能解决你的问题,请参考以下文章