Java----线程学习(线程的创建)

Java----线程学习

1.概念(这是与操作系统相关的知识)
◆说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
◆而进程则是执行程序的一-次执行过程,它是一个动态的概念。是系统资源分配的单位。
◆通常在一一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
◆线程就是独立的执行路径;
◆在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程(垃圾回收,jvm守护线程);
◆main()称之为主线程,为系统的入口,用于执行整个程序;
◆在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
◆对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
◆线程会带来额外的开销,如cpu调度时间,并发控制开销。
◆每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

2.线程创建
(1) 继承Thread类
-自定义一个类,继承Thread类
-重写run()方法,编写线程执行体
-创建线程对象,调用start()方法启动线程

例子如下:
package edu.ncu.dong;
//方式一:继承Thread类
//1.继承Thread
public class TestThread1 extends Thread{
    //2.重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run()方法的第"+i+"次");
        }
    }
    public static void main(String[] args) {
        //创建线程对象
        TestThread1 testThread=new TestThread1();
        //调用start()方法启动线程
        testThread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程main()"+i+"次");
        }
    }
}

执行这个代码之后可以看出:确实是多线程,因为输出语句是交替进行的
执行结果

这里我们改变代码,不使用start()方法,我们直接调用testThread.run();会发现,代码按照main()方法的正常逻辑运行了:
执行结果
测试题:多线程从网络上下载图片

package edu.ncu.dong;
import org.apache.commons.io.FileUtils;//这是apache的jar包
import java.io.File;
import java.io.IOException;
import java.net.URL;
//第1种方法测试题目
public class TestThread1_test extends Thread{
    private String url;
    private String file;
    public TestThread1_test(String url,String file){
        this.url=url;
        this.file=file;
    }
    //线程执行体
    @Override
    public void run() {
        WebDownloader downloader=new WebDownloader();
        downloader.downloader(url,file);
        System.out.println("下载了:"+file);
    }
    public static void main(String[] args) {
        //我在云服务器上找的
        String url1="http://gedaduck.top/res/image/lunbo3.jpg";
        String file1="myDownload1.jpg";
        String url2="http://gedaduck.top/res/image/lunbo1.jpg";
        String file2="myDownload2.jpg";
        String url3="http://gedaduck.top/res/image/lunbo2.jpg";
        String file3="myDownload3.jpg";
        //创建三个线程
        TestThread1_test test1=new TestThread1_test(url1,file1);
        TestThread1_test test2=new TestThread1_test(url2,file2);
        TestThread1_test test3=new TestThread1_test(url3,file3);
        //启动线程
        test1.start();
        test2.start();
        test3.start();
    }
}
//下载器:封装了apache的工具
class WebDownloader{
    //下载方法
    public void downloader(String url,String file) {
        try{
            FileUtils.copyURLToFile(new URL(url),new File(file));
        }catch (IOException e){
            e.printStackTrace();
            System.out.println("IO异常");
        }
    }
}

查看下载结果,三个线程的结束顺序和启动顺序不同,也说明了这是个多线程,主要根据cpu的调度算法决定到底运行那个线程:
运行结果
确实下载了三张图片:
在这里插入图片描述
(2)实现Runnable接口
-实现Runnable接口
-重写run方法
-创建Runnable接口的实现类对象中
-将对象交由Thread对象,调用start()方法开启线程

实现的例子如下:
package edu.ncu.dong;

//方式二:实现Runnable接口,重写run方法,执行线程放入Runnable接口的实现类中
//1.实现Runnable接口
public class TestThread2 implements Runnable{
    //2.重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("这是Runnable()接口实现类"+i+"次");
        }
    }
    public static void main(String[] args) {
        //3.创建Runnable接口实现类对象
        TestThread2 test=new TestThread2();
        //4.创建线程对象,通过Thread调用start()开启自定义的线程
        Thread thread=new Thread(test);
        thread.start();
        for (int i = 0; i < 50; i++) {
            System.out.println("这是主线程执行第"+i+"次");
        }
    }
}

