多线程的概念:
用户想要一边听歌,一边QQ聊天,一边游戏。要求能并发执行。
- program程序: 有特殊功能的一组代码
- process进程: 正在执行中的program,或者程序program的一次执行过程
- thread线程:程序program内部的一条执行路经,一个process至少有一个thread,一个process同一时间若并行执行多个thread,就是支持多线程的。
一个process中的多个thread共享相同的内存单元,他们从同一个堆中分配对象,可以访问相同的变量和对象,但是多个thread操作共享的系统资源可能就会带来安全的隐患。注意:不同的进程之间是不共享内存的;进程之间的数据交换和通信成本很高;
如图所示,下面的红框左侧Method Area和Heap就是共享的,浅蓝色的Virtual Machine Stack, Native Method Stack, Program Counter Register是每个thread运行时自己独有的。
Thread调度
- 分时调度: CPU给大家平均每个thread分多久时间,均分时间。
- 抢占式调度: 让优先级高的以较大概率优先使用CPU,如果thread们优先级相同,那就随机选择一个,Java用的就这种。
并行parallel和并发concurrency
并行 parallel:两个或多个事件在同一时刻同时发生。同一时刻,有多条指令在多个CPU上同时执行,相当于“多个人同时干不同的活”。
并发 concurrency:两个或多个事件在同一时间段发生。在一段时间内,有多条指令在单个CPU上快速轮换,交替执行,使得在宏观上看起来具有多个进程同时执行的效果。相当于“一个人不停的干不同的活”。
如何创建线程
两种办法
多线程的创建方式一:自建类继承Thread类,用户重写run方法
多线程的创建方式二:用户创建实现Runnable接口的类,再在里面重写run方法
(java.lang.Thread类是线程类,所有的线程对象都必须是Thread类或其子类的实例。)
第一种: 线程执行体:Thread对象调用run()
具体步骤:
- 创建extends继承Thread类的子类。
- 重写Thread的run(),该run方法体囊括此线程需要完成的各种操作
- 创建该Thread类子类的对象(创建Thread子类的实例)
- 调用线程对象的start()方法,启动该线程,JVM会自动调用用户重写的那个run方法
- 如果想实现其他更多线程,需要创建新的线程对象。
先使用方式一:自建类继承Thread类,用户重写run方法
举例,输出1-100以内所有的偶数。
package Thread;public class EvenNumberTest {public static void main(String[] args) {//3. 创建该Thread类子类的对象(创建Thread子类的实例)PrintNumber x = new PrintNumber();//4. 调用线程对象的start()方法,启动该线程x.start();
}//5. 如果想实现其他更多线程,需要创建新的线程对象,不能再次调用已经start的线程。
}//1. 创建继承Thread类的子类class PrintNumber extends Thread{//2. 重写Thread的run(),该run方法体囊括此线程需要完成的各种操作@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if (i%2==0){System.out.print(i+"\t");}}}}
注意不能让已经start的线程再次执行start(),会报错IllgealThreadStateException,正确方法就是再建一个。
输出结果:
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100
Process finished with exit code 0
举例2,一个线程输出1-100以内所有的偶数。一个线程输出1-100以内所有的奇数。
package Thread;public class Number2Thread {//3. 创建该Thread类子类的对象(创建Thread子类的实例)//4. 调用线程对象的start()方法,启动该线程,JVM会自动调用用户重写的那个run方法//5. 如果想实现其他更多线程,需要创建新的线程对象。public static void main(String[] args) {PrintEvenNum evenx = new PrintEvenNum();evenx.start();PrintOddNum oddx= new PrintOddNum();oddx.start();}
}//1. 创建继承Thread类的子类
class PrintOddNum extends Thread{//2. 重写Thread的run(),该run方法体囊括此线程需要完成的各种操作@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2 !=0){System.out.print("我是odd number奇数"+i+"\t");}}}
}class PrintEvenNum extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2 ==0) {System.out.println("我是偶数even number"+i+"\t");}}}
}
下面运行结果里可以明显看出,两个thread是交互运行输出的,并不是以往常见的,一个模块整体结束再运行下一个:
我是odd number奇数1 我是odd number奇数3 我是odd number奇数5 我是odd number奇数7 我是odd number奇数9 我是odd number奇数11 我是odd number奇数13 我是odd number奇数15 我是odd number奇数17 我是odd number奇数19 我是odd number奇数21 我是odd number奇数23 我是odd number奇数25 我是odd number奇数27 我是odd number奇数29 我是odd number奇数31 我是odd number奇数33 我是odd number奇数35 我是odd number奇数37 我是odd number奇数39 我是odd number奇数41 我是odd number奇数43 我是odd number奇数45 我是odd number奇数47 我是odd number奇数49 我是odd number奇数51 我是odd number奇数53 我是odd number奇数55 我是odd number奇数57 我是odd number奇数59 我是偶数even number0
我是偶数even number2
我是偶数even number4
我是偶数even number6
我是偶数even number8
我是偶数even number10
我是偶数even number12
我是偶数even number14
我是偶数even number16
我是偶数even number18
我是偶数even number20
我是偶数even number22
我是偶数even number24
我是odd number奇数61 我是odd number奇数63 我是odd number奇数65 我是odd number奇数67 我是odd number奇数69 我是odd number奇数71 我是odd number奇数73 我是odd number奇数75 我是odd number奇数77 我是odd number奇数79 我是odd number奇数81 我是odd number奇数83 我是odd number奇数85 我是odd number奇数87 我是odd number奇数89 我是odd number奇数91 我是odd number奇数93 我是odd number奇数95 我是odd number奇数97 我是odd number奇数99 我是偶数even number26
我是偶数even number28
我是偶数even number30
我是偶数even number32
我是偶数even number34
我是偶数even number36
我是偶数even number38
我是偶数even number40
我是偶数even number42
我是偶数even number44
我是偶数even number46
我是偶数even number48
我是偶数even number50
我是偶数even number52
我是偶数even number54
我是偶数even number56
我是偶数even number58
我是偶数even number60
我是偶数even number62
我是偶数even number64
我是偶数even number66
我是偶数even number68
我是偶数even number70
我是偶数even number72
我是偶数even number74
我是偶数even number76
我是偶数even number78
我是偶数even number80
我是偶数even number82
我是偶数even number84
我是偶数even number86
我是偶数even number88
我是偶数even number90
我是偶数even number92
我是偶数even number94
我是偶数even number96
我是偶数even number98 Process finished with exit code 0
也可以使用Thread.currentThread().getName()
打印出当前thread的名字,输出时也会明显看出交替性:
@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2 !=0){//System.out.print("我是odd number奇数"+i+"\t");System.out.println(Thread.currentThread().getName()+"\t"+i);}}}
结果显示,两个thread-1/0互相交替出现
Thread-1 1
Thread-1 3
Thread-1 5
Thread-1 7
Thread-1 9
Thread-1 11
Thread-1 13
Thread-1 15
Thread-1 17
Thread-1 19
Thread-1 21
Thread-1 23
Thread-1 25
Thread-1 27
Thread-1 29
Thread-1 31
Thread-1 33
Thread-1 35
Thread-1 37
Thread-1 39
Thread-1 41
Thread-1 43
Thread-1 45
Thread-1 47
Thread-1 49
Thread-1 51
Thread-1 53
Thread-1 55
Thread-1 57
Thread-1 59
Thread-1 61
Thread-1 63
Thread-1 65
Thread-1 67
Thread-1 69
Thread-1 71
Thread-1 73
Thread-1 75
Thread-0 0
Thread-0 2
Thread-0 4
Thread-0 6
Thread-0 8
Thread-0 10
Thread-0 12
Thread-0 14
Thread-0 16
Thread-0 18
Thread-0 20
Thread-0 22
Thread-0 24
Thread-0 26
Thread-0 28
Thread-0 30
Thread-0 32
Thread-0 34
Thread-0 36
Thread-0 38
Thread-0 40
Thread-0 42
Thread-0 44
Thread-0 46
Thread-0 48
Thread-0 50
Thread-0 52
Thread-0 54
Thread-0 56
Thread-0 58
Thread-1 77
Thread-0 60
Thread-1 79
Thread-0 62
Thread-1 81
Thread-0 64
Thread-1 83
Thread-0 66
Thread-1 85
Thread-0 68
Thread-1 87
Thread-0 70
Thread-1 89
Thread-0 72
Thread-1 91
Thread-0 74
Thread-1 93
Thread-1 95
Thread-1 97
Thread-1 99
Thread-0 76
Thread-0 78
Thread-0 80
Thread-0 82
Thread-0 84
Thread-0 86
Thread-0 88
Thread-0 90
Thread-0 92
Thread-0 94
Thread-0 96
Thread-0 98Process finished with exit code 0
下面对程序进行改写,试图创建Thread类的匿名子类的匿名对象,并调用其start方法
提示:可以使用new Thread(){XXX}.start();
package Thread;public class Number2Thread {
public static void main(String[] args) {//创建Thread类的匿名子类的匿名对象,并调用其start方法// new Thread(){};// new Thread(){重写run方法};// new Thread(){重写run方法}.start();new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2 !=0){//System.out.print("我是odd number奇数"+i+"\t");System.out.println(Thread.currentThread().getName()+"\t"+i);}}}}.start();new Thread(){public void run() {for (int i = 0; i < 100; i++) {if (i%2 ==0) {//System.out.println("我是偶数even number"+i+"\t");System.out.println(Thread.currentThread().getName()+"\t"+i);}}}}.start();}
}
可以观察到,结果依旧是两个thread交替出现的
Thread-0 1
Thread-0 3
Thread-0 5
Thread-0 7
Thread-0 9
Thread-0 11
Thread-0 13
Thread-0 15
Thread-1 0
Thread-1 2
Thread-0 17
Thread-0 19
Thread-0 21
Thread-0 23
Thread-0 25
Thread-0 27
Thread-0 29
Thread-0 31
Thread-0 33
Thread-0 35
Thread-0 37
Thread-0 39
Thread-0 41
Thread-0 43
Thread-0 45
Thread-0 47
Thread-0 49
Thread-0 51
Thread-0 53
Thread-1 4
Thread-1 6
Thread-1 8
Thread-1 10
Thread-1 12
Thread-1 14
Thread-1 16
Thread-1 18
Thread-1 20
Thread-1 22
Thread-1 24
Thread-1 26
Thread-1 28
Thread-1 30
Thread-1 32
Thread-1 34
Thread-1 36
Thread-1 38
Thread-1 40
Thread-1 42
Thread-1 44
Thread-1 46
Thread-1 48
Thread-1 50
Thread-1 52
Thread-1 54
Thread-1 56
Thread-1 58
Thread-0 55
Thread-0 57
Thread-0 59
Thread-0 61
Thread-0 63
Thread-0 65
Thread-0 67
Thread-0 69
Thread-0 71
Thread-0 73
Thread-0 75
Thread-0 77
Thread-0 79
Thread-0 81
Thread-0 83
Thread-0 85
Thread-0 87
Thread-0 89
Thread-0 91
Thread-0 93
Thread-0 95
Thread-0 97
Thread-0 99
Thread-1 60
Thread-1 62
Thread-1 64
Thread-1 66
Thread-1 68
Thread-1 70
Thread-1 72
Thread-1 74
Thread-1 76
Thread-1 78
Thread-1 80
Thread-1 82
Thread-1 84
Thread-1 86
Thread-1 88
Thread-1 90
Thread-1 92
Thread-1 94
Thread-1 96
Thread-1 98Process finished with exit code 0
现在使用创建方式二:用户创建实现Runnable接口的类,再在里面重写run方法
具体步骤如下:
- 创建一个实现Runnable接口的类
- 实现接口中的run() -->将此线程要执行的操作,声明再方法体中
- 创建当前实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
- Thread类的实例调用.start()
这种思维模式可以简单解释成
- 先建一个完成Runnable接口的实现类,里面重写了run方法
- 用这个新建的实现类做一个objectA
- 将这个objectA作为参数传给Thread
- Thread调用start方法时,会使用参数objectA自己重写的run方法
IDEA快捷键ctrl+i快速生成run方法块 (自动弹出override相关method)
双击接口名称处的Runnable按键ctrl+i
可以获得这个接口对应的method,选中override快速构建重写的结构。
举例:使用继承Runnable接口的方法,建立两个thread分别输出100以内能被3整除的所有数字。
package Thread;public class QuickTestRunnable {public static void main(String[] args) {PrtRunDiv3Num p1= new PrtRunDiv3Num();//只需要建立一个PrtRunDiv3Num的实例p1就行Thread y0 = new Thread(p1);Thread y1 = new Thread(p1);y1.start();y0.start();}}class PrtRunDiv3Num implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i%3==0){System.out.println(Thread.currentThread().getName()+"\t"+i);}}}
}
可以看到运行结果里,两个Thread交替输出着
Thread-1 0
Thread-1 3
Thread-1 6
Thread-1 9
Thread-1 12
Thread-1 15
Thread-1 18
Thread-1 21
Thread-0 0
Thread-0 3
Thread-0 6
Thread-0 9
Thread-0 12
Thread-0 15
Thread-0 18
Thread-0 21
Thread-0 24
Thread-0 27
Thread-0 30
Thread-0 33
Thread-0 36
Thread-0 39
Thread-0 42
Thread-0 45
Thread-0 48
Thread-0 51
Thread-0 54
Thread-0 57
Thread-0 60
Thread-0 63
Thread-0 66
Thread-1 24
Thread-1 27
Thread-1 30
Thread-1 33
Thread-1 36
Thread-1 39
Thread-0 69
Thread-0 72
Thread-1 42
Thread-1 45
Thread-1 48
Thread-1 51
Thread-1 54
Thread-1 57
Thread-1 60
Thread-1 63
Thread-1 66
Thread-1 69
Thread-1 72
Thread-1 75
Thread-1 78
Thread-1 81
Thread-1 84
Thread-1 87
Thread-1 90
Thread-1 93
Thread-1 96
Thread-1 99
Thread-0 75
Thread-0 78
Thread-0 81
Thread-0 84
Thread-0 87
Thread-0 90
Thread-0 93
Thread-0 96
Thread-0 99Process finished with exit code 0
举例,使用继承Runnable接口的方法,建立两个thread分别输出1-100内所有的偶数和奇数。
package Thread;public class EvenNumberRunnableTest {//1. 创建一个实现Runnable接口的类//2. 实现接口中的run() -->将此线程要执行的操作,声明再方法体中//3. 创建当前实现类的对象//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例//5. Thread类的实例调用.start()public static void main(String[] args) {PrintEvenNumRunnable x=new PrintEvenNumRunnable();Thread t1 = new Thread(x);t1.start();PrintOddNumRunnable y = new PrintOddNumRunnable();Thread t2 =new Thread(y);t2.start();}
}
class PrintEvenNumRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName()+"\t"+i);}}}
}class PrintOddNumRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if(i%2!=0){System.out.println(Thread.currentThread().getName()+"\t"+i);}}}
}
运行结果可以看出两个Thread交替输出:
Thread-1 1
Thread-1 3
Thread-1 5
Thread-1 7
Thread-1 9
Thread-1 11
Thread-1 13
Thread-1 15
Thread-1 17
Thread-1 19
Thread-1 21
Thread-1 23
Thread-1 25
Thread-1 27
Thread-1 29
Thread-1 31
Thread-1 33
Thread-1 35
Thread-1 37
Thread-1 39
Thread-1 41
Thread-0 0
Thread-0 2
Thread-0 4
Thread-0 6
Thread-0 8
Thread-0 10
Thread-0 12
Thread-0 14
Thread-0 16
Thread-0 18
Thread-0 20
Thread-0 22
Thread-0 24
Thread-0 26
Thread-0 28
Thread-0 30
Thread-0 32
Thread-0 34
Thread-0 36
Thread-0 38
Thread-0 40
Thread-0 42
Thread-0 44
Thread-0 46
Thread-0 48
Thread-0 50
Thread-0 52
Thread-1 43
Thread-1 45
Thread-1 47
Thread-1 49
Thread-1 51
Thread-1 53
Thread-1 55
Thread-1 57
Thread-1 59
Thread-1 61
Thread-1 63
Thread-1 65
Thread-1 67
Thread-1 69
Thread-0 54
Thread-0 56
Thread-0 58
Thread-0 60
Thread-0 62
Thread-0 64
Thread-0 66
Thread-0 68
Thread-0 70
Thread-0 72
Thread-0 74
Thread-0 76
Thread-0 78
Thread-0 80
Thread-0 82
Thread-0 84
Thread-0 86
Thread-0 88
Thread-0 90
Thread-0 92
Thread-0 94
Thread-0 96
Thread-0 98
Thread-1 71
Thread-1 73
Thread-1 75
Thread-1 77
Thread-1 79
Thread-1 81
Thread-1 83
Thread-1 85
Thread-1 87
Thread-1 89
Thread-0 100
Thread-1 91
Thread-1 93
Thread-1 95
Thread-1 97
Thread-1 99Process finished with exit code 0
用匿名实现类对上述程序进行改写,采用Runnable接口匿名实现类的匿名对象简化程序
提示,可使用格式 new Thread(new Runnable() {public void run() {XXX} }).start();
调用start结构
package Thread;public class EvenNumberRunnableTest {public static void main(String[] args) {new Thread(new Runnable(){public void run() {for (int i = 0; i <= 100; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName()+"\t"+i);}}}}).start();//PrintOddNumRunnable y = new PrintOddNumRunnable();new Thread(new Runnable(){public void run() {for (int i = 0; i <= 100; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName()+"bobo"+"\t"+i);}}}}).start();}
}
输出显示确实是交替输出的
Thread-0 0
Thread-0 2
Thread-0 4
Thread-0 6
Thread-0 8
Thread-0 10
Thread-0 12
Thread-0 14
Thread-0 16
Thread-0 18
Thread-0 20
Thread-0 22
Thread-0 24
Thread-0 26
Thread-0 28
Thread-0 30
Thread-0 32
Thread-0 34
Thread-0 36
Thread-0 38
Thread-1bobo 0
Thread-0 40
Thread-0 42
Thread-0 44
Thread-0 46
Thread-0 48
Thread-0 50
Thread-0 52
Thread-0 54
Thread-0 56
Thread-0 58
Thread-0 60
Thread-0 62
Thread-0 64
Thread-0 66
Thread-0 68
Thread-0 70
Thread-0 72
Thread-0 74
Thread-0 76
Thread-0 78
Thread-0 80
Thread-0 82
Thread-0 84
Thread-0 86
Thread-0 88
Thread-0 90
Thread-0 92
Thread-0 94
Thread-0 96
Thread-0 98
Thread-0 100
Thread-1bobo 2
Thread-1bobo 4
Thread-1bobo 6
Thread-1bobo 8
Thread-1bobo 10
Thread-1bobo 12
Thread-1bobo 14
Thread-1bobo 16
Thread-1bobo 18
Thread-1bobo 20
Thread-1bobo 22
Thread-1bobo 24
Thread-1bobo 26
Thread-1bobo 28
Thread-1bobo 30
Thread-1bobo 32
Thread-1bobo 34
Thread-1bobo 36
Thread-1bobo 38
Thread-1bobo 40
Thread-1bobo 42
Thread-1bobo 44
Thread-1bobo 46
Thread-1bobo 48
Thread-1bobo 50
Thread-1bobo 52
Thread-1bobo 54
Thread-1bobo 56
Thread-1bobo 58
Thread-1bobo 60
Thread-1bobo 62
Thread-1bobo 64
Thread-1bobo 66
Thread-1bobo 68
Thread-1bobo 70
Thread-1bobo 72
Thread-1bobo 74
Thread-1bobo 76
Thread-1bobo 78
Thread-1bobo 80
Thread-1bobo 82
Thread-1bobo 84
Thread-1bobo 86
Thread-1bobo 88
Thread-1bobo 90
Thread-1bobo 92
Thread-1bobo 94
Thread-1bobo 96
Thread-1bobo 98
Thread-1bobo 100Process finished with exit code 0
两种线程创建方法,
共同点:都需要重写Run()方法,但也不尽相同。一个是继承Thread方法里面的 Run(); 一个是实现 Runnable接口的Run()
创建的线程对象,都是Thread类以及其子类的实例。
启动Thread调用的都是Thread里面的start
不同点:一个是继承于Thread类,一个是实现Runnable接口
==更建议使用Runnable接口的方式。==原因:1.避免类的单继承局限性 2.当遇到多个线程共享数据时候更好处理 3.实现代码和数据分离
联系:打开Thread类,就会发现最上面写的是
public class Thread implements Runnable {...
Thread类自己也是继承了Runnable接口的实现类。