首先讲一下进程和线程的区别:
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。
一、扩展java.lang.Thread类
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
二、实现java.lang.Runnable接口
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();
}
}
}
结果为:
B运行:0
B运行:1
A运行:1
B运行:2
A运行:2
B运行:3
A运行:3
B运行:4
A运行:4
三、Thread和Runnable的区别
如果一个类继承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):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
注意:该方法有错误数据,不是完全安全的。
四、线程状态转换
五、线程调度
六、常用函数说明
①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)②join():指等待t线程终止。
使用方式。
join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了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
C线程正在执行,i为0
A线程正在执行,i为0
C线程正在执行,i为1
B线程正在执行,i为1
C正处于 yield
A线程正在执行,i为1
C线程正在执行,i为2
C线程正在执行,i为3
C正处于 yield
C线程正在执行,i为4
B正处于 yield
B线程正在执行,i为2
B线程正在执行,i为3
A正处于 yield
A线程正在执行,i为2
A线程正在执行,i为3
B正处于 yield
B线程正在执行,i为4
A正处于 yield
A线程正在执行,i为4
可以 发现 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()就可以很方便的解决。代码如下