运行结果如图:可以看出,main()方法和自定义的run()方法是没有先后顺序的,全看cpu的调度。
运行截图
咱们点进Thread类的源码:可以看出,Thread也是实现了Runnable接口
源码
再点开Runnable接口一看:这是一个java.lang下的函数式编程接口,只有一个run()方法
源码
所以第一种和第二种线程创建方式如此的相似,第一种是我们继承了Thread类,而Thread类是实现了Runnable接口,我们通过Thread继承类的start()方法启动线程;第二种是我们直接实现Runnable接口,然后用Thread类代理调用start()方法启动线程
这里的代理是静态代理,所以以这个例子讲一下静态代理:
1.真实对象和代理对象都要实现同一个接口:TestThread2类和Thread类都实现了Runnable接口
2.代理对象去代理真实对象做任务:Thread 实例对象代理TestThread2的实例对象运行了start()方法,注意这个方法是代理对象的方法,执行的却是TestThread2实例对象的进程,这就是静态代理。

	Thread thread=new Thread(new TestThread2());
   	thread.start();

我们推荐使用Runnable接口:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
这里给一个第二种方式的例子:模拟抢票

package edu.ncu.dong;
//第2种方法测试:模拟抢票操作
public class TestThread2_test implements Runnable{
    private Integer tickets=20;//当前票数
    //线程执行体
    @Override
    public void run() {
        while (true) {
            if ((--tickets) < 0)
                break;//这里先判断:至少还有一张票,才模拟下面的抢票操作
            System.out.println(Thread.currentThread().getName() + "执行了抢票操作,还有第" + tickets + "张票");
        }
    }
    public static void main(String[] args) {
        TestThread2_test test=new TestThread2_test();
        new Thread(test,"东东").start();//第二个参数是自定义线程的名字
        new Thread(test,"兰兰").start();
        new Thread(test,"丽丽").start();
    }
}

这就是同一个对象:TestThread2_test被三个线程使用,更加方便
结果
从结果看出了问题,明明标红处,已经是第0张票了,为什么后面还能抢票?这个就涉及线程的同步与互斥问题,可以加锁解决,这里不多说。
(3)实现Callable接口(了解,加分项)
-实现Callable接口,需要返回值类型
-重写call方法,需要抛出异常
-创建目标对象
-创建执行服务: ExecutorService ser =Executors.newFixedThreadPool(1);
-提交执行: Future result1 = ser.submit(t1);
-获取结果: boolean r1 = result1.get()
-关闭服务: ser.shutdownNow();

 例子:改写上面的图片下载的例子
package edu.ncu.dong;
import java.util.concurrent.*;
//第三种线程创建方式:实现Callable接口
//1.实现Callable接口 Callable<boolean>这里面的返回值要和call()返回值类型相同
public class TestCallable implements Callable<Boolean> {
    private String url;
    private String file;
    public TestCallable(String url,String file){
        this.url=url;
        this.file=file;
    }
    //2.线程执行体:重写call()方法,返回值和类定义时的类型相同
    @Override
    public Boolean call() throws Exception {
        WebDownloader downloader=new WebDownloader();
        downloader.downloader(url,file);
        System.out.println("下载了:"+file);
        return true;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //随便找的云服务器网图
        String url1="http://gedaduck.top/res/image/lunbo3.jpg";
        String file1="myDownload1.jpg";
        String url2="http://gedaduck.top/res/image/lunbo1.jpg";
        String file2="myDownload2.jpg";
        String url3="http://gedaduck.top/res/image/lunbo2.jpg";
        String file3="myDownload3.jpg";
        //创建三个线程
        TestCallable test1=new TestCallable(url1,file1);
        TestCallable test2=new TestCallable(url2,file2);
        TestCallable test3=new TestCallable(url3,file3);
        //3.创建执行服务
        ExecutorService service= Executors.newFixedThreadPool(3);//参数是线程池的大小
        //4.提交执行
        Future<Boolean> result1=service.submit(test1);
        Future<Boolean> result2=service.submit(test2);
        Future<Boolean> result3=service.submit(test3);
        //5.获取结果
        boolean r1=result1.get();
        boolean r2=result2.get();
        boolean r3=result3.get();
        //6.关闭服务
        service.shutdownNow();
    }
}

注:这是我根据网络视频教程的学习笔记,未用于商业用途

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值