多线程程序的引入图解
1 package cn.itcast_01;
2 /*
3 * 进程:
4 * 正在运行的程序,是系统进行资源分配和调用的独立单位。
5 * 每一个进程都有它自己的内存空间和系统资源。
6 * 线程:
7 * 是进程中的单个顺序控制流,是一条执行路径。
8 * 是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
9 *
10 * 一个进程如果只有一条执行路径,则称为单线程程序。
11 * 一个进程如果有多条执行路径,则称为多线程程序。
12 *
13 * 举例:
14 * 扫雷程序,迅雷下载
15 *
16 * 大家注意两个词汇的区别:并行和并发。
17 * 并行:前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
18 * 并发:后者是物理上同时发生,指在某一个时间点同时运行多个程序。
19 *
20 *
21 *
22 * Java程序的运行原理:
23 * 通过java命令会启动 java虚拟机。启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。
24 * 该进程会自动启动一个 “主线程”,然后主线程去调用某个类的 main方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
25 *
26 * 思考题:
27 * jvm虚拟机的启动是单线程的还是多线程的?
28 * 多线程的。
29 * 原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
30 * 现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
31 */
32 public class MyThreadDemo {
33 public static void main(String[] args) {
34 System.out.println("hello");
35 new Object(); // 造对象
36 new Object(); // 造对象
37 new Object(); // 造对象
38 new Object(); // 造对象
39 //...造很多很多对象后,如果垃圾回收线程不启动的话,内存就会溢出!
40 System.out.println("world");
41 }
42 }
代码演示:
package com.thread;
public class ThreadDemo extends Thread {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行了"+i+"次");
}
}
}
class Test{
public static void main(String[] args) {
ThreadDemo thread = new ThreadDemo();
thread.start();
}
}
运行结果:
Thread-0执行了0次
Thread-0执行了1次
Thread-0执行了2次
Thread-0执行了3次
Thread-0执行了4次
Thread-0执行了5次
Thread-0执行了6次
Thread-0执行了7次
Thread-0执行了8次
Thread-0执行了9次
代码演示:
package com.thread;
public class Runable1 implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"执行了"+i+"次");
}
}
}
class Runnable1Demo {
public Runnable1Demo() {
}
public static void main(String[] args) {
Runable1 runable1 = new Runable1();
Thread thread = new Thread(runable1);
Thread thread1 = new Thread(runable1);
thread.start();
thread1.start();
}
}
运行结果:
Thread-0执行了0次
Thread-1执行了0次
Thread-0执行了1次
Thread-1执行了1次
Thread-0执行了2次
Thread-1执行了2次
Thread-0执行了3次
Thread-1执行了3次
Thread-0执行了4次
Thread-1执行了4次
注意事项:
1:Thread类的方法:
public final String getName() 获取线程对象的名称(一般放在需要被线程执行的代run()方法里面)
public final void setName(String name) 设置线程对象的名称
对象名.setName("林青霞");
2:在不是Thread类的子类中,如何获取线程对象的名称呢?
public static Thread currentThread() 返回当前正在执行的线程对象(静态方法)
Thread.currentThread().getName()
3:该自定义的类为什么要重写run()方法?
自定义类中不是所有的代码都需要被线程执行。
而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()方法,用来包含那些需要被线程执行的代码。
注意:这里的 被线程执行 = 开一个新线程执行
4:由于自定义类实现了接口,所以就不能在自定义类中直接使用Thread类的getName()方法了,但是可以间接的使用。
Thread.currentThread().getName()
5:为什么要实现Runnable接口?
-Java不支持多继承
-不打算重写Thread类的其他方法
小问题:
1:为什么要重写run()方法?
答:run()方法里面封装的是被线程执行的代码。
2:启动线程对象用的是哪个方法?
答:start()方法
3:run()方法和start()方法的区别?
答:run()方法直接调用仅仅是普通方法。
start()方法是先启动线程,再由jvm去调用run()方法。
4:有了方式1,为什么还来一个方式2呢?
答:若自定义类MyThread类已经有一个父类了,那么它就不可以再去继承Thread类了。(java不支持多继承)
若自定义类MyRunnable类已经实现了一个接口了,那么它还可以再去实现Runnable接口。(java支持多实现)
即可以避免由于Java单继承带来的局限性。
在测试类MyThreadTest中,要想开多个线程,就要先new多个自定义类MyThread的对象,每一个自定义类MyThread的对象的成员变量都相同,这样需要在栈中开辟很多内存;
在测试类MyRunnableTest中,要想开多个线程,只需要new一个自定义类MyRunnable的对象,再new多个Thread类的对象即可,这样就大大节约了内存。
即适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码和数据有效分离(即耦合性降低),较好的体现了Java面向对象的设计思想。
public static void sleep(long millis) 单位是毫秒(该方法会抛出异常)
Thread.sleep(1000);
package com.thread;
public class Sleep implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
if (i==5){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"执行了"+i+"次");
}
}
}
class SleepDemo {
public SleepDemo() {
}
public static void main(String[] args) {
Sleep sleep = new Sleep();
Thread thread = new Thread(sleep);
thread.start();
}
}
B:线程加入
public final void join() 等待该线程终止(为了使某线程先执行完毕)(该方法会抛出异常)
对象名.join(); // 该方法必须在启动线程后调用
package com.join;
public class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(this.getName()+"执行了"+i+"次");
}
}
}
class JoinDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<5;i++){
System.out.println("主线程执行了"+i+"次");
}
System.out.println("主线程执行结束");
}
}
运行结果:
Thread-0执行了0次
Thread-0执行了1次
Thread-0执行了2次
Thread-0执行了3次
Thread-0执行了4次
Thread-0执行了5次
Thread-0执行了6次
Thread-0执行了7次
Thread-0执行了8次
Thread-0执行了9次
主线程执行了0次
主线程执行了1次
主线程执行了2次
主线程执行了3次
主线程执行了4次
主线程执行结束
C:线程礼让
public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 能够在一定的程度上,让多个线程的执行更和谐,但是不能靠它保证一个线程一次。
Thread.yield();
D:后台线程(守护线程/用户线程)
public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程
对象名.setDaemon(true); // 设置守护线程
当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。
E:中断(终止)线程(掌握)
public final void stop() 让线程停止,过时了,但是还可以使用。(为什么会过时呢?因为该方法太暴力了,具有固有的不安全性,直接把线程停止,该线程之后的代码都不能执行了)
对象名.stop();
public void interrupt() 中断线程。 把线程的状态终止,并抛出一个InterruptedException异常。
对象名.interrupt();
注意事项:
如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws。