说到程序,离不开进程和线程这两个概念。那么这两者分别有什么作用和区别呢?
进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe文件的运行)。
线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。如果把进程理解为在逻辑上操作系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。例如,假设用户启动了一个窗口中的数据库应用程序,操作系统就将对数据库的调用表示为一个进程。假设用户要从数据库中产生一份工资单报表,并传到一个文件中,这是一个子任务;在产生工资单报表的过程中,用户又可以输人数据库查询请求,这又是一个子任务。这样,操作系统则把每一个请求――工资单报表和新输人的数据查询表示为数据库进程中的独立的线程。线程可以在处理器上独立调度执行,这样,在多处理器环境下就允许几个线程各自在单独处理器上进行。操作系统提供线程就是为了方便而有效地实现这种并发性。
一个cpu在任意时刻,只能执行一个线程任务,我们平时一边浏览网页,一边听音乐的场景其实是依赖cpu高速地线程间切换实现地,这也引出了一组容易混淆地概念:并发(Concurrency)和并行(Parallelism)。 它们都可以表示两个或者多个任务一起执行,但是偏重点有些不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的,而并行是真正意义上的“同时执行”,它依赖于多核cpu而实现。
引入线程的好处:
进程和线程的关系:
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。 下图显示了一个线程完整的生命周期:
Java 提供了三种创建线程的方法:
创建一个线程的第一种方法是创建一个类并继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。 请看下面实例:
/**
* @author guozhengMu
* @version 1.0
* @date 2019/11/2 14:31
* @description
* @modify
*/
public class ThreadTest {
public static void main(String[] args) {
MyThread thread1 = new MyThread("Thread-A");
MyThread thread2 = new MyThread("Thread-B");
System.out.println();
thread1.start();
thread2.start();
}
}
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
System.out.println("创建线程 " + name);
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("运行线程 " + name + " " + i);
// 线程休眠,增强线程交互执行的效果
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("\r\n退出线程 " + name);
}
}
运行结果如下:
创建线程 Thread-A
创建线程 Thread-B
运行线程 Thread-B 0
运行线程 Thread-A 0
运行线程 Thread-A 1
运行线程 Thread-B 1
运行线程 Thread-A 2
运行线程 Thread-B 2
退出线程 Thread-B
退出线程 Thread-A
多运行几次,会发现运行线程部分的结果是随机的,这也印证了多线程执行顺序的不确定性。
创建一个线程第二种方法是创建一个实现 Runnable 接口的类。
/**
* @author guozhengMu
* @version 1.0
* @date 2019/11/2 14:31
* @description
* @modify
*/
public class ThreadTest {
public static void main(String[] args) {
MyRunnable runnable1 = new MyRunnable("Thread-A");
MyRunnable runnable2 = new MyRunnable("Thread-B");
System.out.println();
runnable1.start();
runnable2.start();
}
}
class MyRunnable implements Runnable {
private Thread thread;
private String name;
public MyRunnable(String name) {
this.name = name;
System.out.println("创建线程 " + name);
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("运行线程 " + name + " " + i);
// 线程休眠,增强线程交互执行的效果
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("\r\n退出线程 " + name);
}
public void start() {
System.out.println("启动线程 " + name);
if (thread == null) {
thread = new Thread(this, name);
thread.start();
}
}
}
运行结果:
创建线程 Thread-A
创建线程 Thread-B
启动线程 Thread-A
启动线程 Thread-B
运行线程 Thread-B 0
运行线程 Thread-A 0
运行线程 Thread-A 1
运行线程 Thread-B 1
运行线程 Thread-B 2
运行线程 Thread-A 2
退出线程 Thread-A
退出线程 Thread-B
第三种方法是通过 Callable 和 Future 创建线程,前面两种都是无返回值,而这种方法适合获取线程执行结果。基本步骤如下:
/**
* @author guozhengMu
* @version 1.0
* @date 2019/11/2 18:36
* @description
* @modify
*/
public class CallableTest implements Callable<Integer> {
public static void main(String[] args) {
CallableTest callableTest = new CallableTest();
FutureTask<Integer> ft = new FutureTask<>(callableTest);
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " 的变量i的值" + i);
if (i == 5) {
new Thread(ft, "有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:" + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Integer call() {
int i = 0;
for (; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
}
return i;
}
}
输出结果:
main 的变量i的值0
main 的变量i的值1
main 的变量i的值2
main 的变量i的值3
main 的变量i的值4
main 的变量i的值5
main 的变量i的值6
main 的变量i的值7
main 的变量i的值8
main 的变量i的值9
有返回值的线程-0
有返回值的线程-1
有返回值的线程-2
有返回值的线程-3
有返回值的线程-4
有返回值的线程-5
有返回值的线程-6
有返回值的线程-7
有返回值的线程-8
有返回值的线程-9
子线程的返回值:10
下表列出了Thread类的一些重要方法:
方法 | 描述 |
---|---|
public void run() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
public void start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
public final void setName(String name) | 改变线程名称,使之与参数 name 相同。 |
public final void setPriority(int priority) | 更改线程的优先级。 |
public final void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程。 |
public final void join(long millisec) | 等待该线程终止的时间最长为 millis 毫秒。 |
public void interrupt() | 中断线程。 |
public final boolean isAlive() | 测试线程是否处于活动状态。 |
public static void yield() | 让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。 |
public static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 |
public static boolean holdsLock(Object x) | 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
public static void dumpStack() | 将当前线程的堆栈跟踪打印至标准错误流。 |
一些重要方法的使用实例待完善…