首先讲一下进程和线程的区别:
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。
public class ExtendThread extends Thread { private String name; public ExtendThread(String name) { super(); this.name = name; } public void run() { for (int i = 0; i < 5; i++) { System.out.println(name + "运行 : " + i); try { sleep((int) Math.random() * 10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static class Main { public static void main(String[] args) { ExtendThread mTh1 = new ExtendThread("A"); ExtendThread mTh2 = new ExtendThread("B"); mTh1.start(); mTh2.start(); } } }
A运行 : 0
B运行 : 0
A运行 : 1
B运行 : 1
A运行 : 2
B运行 : 2
A运行 : 3
B运行 : 3
A运行 : 4
B运行 : 4
再次运行是:
A运行 : 0
B运行 : 0
B运行 : 1
A运行 : 1
B运行 : 2
A运行 : 2
B运行 : 3
A运行 : 3
B运行 : 4
A运行 : 4
public class ImpleThread implements Runnable{ private String name; public ImpleThread(String name) { super(); this.name = name; } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(name+"运行:"+i); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static class main{ public static void main(String[] args) { ImpleThread im1 =new ImpleThread("a"); ImpleThread im2 =new ImpleThread("b"); new Thread(new ImpleThread("A")).start(); new Thread(new ImpleThread("B")).start(); } } }
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
public class ShareThread extends Thread{ private int count =5; private String name; public ShareThread(String name) { super(); this.name = name; } public void run(){ for (int i = 0; i < 5; i++) { System.out.println(name+"运行 count="+count--); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static class main{ public static void main(String[] args) { ShareThread mTh1=new ShareThread("A"); ShareThread mTh2=new ShareThread("B"); mTh1.start(); mTh2.start(); } } }
A运行 count=5
B运行 count=5
B运行 count=4
A运行 count=4
A运行 count=3
B运行 count=3
A运行 count=2
B运行 count=2
B运行 count=1
A运行 count=1
从上面可以看出,不同的线程之间count是不同的,这对于卖票系统来说就会有很大的问题,当然,这里可以用同步来作。这里我们用Runnable来做下看看
public class ShareThread1 implements Runnable{ private int count=15; public void run(){ for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+ "运行 count= " + count--); try { Thread.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static class main{ public static void main(String[] args){ ShareThread1 sh=new ShareThread1(); new Thread(sh,"A").start(); new Thread(sh,"b").start(); new Thread(sh,"c").start(); } } }
A运行 count= 15
c运行 count= 13
b运行 count= 14
A运行 count= 12
b运行 count= 11
c运行 count= 12
c运行 count= 9
A运行 count= 8
b运行 count= 10
c运行 count= 7
A运行 count= 5
b运行 count= 6
c运行 count= 4
b运行 count= 3
A运行 count= 2
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
注:有错误数据。该方法还需要考虑使用。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
注意:该方法有错误数据,不是完全安全的。
join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
不加join。
public class NoJoinThread implements Runnable{ private String name; public NoJoinThread(String name) { super(); this.name = name; } @Override public void run() { System.out.println(name+"线程开始执行"); for (int i = 0; i < 10; i++) { System.out.println(name+"线程:"+i); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(name+"线程结束执行"); } public static class Main{ public static void main(String[] args){ System.out.println("主线程开始执行"); new Thread(new NoJoinThread("A")).start(); new Thread(new NoJoinThread("B")).start(); System.out.println("主线程执行完毕"); } } }
主线程开始执行
主线程执行完毕
B线程开始执行
B线程:0
A线程开始执行
A线程:0
B线程:1
A线程:1
B线程:2
A线程:2
A线程:3
B线程:3
A线程:4
B线程:4
A线程:5
B线程:5
A线程:6
B线程:6
A线程:7
B线程:7
B线程:8
A线程:8
B线程:9
A线程:9
A线程结束执行
B线程结束执行
发现主线程比子线程早结束
加join方法以后
public class UseJoinThread implements Runnable{ private String name; public UseJoinThread(String name) { super(); this.name = name; } @Override public void run() { System.out.println(name+"线程开始执行"); for (int i = 0; i < 5; i++) { System.out.println(name+"线程:"+i); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(name+"线程结束执行"); } public static class Main{ public static void main(String[] args) throws InterruptedException{ System.out.println("主线程开始执行"); UseJoinThread join1=new UseJoinThread("A"); UseJoinThread join2=new UseJoinThread("B"); Thread t1=new Thread(join1); Thread t2=new Thread(join2); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("主线程执行完毕"); } } }
主线程开始执行
B线程开始执行
B线程:0
A线程开始执行
A线程:0
B线程:1
A线程:1
B线程:2
A线程:2
B线程:3
A线程:3
B线程:4
A线程:4
B线程结束执行
A线程结束执行
主线程执行完毕
③yield():暂停当前正在执行的线程对象,并执行其他线程。
public class YieldThread implements Runnable { private String name; public YieldThread(String name) { super(); this.name = name; } public void run() { for (int i = 0; i < 5; i++) { System.out.println(name+"线程正在执行,i为"+i); if (i%2!=0) { Thread.currentThread().yield(); System.out.println(name+"正处于 yield"); } } } public static class Main{ public static void main(String[] args){ YieldThread y1=new YieldThread("A"); YieldThread y2=new YieldThread("B"); YieldThread y3=new YieldThread("C"); new Thread(y1).start(); new Thread(y2).start(); new Thread(y3).start(); } } }结果为:B线程正在执行,i为0
可以 发现 yield之后 依然还是 这个线程。
sleep()和yield()的区别
sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
④setPriority(): 更改线程的优先级。
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10
Thread4 t1 = new Thread4("t1"); Thread4 t2 = new Thread4("t2"); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY);⑤interrupt():中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭
要想结束进程最好的办法就是用sleep()函数的例子程序里那样,在线程类里面用以个boolean型变量来控制run()方法什么时候结束,run()方法一结束,该线程也就结束了。
⑥wait()
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下