线程与进程
操作系统中会存在多个进程:这些进程包括系统进程、以及用户进程。
系统进程指:操作系统内部建立的进程。
用户进程指:用户程序建立的进程。
进程和线程的区别:
1.进程和进程之间不共享内存,进程是在独立的内存空间中运行的。
2.而线程则可以共享系统分配给这个进程的内存空间。
3.线程不仅能共享进程的内存,也拥有自己的内存空间,这段空间叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据。
start和run
Java通过Thread类将线程所必需的功能都封装了起来。
线程执行函数就是run()方法
Thread还有一个start()方法,这个方法是建立线程,调用strat()方法后,如果线程建立成功,则程序会自动调用Thread类的run()方法。
注:strat()方法,线程建立成功,会自动调用run方法。执行线程必须调用start()加入调度器中,不一定立即执行,系统安排调度分配执行,直接调用run()不是开启多线程,而是普通方法调用
任何继承Thread类的都可以通过调用start方法【建立】线程,如果希望运行自己编写线程的执行函数,则要覆盖Thread类的run()方法。
/**
* 多线程测试
* 第一种方法继承Thread
* 多次执行结果不同,两个线程同时执行
* strat()方法不保证立即运行,由CPU决定
*/
public class ThreadTest extends Thread {
@Override
public void run(){
for (int i = 0;i<=10;i++){
System.out.println(i);
}
}
public void run2(){
for (int i = 0;i<=10;i++){
System.out.println(i+"run2.............");
}
}
//显式无参构造
ThreadTest(){
System.out.println("创建了ThreadTest");
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
threadTest.run2();
}
}
图片下载案例
/**
* 图片下载案例
*/
public class ThreadTestPictureDown {
/**
* 下载方法
* @param url
* @param name
*/
public void download(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("不合法的URL,下载失败");
}
}
}
本质上和之前的案例没什么区别,唯一有需要注意的地方是FileUtils这个工具类,能实现下载文件的功能。
public class TDownLoad extends Thread{
private String url;
private String name;
public TDownLoad(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
ThreadTestPictureDown threadTestPictureDown = new ThreadTestPictureDown();
threadTestPictureDown.download(url,name);
System.out.println("下载"+name+"图片完成");
}
public static void main(String[] args) {
TDownLoad t1 = new TDownLoad("https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF","t1.jpg");
TDownLoad t2 = new TDownLoad("https://t7.baidu.com/it/u=4036010509,3445021118&fm=193&f=GIF","t2.jpg");
TDownLoad t3 = new TDownLoad("https://t7.baidu.com/it/u=963301259,1982396977&fm=193&f=GIF","t3.jpg");
t1.start();
t2.start();
t3.start();
}
}
打印结果图片3、图片1、图片2
Thread和Runnable
能够通过继承Thread类、Runnable接口实现线程类,Thread类其实也实现了Runnable接口,Thread作为实现类功能更加强大,而Runnable接口源码中只有一个run方法。但是Java中只能够实现单继承,却可以实现多个接口,因此在使用时,注意合理选择。
在继承Thread类时,如果没有为这个类命名,会自动使用默认线程名Thread-N,N代表线程建立的顺序,是一个不重复的整数。
注意:通过实现Runnable接口创建线程类时,依然需要创建Thread对象,将线程类作为参数传入Thread对象中,并调用start方法,直接调用run方法并不能够启动线程,依然是在主线程中启动run方法。
此时可以多个代理。下面抢票小案例可以看出。
public class ThreadTest01 implements Runnable{
@Override
public void run(){
for (int i = 0;i<=10;i++){
System.out.println(i);
}
}
public void run2(){
for (int i = 0;i<=10;i++){
System.out.println(i+"run2.............");
}
}
//显式无参构造
ThreadTest01(){
System.out.println("创建了ThreadTest");
}
public static void main(String[] args) {
ThreadTest01 threadTest01 = new ThreadTest01();
new Thread(threadTest01).start();
threadTest01.run2();
}
}
抢票小案例
public class ThreadTest01 implements Runnable{
private int tickNums = 99;
@Override
public void run(){
while (true){
if (tickNums<0){
break;
}
System.out.println(Thread.currentThread().getName()+"--->"+tickNums--);
}
}
//显式无参构造
ThreadTest01(){
System.out.println("创建了ThreadTest");
}
public static void main(String[] args) {
ThreadTest01 threadTest01 = new ThreadTest01();
//多个代理,加入名称区分
new Thread(threadTest01,"thread01").start();
new Thread(threadTest01,"thread02").start();
new Thread(threadTest01,"thread03").start();
}
}
但这样是有问题的,现实情况中网络存在延迟,就可能出现负数问题。此时还没出现,下面加入线程睡眠200毫秒,模拟这种情况。
@Override
public void run(){
while (true){
if (tickNums<0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+tickNums--);
}
}
这样在控制台打印时就会出现负数的情况。此时也算是并发问题,后续需要保证线程安全。runable接口可以共享资源,但也会出现资源抢夺的问题。