(Java多线程系列二)线程间同步
2019-12-24 11:28
786 查看
Java多线程间同步
1、什么是线程安全
通过一个案例了解线程安全
案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
先来看一个线程不安全的例子
class SellTicketRunnable implements Runnable { public int count = 100; @Override public void run() { while (count > 0) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } int index = 100 - count + 1; System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票"); count--; } } } public class JavaSyncDemo { public static void main(String[] args) { SellTicketRunnable runnable = new SellTicketRunnable(); Thread sellThread1 = new Thread(runnable); Thread sellThread2 = new Thread(runnable); sellThread1.start(); sellThread2.start(); } }
可以看到两个线程同时卖票的时候,会出现漏卖,多卖同一张票,还会出现超卖的问题,这就是线程不安全的问题。
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
2、线程安全问题的解决办法
(1)使用同步代码块
class SellTicketRunnable implements Runnable { public int count = 100; private Object lock = new Object(); @Override public void run() { while (count > 0) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock) { if (count > 0) { int index = 100 - count + 1; System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票"); count--; } } } } } public class JavaSyncDemo { public static void main(String[] args) { SellTicketRunnable runnable = new SellTicketRunnable(); Thread sellThread1 = new Thread(runnable); Thread sellThread2 = new Thread(runnable); sellThread1.start(); sellThread2.start(); } }
从上面的案例可以看出,使用synchronized同步代码块包裹住写操作,每个线程在调用同步代码块中逻辑的时候,都需要先获取同步锁,所以避免了多线程写操作数据的冲突问题。
(2)使用同步函数
class SellTicketRunnable01 implements Runnable { public int count = 100; @Override public void run() { while (count > 0) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } this.sale(); } } synchronized void sale() { if (count > 0) { int index = 100 - count + 1; System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票"); count--; } } } public class JavaSyncDemo01 { public static void main(String[] args) { SellTicketRunnable01 runnable = new SellTicketRunnable01(); Thread sellThread1 = new Thread(runnable); Thread sellThread2 = new Thread(runnable); sellThread1.start(); sellThread2.start(); } }
synchronized包裹的函数,其实就是给该函数块添加了一把this锁。
注意:synchronized 修饰静态方法使用锁是当前类的字节码文件(即
类名.class),同理,如果在静态方法中添加个同步代码块,可以获取类名.class为代码块加锁
class SellTicketRunnable02 implements Runnable { public static int count = 100; @Override public void run() { while (count > 0) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } SellTicketRunnable02.sale(); } } static void sale() { synchronized (SellTicketRunnable02.class) { if (count > 0) { int index = 100 - count + 1; System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票"); count--; } } } } public class JavaSyncDemo02 { public static void main(String[] args) { SellTicketRunnable02 runnable = new SellTicketRunnable02(); Thread sellThread1 = new Thread(runnable); Thread sellThread2 = new Thread(runnable); sellThread1.start(); sellThread2.start(); } }
(3)使用lock锁
class SellTicketRunnable03 implements Runnable { public int count = 100; private Lock lock = new ReentrantLock(); @Override public void run() { while (count > 0) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); if (count > 0) { int index = 100 - count + 1; System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票"); count--; } lock.unlock(); } } } public class JavaSyncDemo03 { public static void main(String[] args) { SellTicketRunnable03 runnable = new SellTicketRunnable03(); Thread sellThread1 = new Thread(runnable); Thread sellThread2 = new Thread(runnable); sellThread1.start(); sellThread2.start(); } }
lock和synchronized的区别
①lock在使用时需要手动的获取锁和释放锁;
②lock可以尝试非阻塞的获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁;
③lock锁可以响应中断,当获取到锁的线程被中断时,中断异常会被抛出,同时锁被释放;
④lock在指定截至时间之前获取锁,如果解释时间到了依旧无法获取锁,就返回。
// lock锁的安全使用方法 class lockDemo { Lock lock = new ReentrantLock(); void demoFun() { lock.lock(); try { // 可能出现线程安全的操作 } finally { lock.unlock(); } } }
(4)使用Java原子类
java.util.concurrent.atomic.AtomicBoolean;
java.util.concurrent.atomic.AtomicInteger;
java.util.concurrent.atomic.AtomicLong;
java.util.concurrent.atomic.AtomicReference;
class SellTicketRunnable04 implements Runnable { public AtomicInteger count = new AtomicInteger(100); @Override public void run() { while (true) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } if (count.get() > 0) { int index = 100 - count.getAndDecrement() + 1; System.out.println(Thread.currentThread().getName() + "卖出第" + index + "张票"); } } } } public class JavaSyncDemo04 { public static void main(String[] args) { SellTicketRunnable04 runnable = new SellTicketRunnable04(); Thread sellThread1 = new Thread(runnable); Thread sellThread2 = new Thread(runnable); sellThread1.start(); sellThread2.start(); } }
3、死锁
先看一个死锁的示例
public class DeadLockDemo01 { private static Object lock1 = new Object(); private static Object lock2 = new Object(); public static void main(String[] args) { new Thread() { //线程1 public void run() { while (true) { synchronized (lock1) { System.out.println(this.getName() + ":获取lock1锁"); synchronized (lock2) { System.out.println(this.getName() + ":获取lock2锁"); } } } } }.start(); new Thread() { //线程2 public void run() { while (true) { synchronized (lock2) { System.out.println(this.getName() + ":获取lock2锁"); synchronized (lock1) { System.out.println(this.getName() + "::获取lock1锁"); } } } } }.start(); } }
运行上面的代码,可以观察到线程卡死,就是出现了死锁
线程1先拿到lock1锁,再拿到lock2锁,执行完成后才能释放所有锁;
线程2先拿到lock2锁,再拿到lock1锁,执行完成后才能释放所有锁。
如果在线程1获取到lock1锁的时候,线程2获取到lock2还没释放,线程1无法获取lock2锁,也就无法释放lock2锁,这时系统就会出现死锁。
线程死锁的避免办法:不要在同步中嵌套同步
相关文章推荐
- 多线程系列二——java线程间的互斥与同步
- java 多线程:开两个线程,一个线程跑同步代码块,一个线程跑同步函数
- Java多线程系列--“基础篇”06之 线程让步
- Java基础-23总结多线程,线程实现Runnable接口,线程名字获取和设置,线程控制,线程安全,同步线程...
- java线程研究---(7)Thread同步:多线程数据共用会产生问题
- Java多线程系列--“基础篇”06之 线程让步
- Java多线程系列--“基础篇”10之 线程优先级和守护线程
- Java多线程系列--“基础篇”06之 线程让步
- 【Java】多线程系列(三)之阻塞线程的多种方法
- Java多线程-线程的同步(同步代码块)
- java 多线程系列基础篇(七)之线程休眠
- Java多线程线程、同步代码块、同步函数、死锁
- 【Java面试题】26 多线程有几种实现方法?同步有几种实现方法? 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
- Java多线程系列--“基础篇”05之 线程等待与唤醒
- Java多线程系列--【基础篇10】- 线程优先级和守护线程
- Java多线程系列--“基础篇”10之 线程优先级和守护线程
- Java多线程系列--“基础篇”09之 interrupt()和线程终止方式
- Java多线程开发系列之四:玩转多线程(线程的控制2)
- Java多线程系列--“基础篇”06之 线程让步
- Java多线程系列--“基础篇”07之 线程休眠