概述
进程,线程和多线程
进程: 是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元;
线程: 就是进程中的一个独立控制单元,线程在控制着进程的执行。一个进程中至少有一个线程。
多线程: 一个进程中不只有一个线程。
多线程优势
①、为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时 可以执行其他任务,而不需要等待;
②、进程之间不能共享数据,线程可以;
③、系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;
④、Java语言内置了多线程功能支持,简化了java多线程编程。
拓展
- 并发与并行:
. 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
. 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那 么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。 - 线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。
- 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确. 在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
核心概念
- 线程是独立的执行路径.
- 在程序执行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程.
- main()称之为主线程,为系统的入口,用于执行整个程序.
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与系统紧密相关的,先后顺序是不能人为干预的.
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制.
- 线程会带来额外的开销,如CPU调度时间,并发数控制开销.
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致.
继承Thread类
- 继承Thread类,重写run()方法,调用start开启线程
- 注意:线程开启不一定立即执行,由CPU调度执行
[代码实例]
package com.TestThread;
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
//注意:线程开启不一定立即执行,由CPU调度执行
public class ThreadDemo1 extends Thread {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在写代码--"+i);
}
}
public static void main(String[] args) {
//main方法线程,主线程
//创建一个线程对象
ThreadDemo1 threadDemo1 = new ThreadDemo1();
//调用start()方法开启线程
threadDemo1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在学习多线程--"+i);
}
}
}
多线程原理
jvm中的多线程有很多,其中有负责定义代码运行的线程(这个从main方法开始执行的主线程),也有垃圾回收的线程(因为CPU的切换的不确定所以不定时执行。或者达到某个条件下执行)。
多线程的运行的根据CPU的切换完成的,也就是说怎么切换CPU说了算,所以多线程运行是随机的(CPU快速切换造成的)。
每次运行结果不一定相同,因为随机性造成的。
没一个线程都有运行的代码内容。这个称为线程的任务。创建一个线程就是为了去运行指定的任务代码。
网图下载
前提需要需要导commons-io-2.6.jar 测试代码如下:
import org.apache.commons.io.FileUtils;
//练习Thread,实现多线程同步下载
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class FileDownLoad extends Thread {
String url; //网络图片地址
String name; //保存的文件名
public FileDownLoad (String url, String name){
this.url = url;
this.name = name;
}
//下载图片的线程执行体
@Override
public void run() {
FileLoad fileLoad = new FileLoad();
fileLoad.dowload(url,name);
System.out.println("图片下载成功了!!!");
}
public static void main(String[] args) {
FileDownLoad t1 = new FileDownLoad("http://img.alicdn.com/tfscom/i1/821121421/O1CN012nazhz1MMrI77IGuX_!!821121421.jpg_250x250xz.jpg","4.jpg");
FileDownLoad t2 = new FileDownLoad("http://gw.alicdn.com/bao/uploaded/TB1JJgEj8r0gK0jSZFnSuvRRXXa.jpg_440x440q75","5.jpg");
FileDownLoad t3 = new FileDownLoad("//gw.alicdn.com/bao/uploaded/TB1.t7Aj1L2gK0jSZFmSuw7iXXa.jpg_440x440q75","6.jpg");
t1.start();
t2.start();
t3.start();
}
}
class FileLoad {
//下载方法
public void dowload(String url, String name){
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch (IOException e){
e.printStackTrace();
System.out.println("IO异常!!!");
}
}
}
实现Runnable
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
推荐使用Runnable对象,因为Java单继承的局限性
[代码实例]
package com.TestThread;
//创建线程方式2:实现runnable接口,重写run方法,执行线程
public class ThreadDemo2 implements Runnable {
@Override
public void run() {
//run方法体
for (int i = 0; i < 20; i++) {
System.out.println("我在写代码--"+i);
}
}
public static void main(String[] args) {
//创建runnable接口实现类对象
ThreadDemo2 threadDemo2 = new ThreadDemo2();
//创建线程对象,通过线程对象来开启我们的线程
new Thread(threadDemo2).start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在学习多线程--"+i);
}
}
}
小结
- 继承Thread类
1. 子类继承Thread类具备多线程能力
2. 启动线程:子类对象.start()
3. 不建议使用:避免OPP单继承局限性 - 实现Runnable接口
1. 实现接口Runnable具有多线程能力
2. 启动线程:传入目标对象+Thread对象.start()
3. 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用