Java中的多线程
首先,在开篇讲线程之前要说一个问题,我们知道多线程的执行原理是cpu在不同的线程中做着切换操作,而一提到多线程,大家首先想到的肯定是提高系统的运行效率,可是真的是这样的么?我们来借助一位大神博客中的代码就可以看出来有时单线程的运行效率要高于多线程:
import threading from time import ctime class MyThread(threading.Thread): def __init__(self, func, args, name): threading.Thread.__init__(self) self.name = name self.func = func self.args = args def run(self): print ‘starting‘, self.name, ‘at:‘,ctime() apply(self.func, self.args) print self.name, ‘finished at:‘, ctime() def fun1(x): y = 0 for i in range(x): y+=1 def fun2(x): y = 0 for i in range(x): y+=1 def main(): print ‘staring single thread at:‘,ctime() fun1(10000000) fun2(10000000) print ‘finished single thread at:‘,ctime() t1 = MyThread(fun1,(10000000,),fun1.__name__) t2 = MyThread(fun2,(10000000,),fun2.__name__) t1.start() t2.start() t1.join() t2.join() print ‘all done‘ if __name__ == ‘__main__‘: main()
程序的运行结果就是单线程比多线程的运行效率有着明显的提升,可这是为什么呢?还是因为线程的执行原理,是因为cpu在你的各个线程之间做着快速的切换操作,那么如果你是单核的cpu那么你的程序就没有必要验证多线程了,因为在各个线程之间的切换都是在一个cpu下执行的,只有在双核或多核的cpu才可以以不同的cpu来执行不同的线程来提高程序的运行效率。相信很多人在自己的电脑上实验过多线程的执行时间问题,对于得出的结果疑惑不已,其实就是因为你的cpu的原因了。
好了,提了一个小的问题,现在我们切入主题,说一下java中的线程。线程有两种两种创建方式,一种是直接继承Thread类并且覆盖其run()方法,但是我们知道java只支持单继承,那么如果我们的类还有一个父类,那么就难以实现这个方法。第二种是通过声明实现Runnable接口,然后实现run方法,然后可以分配该类的实例,在创建Thread类的时候,通过带参构造来传递并启动,Runnable为非Thread类提供了一种激活方式。那么下面我们就用代码演示一下第二种创建线程的方式:
class Show implements Runnable { private int num = 100; @Override public void run() { while (true) { if (num > 0) { System.out.println(Thread.currentThread().getName()+":"+num); num--; } else { break; } } } } public class MyMain { public static void main(String[] args) { Show show = new Show(); Thread t = new Thread(show); Thread t1 = new Thread(show); Thread t2 = new Thread(show); Thread t3 = new Thread(show); t.start(); t1.start(); t2.start(); t3.start(); } }
这样就完成了四个线程在同时执行循环,直到num见到0为止,可是如果我们开他的运行结果,会发现一个很明显的问题:
这是我截图了一部分运行结果,可以明显的发现出现了四次100,那么这是为什么呢?就拿这段代码打个比方,当你的t线程拿到cpu的执行权时,执行内部的run方法,进入循环判定if条件,判定num>0通过,进入内部代码块打印出num的值,可是就在这个时候,另外一个线程t1获得了执行权,同样进入循环判定if条件当判定num>0通过时,num的值并未改变,可是却依旧打印出num的值,这样就出现了两个相同的值,同样的当有多个这样的状况出现时,就会出现多个重复的值,这就是线程的安全问题,也就是当多个线程同时操作同一个variable,就可能会出现不可预知的结果。那么该怎样解决这样的问题呢?这就用到了synchronized(同步函数),为线程加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。可能光靠说的很难理解,下面还是展示一段上面代码的修改版,然后我们再来看看运行结果:
class Show implements Runnable { private int num = 100; Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) { if (num > 0) { System.out.println(Thread.currentThread().getName()+":"+num); num--; } else { break; } } } } } public class MyMain { public static void main(String[] args) { Show show = new Show(); Thread t = new Thread(show); Thread t1 = new Thread(show); Thread t2 = new Thread(show); Thread t3 = new Thread(show); t.start(); t1.start(); t2.start(); t3.start(); } }
这次再看代码的运行结果时,我们会发现刚刚的重复问题不见了,而且程序虽然还是在各个线程之间切换,可是打印出来num的顺序却是固定的有序的了,就像单线程的循环一样100到1,这就是线程的安全问题,其实解决的问题很简单,就是在需要判定并且改变值的代码外部加上synchronized块,他会在你的线程进入该块的时候做一个判定,如果有正在使用该方法的线程,如果有的话,会拦截该线程,直到正在使用synchronized方法的线程失去执行权的时候,才会再运行该线程。没有使用的线程,执行线程并锁定线程。在使用synchronized时需要注意死锁问题。