在说多线程之前,先说两个概念:并行和并发。
并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。
从上面的概念可知,现在经常提起的高并发,并不是指瞬时同时发生,而是一种CPU的瞬时大量切换处理线程的情形。CPU在多个线程之间来回切换,来处理相应的任务。
现在我们在购买电脑时,导购员经常会跟我们说是四核八线程等等。指的就是CPU的核数,这样在处理多线程任务时,就可以更高效地并行执行任务了。
再来说一个概念:多个CPU和多核CPU的区别?
多个CPU,那就是多个物理CPU,各个CPU之间是通过总线
进行通信的,效率比较低。
多核CPU:不同的核通过L2 Cache
进行通信,存储和外设通过总线与CPU通信。
这二者的效率肯定是:多核CPU
>多个CPU
,但是多核的它贵啊。
现在让我们一个简单售票的小例子来看一下线程安全的问题及解决方案吧。
售票的简单例子
模拟一下售票的例子,使用三个线程去售票(总共票数设置为100张):
public class SaleTicketImpl implements Runnable { private int ticket = 100;
@Override public void run() { while (true) { if (ticket > 0) { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票"); ticket--; } } } }
|
测试类如下:
public class SaleTicketTest { public static void main(String[] args) { SaleTicketImpl saleImpl = new SaleTicketImpl(); try { Thread t1 = new Thread(saleImpl); t1.setName("一号窗口"); Thread t2 = new Thread(saleImpl); t2.setName("二号窗口"); Thread t3 = new Thread(saleImpl); t3.setName("三号窗口");
t1.start(); t2.start(); t3.start(); }catch (Exception e){ e.printStackTrace(); } } }
|
经过测试发现,这种代码会出现重复售票以及售出负数票的情况。
说明上面的售票线程实现类存在多线程下安全问题,多线程之所以会出现安全问题,究其本质是因为多个线程操作了共享数据资源导致的。
那么我们该怎么解决这个多线程访问共享资源的安全问题呢?
来看第一种解决方式,使用同步代码块
解决多线程安全问题
同步代码块
public class SaleTicketImpl implements Runnable{ private int ticket = 100;
Object obj = new Object();
@Override public void run() { while (true) { synchronized (obj) { if (ticket > 0) { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票"); ticket--; } } } } }
|
同步方法
public class SaleTicketImpl implements Runnable{ private int ticket = 100;
@Override public void run() { while (true) { saleTicket(); } }
public synchronized void saleTicket(){ if (ticket > 0) { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票"); ticket--; } } }
|
使用Lock锁的子类实现
public class SaleTicketImpl implements Runnable{ private int ticket = 100;
Lock lock = new ReentrantLock();
@Override public void run() { while (true) { lock.lock(); if (ticket > 0) { try { Thread.sleep(10); System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票"); ticket--; } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); }
} } } }
|
以上三种方式都可以解决多线程对共享资源操作带来的安全问题。