一、线程和多进程简介
1、多进程的概念
- 当前的操作系统都是多任务OS
- 每个独立执行的任务就是一个进程
- OS将时间划分为多个时间片(时间很短)
- 每个时间片内将CPU分配给某一任务,时间片结束,CPU将自动回收,再分配给另外任务。从外部看,所有的任务是同时执行。但是在CPU上,任务是按照串行依次运行(单核CPU)。如果是多核CPU,多个进程任务可以并行。但是在单核上,多进程只能串行执行。
- 多进程的优点:
- 可以同时运行多个任务
- 程序因I/O堵塞时,可以释放CPU,让CPU为其他程序服务
- 当系统有多个CPU时,可以为多个程序同时服务
- 我们使用的CPU不在提高频率,而是提高核数
- 2005年Herb Sutter的文章The free lunch is over,指明多核和并发程序才是提高程序性能的唯一办法。
- 串行程序,是指程序只能在单核上运行,无法利用多个CPU;
- 并行程序,是指程序可以利用多个计算核运行,加快计算速度。
- 多进程的缺点
- 太笨重,不好管理
- 不好切换
2、多线程概念
- 一个程序可以包括多个子任务,可串行/并行
- 每个子任务可以成为一个线程
- 如果一个子任务堵塞,程序可以将CPU调度到另外一个子任务进行工作。这样CPU还是保存在本程序中,而不是被调度到别的程序(进程)去。这样,提高本程序所获得CPU时间的和利用率。
3、多进程vs多线程
- 线程共享数据
- 线程通讯更高效
- 线程更轻量级,更容易切换
- 多个线程更容易管理
多进程示列程序
public class ProcessDemo1 {
public static void main(String[] args) {
while(true)
{
int a = (int) (Math.random() * 100);
System.out.println(" main thread is running " + a);
try {
Thread.sleep(5000); //5000毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
sleep函数目的是让当前的程序睡眠5000毫秒
从输出结果来看,有两控制台。这里右键->run了两次,所以有两个Java程序运行(两个Java.exe在运行)。它们分别有各自的运行空间和输出位置,两个Java程序运行是互不干扰的,相互之间通讯也很困难。多进程实际上是多个Java程序在运行
注: Eclipse里面关闭程序,一定要把所有的console窗口都关闭,防止有进程还在运行
多线程示列程序
public class ThreadDemo1
{
public static void main(String args[]) throws Exception
{
new TestThread1().start();
while(true)
{
System.out.println("main thread is running");
Thread.sleep(1000);
}
}
}
class TestThread1 extends Thread
{
public void run()
{
while(true)
{
System.out.println(" TestThread1 is running");
try {
Thread.sleep(1000); //1000毫秒=1s
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
从运行结果可以看到main thread和test thread1交替出现,而且是无规律的出现,有可能一个出现一次另外一个出现两次,本程序只有一个Java.exe在运行。这里说明,程序不一定要执行完第一行才能执行第二行。程序不在固守上一行到下一行顺序执行,可以两段代码独立的运行。
二、多线程实现
Java多线程创建的两种方法
1.java.lang.Thread
Thread方法
- 通过继承Thread类创建线程,通过start方法启动线程的run方法
public class Thread1 extends Thread{
public void run()
{
System.out.println("hello");
}
public static void main(String[] a)
{
new Thread1().start();
}
}
2、java.lang.Runnable接口
Runnable方式
- 通过实现Runnable接口创建线程
- 实现Runnable的对象必须包装在Thread类里面,才可以启动
- 通过start方法启动线程的run方法
public class Thread2 implements Runnable{
public void run()
{
System.out.println("hello");
}
public static void main(String[] a)
{
new Thread(new Thread2()).start();
}
}
- Runnable接口是Java的四大接口之一
- Java主要的四个接口
- Clonable,用于对象克隆
- Comparable,用于对象比较
- Serializable,用于对象序列化
- Runnable,用于对象线程化
Java多线程启动
1、启动
- start启动方法,会自动以新进程调用run方法
- 直接调用run方法,将变成串行执行
- 同一线程,多次start会报错,只执行第一次start方法
- 多个线程启动,其启动的先后顺序是随机的
- 线程无需关闭,只要其run方法执行结束后自动关闭
- main函数(线程)可能早于新线程结束,整个查询并不终止
- 整个程序终止是等所有的线程都终止(包括main函数线程)
2、多线程的运行规则
规则一
- 若调用run方法,来启动run方法,将会是串行运行
public class ThreadDemo0
{
public static void main(String args[]) throws Exception
{
new TestThread0().run();
while(true)
{
System.out.println("main thread is running");
Thread.sleep(10);
}
}
}
class TestThread0
{
public void run()
{
while(true)
{
System.out.println(" TestThread1 is running");
try {
Thread.sleep(1000); //1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
程序一直输出 TestThread1 is running,而没有输出main thread is running说明程序是串行执行,new TestThread0().run();语句还没运行完。
- 若调用start方法,来启动run方法,将会是并行运行(多线程运行)
把
new TestThread0().run();
修改为
new TestThread1().start();
两个线程交替运行
规则二:
- main线程可能早于子线程结束
- main线程和子线程都结束,整个程序才算终止
public class ThreadDemo2
{
public static void main(String args[]) throws InterruptedException
{
new TestThread2().start();
// while(true)
// {
// System.out.println("main thread is running");
// Thread.sleep(1000);
// }
}
}
class TestThread2 extends Thread
{
public void run()
{
while(true)
{
System.out.println("TestThread2" +
" is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
把这段代码注释了
// while(true)
// {
// System.out.println("main thread is running");
// Thread.sleep(1000);
// }
在执行new TestThread2().start()语句后;main函数(main线程)就结束了,而子线程还在运行。说明主线程可以早于子线程结束
规则三:
- 实现Runnable的对象必须包装在Thread类里面,才可以启动
- 不能直接对Runnable的对象进行start方法。
public class ThreadDemo3
{
public static void main(String args[])
{
//new TestThread3().start();
//Runnable对象必须放在一个Thread类中才能运行
TestThread3 tt= new TestThread3();//创建TestThread类的一个实例
Thread t= new Thread(tt);//创建一个Thread类的实例
t.start();//使线程进入Runnable状态
while(true)
{
System.out.println("main thread is running");
try {
Thread.sleep(1000); //1000毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class TestThread3 implements Runnable //extends Thread
{
//线程的代码段,当执行start()时,线程从此出开始执行
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName() +
" is running");
try {
Thread.sleep(1000); //1000毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
System.out.println(Thread.currentThread().getName() +
" is running");
该方法获取并输出当前线程的名称。JVM会自动给每个子线程自动派发一个名字。这里线程名为Thread-0。
规则四:
- 一个线程对象不能多次start,多次start会报出异常。
TestThread4 t=new TestThread4();
t.start();
t.start();
t.start();
- 多个线程都start后,先执行哪一个,完全有JVM/OS来主导,程序员无法指定。
两种多线程实现方法对比
Thread vs Runnable
- Thread占据了父类的名额(使继承自Thread类的类不能再继承于其他类),不如Runnable方便
- Thread类实现Runnable
- Runnable启动时需要Thread类的支持
- Runnable更容易实现多线程中资源共享
结论: 建议实现Runnable接口来完成多线程