一直都很想写关于多线程的东西,以来可以巩固巩固自己的知识,而来可以看看自己的掌握的水平,因为一直都觉得这方面挺有意思的好了。废话不多说,入正题。
java多线程,我们首先想多的是什么。进程,Thread,Runnable,start,run...
那我们就先从他们入手了。为什么会想到进程呢。以为一直都是多线程多进程的说。那他们有什么区别。
进程:进程是程序的运行和操作系统分配资源的最基本的独立单位。每个进程在没有特殊的处理下,是各自独立的。
线程呢:线程不能独立存在,线程必须依附于进程,线程共享进程的资源,从某种意义上来说,使用线程对系统开销更小,效率应该高点。
在java中,主要两种方式能够实现多线程的编程。一种是继承Thread类,一种是实现Runnable接口。要说两者的区别:我觉得无非是
1.Runnable更适合线程间的资源共享,也更推荐使用Runnable。
2.Runnable是接口所以可以避免Thread单继承的缺陷。
好了,这只是嘴上说说而已,还是来看看具体代码吧。
我们来分别实现一个经典的窗口买票程序:
继承Thread:
<span style="font-size:14px;">class SalerThread extends Thread{ private int sum = 5; public void sale(){ System.out.println(getName() + "号窗口正在售票,还剩" + (--sum) + "张票."); } @Override public void run(){ while (sum > 0) { sale(); } } } public class TestThread { public static void main(String[] args) { SalerThread thread = new SalerThread(); SalerThread thread1 = new SalerThread(); SalerThread thread2 = new SalerThread(); thread.setName("1"); thread1.setName("2"); thread2.setName("3"); thread.start(); thread1.start(); thread2.start(); } } 我们可以看到结果,很明显不符合我们的实际情况,但是但我们换成实现Runnable呢! class SalerRunnale implements Runnable{ private int sum = 5; public void sale(){ System.out.println(Thread.currentThread().getName() + "号窗口正在售票,还剩" + (--sum) + "张票."); } @Override public void run() { while (sum > 0) { sale(); } } } public class TestThread { public static void main(String[] args) { SalerRunnale runnale = new SalerRunnale(); Thread thread = new Thread(runnale, "1"); Thread thread1 = new Thread(runnale, "1"); Thread thread2 = new Thread(runnale, "1"); thread.start(); thread1.start(); thread2.start(); } }</span>
两个线程主体基本一样,就是在调用Thread中的方法时,我们需要多写一步Thread.currentThread.
也许你想问了,为什么我们是调用start()呢,我们当然也可以调用run()啦,我保证,编译器不会报错,而且有结果,但是,这个结果是按照我们调用所谓的线程的顺序输出的。
也许你会想,哼,肯定只是偶然,但当我们多次调用之后,结果依然不变。好了,其实,在直接调用run()方法时,jvm只是把它当做一个普通方法进行调用,并不会给他重新分配一个线程。所以一定要记住,在写新线程的时候,调用的是它的start()方法,而不是我们重写的run()方法。
好了,现在聊一聊多线程的一些状态:
线程可以有6种状态:
*New(新创建)
*Runnable(可运行)
*Blocked(被阻塞)
*Waiting(阻塞)
*Timed waiting(计时等待)
*Terminated(被终止)
摘自:http://my.oschina.net/mingdongcheng/blog/139263)
线程之间是有自己的优先级的,相信这个词你应该很熟悉。默认情况下,子线程是继承父线程的优先级,当然,你也可以通过调用setPriority方法来设置优先级。线程的优先级应该在MIN_PRIORITY(Thread中定义为1)到MAX_PRIORITY(Thread中定义为10)。
有了jvm默认是先调用优先级高的线程,默认的是NORM_PRIORITY(Thread中定义5),当我们在线程中调用yield()时,会使当前线程处于一种让步的状态,即先让不低于自己的程序先运行,假如没有比自己高的呢,依旧自己运行。当然这不是绝对的,他依靠操作系统的支持。
有一种线程叫做守护线程。顾名思义,他是需要被守护者的,假如只有他自己则没有意义。所以,他不会独立存在。调用他们非常简单。只需要在start()之前调用setDeamon(true)即可。
但是,他依旧有缺陷,守护进程不应该去访问固有资源,如文件、数据库,因为他会在任何时候甚至在一个操作的中间发生中断。
我们在一个线程运行到一半想终止他怎么办。java一开始提供了stop方法,但是因为他很不安全,所以被弃用了。不仅如此,java还提供线程挂起的方法suspend和唤醒的方法resume。不过suspend很容易导致死锁的情况,所以也已经被抛弃了。
但是假如我们需要暂停一个线程怎么办。
我们可以设置一个boolean值。
如private boolean running;
需要暂停的时候
public synchronized void stop(){
running = false;
}
而我们在run方法中假如running的条件判断
while (running){
//your code...
}
如此,我们便实现了自己的暂停方法。当然才学到这的你可能会问为什么要加synchronized呢,他是什么意思呢。这个问题我们稍后会介绍,当然,你也可以看看我之前写的那一篇博客---超链接---。
好了,到这,我们还需要说一下线程的中断。线程在执行完run方法最后一条语句后,或者有未捕获的异常时,线程会终止。当然,我们可以调用interrupt方法来终止线程。每个线程都有表示中断状态的boolean值。每个线程都应该不时检查这个标志位,当然,假如在调用它之前,线程已经调用了wait,sleep或者join,他的interrupt
status将会被清除,并且会抛出一个InterruptedException
.异常。
Thread还有一个join方法,在一个线程A中调用另一个线程B,线程B调用join,那么线程A将等待线程B执行完毕。或者调用join的重载方法,设置时间表示之多等待多久。
<span style="font-size:14px;"><span style="font-size:12px;">class RunnableA implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++){ System.out.println("threadA :" + i); } } } public class TestForJoin { public static void main(String[] args) throws InterruptedException { RunnableA runnableA = new RunnableA(); Thread threadA = new Thread(runnableA); threadA.start(); // threadA.join(); for (int i = 0; i < 5; i++){ System.out.println(Thread.currentThread().getName() + " :" + i); } } }</span></span>
当我们注释threadA.join();时,结果如图:
当我们取消注释:
假如是独立的两个线程,一个线程调用join,对另外一个线程是不起作用的。
<span style="font-size:14px;"><span style="font-size:12px;">public class TestForJoin { public static void main(String[] args) throws InterruptedException { RunnableA runnableA = new RunnableA(); Thread threadA = new Thread(runnableA, "ThreadA"); Thread ThreadB = new Thread(runnableA, "ThreadB"); threadA.start(); threadB.start(); threadA.join(); for (int i = 0; i < 5; i++){ System.out.println(Thread.currentThread().getName() + " :" + i); } } }</span></span>因为在一个线程A中调用另一个线程B,B线程调用join,这只会阻塞A线程,假如同时存在线程C,C依旧可以和B抢占CPU.
我们说过线程之间的资源出自身的一些变量方法,其余都共享,所以,多线程必定会产生多个线程同时修改一块内存块的问题。我们该如何避免呢。
最简单也是最有效的方法当然是不使用多线程啦。但是当我们必须使用多线程的时候我们该怎么办。
我们先来做一个小程序,多线程按顺序打印1-9,要求先打印1-3,在打印4-6,最后全部输出。
<span style="font-size:14px;"><span style="font-size:12px;">public class ThreadPrint { static Lock lock = new ReentrantLock(); static Condition reachThree = lock.newCondition(); static Condition reachSix = lock.newCondition(); public static void main(String[] args) { final int[] integer = {0}; Thread threadA = new Thread(new Runnable() { @Override public void run() { lock.lock(); try { for (int i = 1; i <= 3; i++) { System.out.println(i); } integer[0] = 3; reachThree.signal(); } finally { lock.unlock(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { lock.lock(); try { while (integer[0] != 3) { reachThree.await(); } for (int i = 4; i <= 6; i++) { System.out.println(i); } integer[0] = 6; reachSix.signal(); } catch (InterruptedException e) { } finally { lock.unlock(); } } }); Thread threadC = new Thread(new Runnable() { @Override public void run() { lock.lock(); try { while (integer[0] != 6) { reachSix.await(); } for (int i = 6; i <= 9; i++) { System.out.println(i); } integer[0] = 9; } catch (InterruptedException e) { } finally { lock.unlock(); } } }); threadA.start(); threadB.start(); threadC.start(); } }</span></span>Lock是concurrent中给我们提供的一种锁机制,当一个线程进入一个类时,便给它加上锁,当还有其他的线程在之前线程还未退出,请求范文该类时,就会被阻塞,直至第一个进入的线程退出并解除锁。condition是一个条件,一个锁可以有一个或多个条件。当线程因为一个条件被阻塞时,会自动释放它的锁,一边其他线程进入并唤醒它。我们调用的锁ReentrantLock是一种可重入的锁,但一个线程进入一个类,调用一个含锁的方法时,它的计数器会加1,当释放一个锁时便减一,每次有其他线程进入该类会检查计数器是否为0.我们还可以在调用构造函数时将它设置成公平锁new ReentrantLock(true),即等待时间越长越有可能获得该锁,但是它的效率相比不公平锁会低很多,所以不是必须使用它,我们总是使用缺省的不公平锁。
当然还有一种ReentrantReadWriteLock,当线程频繁对数据进行读操作时,他是一个很好的选择,它有读锁和写锁,它允许多个线程进行读取,指定数量的线程进行写操作。所以效率高。每次进行读操作时利用ReentrantReadWriteLock.ReadLock,写操作便用ReentrantReadWriteLock。
当然java还提供一种更为简单的机制synchronized,可以设置同步块和同步方法,具体可以参考我的另一篇博文:初探java 对象中wait(),notify(),notifyAll() 和线程中的synchronized。需要多说的是,synchronized调用的实际上是一个内部锁,它只含有一个条件。
你会想那什么时候用synchronized什么时候用Lock呢。
1.其实最好是两个都不用,因为在我们的concurrent包中,有同步队列,他会自动帮我们处理数据的同步问题。
2.如果synchronized能够符合我们要求时,尽量使用它。
3.当我们需要用到Lock/Condition的特殊功能时,才考虑使用它。