目录
多线程
一、前言
多线程是Java语言的重要特性,大量应用于网络编程、服务器端程序的开发等。我们可以上万人同时访问某个网站,也是基于网站服务器的多线程原理。
二、进程和线程
进程:程序是一个静态的概念,一般对应于操作系统中的一个可执行文件,比如:我们要启动war 3打游戏,则双击war 3的可执行程序,将war3的程序加载到内存中,开始执行程序,这就产生了进程。 执行中的程序叫做进程(Process),是一个动态的概念。
进程的特点:
(1)进程是程序的一次动态执行过程, 占用特定的地址空间。
(2)每个进程由3部分组成:cpu、data、code。每个进程都是独立的,保有自己的cpu时间,代码和数据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这3样东西,这样的缺点是:浪费内存,cpu的负担较重。
(3)多任务(Multitasking)操作系统将CPU时间动态地划分给每个进程,操作系统同时执行多个进程,每个进程独立运行。以进程的观点来看,它会以为自己独占CPU的使用权。
(4)进程间的切换会有较大的开销。
线程(thread)是指一个进程中开辟多条路径,可以理解为独立的执行路径,充分利用CPU。
线程的特点:
(1)每个进程至少开辟1个线程,可以产生多个线程。同一进程的多个线程也可以共享此进程的某些资源(比如:代码、数据)。
(2)main()称为主线程,为系统的入口点,用于执行整个程序。
(3)在一个进程中,如果开辟了多个线程,线程的运行则由调度器安排调度,先后顺序是由操作系统决定的,不能人为干预。
(4)对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
(5)线程会带来额外的开销,如cpu调度时间,并发控制开销。
(6)加载和存储主内存不当会造成数据不一致。
(7)线程的启动、中断、消亡,消耗的资源非常少。
总结:
(1)进程可以理解为资源分配的单位,线程是调度和执行的单位。
(2)真正的多线程是指有很多CPU,很多时候多线程是模拟出来的。
(3)系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存,线程只能共享它所属进程的资源。
特殊的一种线程:守护线程:
(1)守护线程是为其他线程服务的线程;
(2)所有非守护线程都执行完毕后,虚拟机退出;
(3)守护线程不能持有需要关闭的资源(如打开文件等)
三、多线程创建的方法
三种创建多线程的方法
(1)继承Thread类
(2)实现Runnable接口 (最常用)
(3)实现Callable接口
(1)继承Thread类实现多线程的步骤
1. 在Java中负责实现线程功能的类是java.lang.Thread 类。
2. 可以通过创建Thread的实例来创建新的线程。
3. 每个线程都是通过某个特定的Thread对象所对应的方法run( )来完成其操作的,方法run( )称为线程体。
4. 通过调用Thread类的start()方法来启动一个线程。
package Thread;
/*
* 创建线程方式一
* 1、创建:继承Thread+重写run
* 2、启动:创建子类对象+start
* */
public class StartThread extends Thread {
//线程的入口点
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("一边听歌");
}
}
public static void main(String[] args) {
//启动线程
StartThread st = new StartThread();
st.start(); //开启一个新线程
for(int i=0;i<5;i++){
System.out.println("一边coding");
}
}
}
运行结果: 或
等 因为线程运行的先后顺序由操作系统决定。
(2)通过多线程下载网络图片
下载工具代码
import org.apache.commons.io.FileUtils;
import java.io.IOException;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
/*
* 下载图片
* */
public class WebDownloader {
//下载工具
public void download(String url, String name){
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch (MalformedURLException e){
e.printStackTrace();
System.out.println("不合法的url");
}catch (IOException e){
e.printStackTrace();
System.out.println("图片下载失败了");
}
}
}
调用下载工具,实现多线程下载图片
public class TDownloader extends Thread {
private String url; //运程路径
private String name; //存储名字
public TDownloader(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader wd = new WebDownloader();
wd.download(url,name);
System.out.println(name);
}
public static void main(String[] args) {
TDownloader td1 = new TDownloader("https://tse3-mm.cn.bing.net/th/id/OIP.Tlumx9iXVC4rqPbISLZaRQHaF7?w=200&h=160&c=7&o=5&pid=1.7","1.jpg");
TDownloader td2 = new TDownloader("https://tse1-mm.cn.bing.net/th/id/OIP.qwg11Rs1diETRMdP0WS6zQHaE-?w=200&h=134&c=7&o=5&pid=1.7","2.jpg");
TDownloader td3 = new TDownloader("https://tse2-mm.cn.bing.net/th/id/OIP.zLNUiUPPCzf21pgCBJdXwAHaJy?w=200&h=265&c=7&o=5&pid=1.7","3.jpg");
//启动三个线程,这三个的执行完成顺序是不固定的
td1.start();
td2.start();
td3.start();
}
}
(3)实现Runnable接口完成多线程 (最常用)
/*
* 创建线程方式二
* 1、创建:实现Runnable接口+重写run
* 2、启动:创建实现类对象+Thread对象+start
* */
public class StartRun implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println("一边听歌");
}
}
public static void main(String[] args) {
//创建实现类对象
StartRun sr = new StartRun();
//创建代理类对象
Thread t = new Thread(sr);
//启动
t.start();
// 等于匿名使用 new Thread(new StartRun()).start();
for(int i=0;i<5;i++){
System.out.println("一边coding");
}
}
}
这个方法的好处就是避免单继承的局限性,优先使用接口,而且这个方法会方便共享资源
启动线程简写版:new Thread(new StartRun()).start
(4)共享资源(模拟抢票)
/*
* 共享资源(这里需要保证线程安全)
* */
public class Web12306 implements Runnable{
//票数
private int ticketNums = 10;
@Override
public void run() {
while(true){
if(ticketNums==0){
break;
}
System.out.println(Thread.currentThread().getName()+"抢到了"+ticketNums--+"号票");
}
}
public static void main(String[] args) {
//1份资源
Web12306 web = new Web12306();
//多个代理
new Thread(web,"抢票1").start();
new Thread(web,"抢票2").start();
new Thread(web,"抢票3").start();
}
}
运行效果:
(5)实现Callable接口完成多线程(了解)
一般在高并发编程中才用。
Callable接口特点:
- Callable规定的方法是call(),而Runnable规定的方法是run().
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
- call()方法可抛出异常,而run()方法是不能抛出异常的。
- 运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。
- 它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
- 通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。