首页 > 代码库 > 有关多线程与线程共享数据的简单实现
有关多线程与线程共享数据的简单实现
首先讨论一下一个简单直接的问题,为什么要学多线程?
那么原因很简单,会了多线程,你就能涨工资或者找到更好的工作啊!!!
开个玩笑。
好吧,其实不怎么好笑。
多线程能解决什么问题?
举个例子,现在有两个任务A,B在同时进行,A需要10s,B需要2s。没有多线程之前,通常的做法无非两种,先执行A再执行B,或者先执行B再执行A,时间上的结果都是需要12s。那么有没有可能,A和B同时进行呢?有的!用多线程来实现。这个时候,AB任务的完成时间就是10s,节约了2s。
接下来要讲实现了。
讲实现之前,区分一下,进程和线程。
进程是什么?最直接的理解就是windows任务管理列表里面的一个.exe执行单元,系统进行资源分配和调度的一个单位。线程是什么?线程就是进程中独立运行的子任务。
比如,QQ.exe是一个进程,打开QQ,可以传输文件的同时,与好友聊天,是不同的线程。
在JAVA里面,多线程主要有两种实现,继承自Thread类,或者是实现Runnable接口,下面分别看一下。
继承自Thread类
package com.java.multiThread; public class ThreadSay extends Thread { @Override public void run() { try { for(int i =0;i<10;i++) { int time = (int) Math.random()*1000; Thread.sleep(time); System.out.println("T run name:"+Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
重载Run方法,业务代码就放到run方法里面。
测试一下
public static void threadDemoFunc() { try { ThreadSay ts = new ThreadSay(); ts.setName("thread say"); ts.start(); for(int i=0;i<10;i++) { int time = (int) Math.random()*1000; Thread.sleep(time); System.out.println("Main run name:"+Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } }
结果是什么样子呢
T run name:thread say Main run name:main T run name:thread say Main run name:main T run name:thread say Main run name:main T run name:thread say Main run name:main T run name:thread say T run name:thread say Main run name:main T run name:thread say Main run name:main T run name:thread say Main run name:main T run name:thread say Main run name:main T run name:thread say Main run name:main Main run name:main
从上面的结果还可以看出,CPU执行哪个线程具有不确定性,也就是线程具有随机特性。
接着看看Runnable接口怎么实现多线程。
package com.java.multiThread; public class RunnableSay implements Runnable { @Override public void run() { System.out.println("Runnable say"); } }
也是实现run方法的重载
测试一下
public static void runnableDemoFunc() { RunnableSay rs = new RunnableSay(); Thread t = new Thread(rs); t.start(); }
上述代码可以看到,rs不能直接start,需要放到Thread来启动。
结果就不演示了,和Thread类继承的实现是一样的。
使用多线程的时候需要特别注意的是线程间的共享数据。
先看一个例子。
package com.java.multiThread; public class DataShare extends Thread { private int count = 5; public void run() { count --; System.out.println("count:"+count); } }
然后我如下图所示,这么调用
public static void dataShareDemoFunc() { DataShare ds = new DataShare(); Thread a = new Thread(ds,"A"); Thread b = new Thread(ds,"B"); Thread c = new Thread(ds,"C"); Thread d = new Thread(ds,"D"); Thread e = new Thread(ds,"E"); a.start(); b.start(); c.start(); d.start(); e.start(); }
结果会是怎样呢?
count:3 count:1 count:2 count:0 count:3
结果就会有可能出现下图类似的场景,数据就错乱了。
为什么,因为count--实际上不是一个一步到位的原子操作,它可以分为3步:取到count,count-1,把count-1赋值给count。因此一个线程在读修改count的时候,可能另一个线程正在读取count。
这种情况其实很严重,比如在一个销售管理系统中,商品出库是一个多线程程序,员工A,B,C,D,E都有出库权,商品总库存需要根据出库操作变化,如果程序如上图那么写,那么员工拿到的库存数据就是一个脏读数据,后果很严重。
怎么处理?这里需要引入synchronized参数做处理,把它加在需要同步锁的方法或者代码块里面。
package com.java.multiThread; public class DataShare extends Thread { private int count = 5; synchronized public void run() { count --; System.out.println("count:"+count); } }
再执行的结果
count:4 count:3 count:2 count:1 count:0
就是一个顺序的结果了。因为synchronized能够保证其内部操作的原子性,线程再执行synchronized方法或者代码块的时候,会锁住这个方法,然后其他线程需要等待当前线程释放锁(执行完毕),才能拿到锁来执行方法或者代码块。
多线程数据贡献还需要了解另一个关键字volatile,一个变量声明为volatile,就意味着这个变量被修改时其他所有使用到此变量的线程都立即能见到变化(称之为可见性)。具体是在每次使用前都要先刷新,以保证别的线程中的修改已经反映到本线程工作内存中,因此可以保证执行时的一致性。以下例子展现了volatile的作用:
public class StoppableTask extends Thread { private volatile boolean pleaseStop; public void run() { while (!pleaseStop) { // do some stuff... } } public void tellMeToStop() { pleaseStop = true; } }
有关多线程与线程共享数据的简单实现