当多个线程对同一数据进行访问时,容易出现线程安全问题,这个时候就需要让线程同步来保证数据的安全。线程同步就是说在两个或两个以上的线程访问同一资源的时候,需要用到某种方式来保证资源在某一时刻只能被一个线程访问
线程同步的实现方案:
一、同步代码块:synchronized(同步监视器)
1、认识同步监视器(锁子)
synchronized(同步监视器){}
1)必须是引用数据类型,不能是基本数据类型
2)在同步代码块中可以改变同步监视器对象的值,不能改变其引用
3)尽量不要使用String和包装类Integer做同步监视器,如果要使用,则必须保证代码快啊中不对其做任何操作
4)一般使用共享资源做同步器
5)可以创建一个专门的同步监视器,没有任何含义
6)建议使用final来修饰同步监视器
2、同步代码块的执行过程
1)第一个线程来到同步代码块,发现同步监视器是open状态,需要close,然后执行其中的代码
2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了CPU,但是没有开锁
3)第二个线程获取了CPU,来到同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个也进入了阻塞状态
4)第一个线程再次获得CPU,执行后续代码,执行完毕释放锁
5)第二个线程再次获得CPU,来到同步代码块发现是开锁状态,重复第一个线程的处理过程
3、下面的代码是用同步代码块来实现线程同步(多个窗口实现安全售票)
public class TiketsTest { public static void main(String[] args) { for(int i = 0;i<5;i++){//运用循环来开启五个线程(模拟五个售票员) new Thread(new TiketsRunnable(),"售票员"+(i+1)).start();//此处为了方便直接使用匿名对象 } } public class TiketsRunnable implements Runnable { private int tikets = 100;//要卖票的总数 private Object obj = new Object(); @Override public void run() { while (true){ synchronized (obj) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (tikets <= 0) { break; } System.out.println(Thread.currentThread().getName() + "卖了第" + tikets-- + "票"); } } } }
二、同步方法:修饰符 synchronized 返回值类型 方法名(参数){}
1、不要将run()定义为同步方法
2、同步方法的同步监视器是this
3、同步代码块的效率要高于同步方法
1)同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住了使用该同步代码块,而没有锁住使用其他监视器的代码块
2)同步方法是将线程锁在了方法的外部,而同步代码块将线程锁在了代码块的外部,但是却是方法的内部
4、下面的代码是用同步方法来实现线程同步(多个窗口实现安全售票)
public class TiketsTest { public static void main(String[] args) { for(int i = 0;i<5;i++){//运用循环来开启五个线程(模拟五个售票员) new Thread(new TiketsRunnable(),"售票员"+(i+1)).start();//此处为了方便直接使用匿名对象 } } } public class TiketsRunnable implements Runnable { private int tikets = 3; private Object obj = new Object(); @Override public void run() { while (true) { sell(); if (tikets <= 0) { break; } } } public synchronized void sell(){//同步方法 if(tikets<=0){ return; } try { Thread.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖了第" + tikets+ "票"); tikets --; } }
三、Lock锁
1、Lock锁
1)JDK1.5后新增功能,与采用synchronized想比,lock锁可提供多种锁方案,更灵活
2)java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算
法、性能特性或者锁定语义。
3)ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义, 但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的
性能。
注意:如果同步代码有异常,要将unlock()写入finally语句块中,确保关锁
2、Lock和synchronized的区别
1)Lock是显示锁(需要手动开锁、关锁,不要忘记关锁),synchronized是隐式锁,遇到异常自动解锁
2)Lock锁只有代码块锁,synchronized有代码块锁和方法锁
3)使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
3、下面的代码是用Lock锁来实现线程同步(多个窗口实现安全售票)
public class TiketsTest { public static void main(String[] args) { for(int i = 0;i<5;i++){//运用循环来开启五个线程(模拟五个售票员) new Thread(new TiketsRunnable(),"售票员"+(i+1)).start();//此处为了方便直接使用匿名对象 } } } import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TiketsRunnable implements Runnable { private int tikets = 100; Lock lock = new ReentrantLock(); @Override public void run() { while (true) { lock.lock();//开锁 try { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (tikets <= 0) { break; } System.out.println(Thread.currentThread().getName() + "卖了第" + tikets-- + "票"); }finally { lock.unlock();//放在finally语句块中确保关锁 } } } }
四、三种锁的优先使用顺序
Lock锁 —— 同步代码块(已经进入了方法体,分配了相应的资源)—— 同步方法(在方法体之外)
原文:https://www.cnblogs.com/huxiaoyang/p/11924018.html