学习日志day16(2021-07-29)(1、创建和启动线程 2、线程的生命周期 3、从浏览器多线程爬取文件)

本文详细介绍了Java中线程的创建与启动,包括继承Thread类和实现Runnable接口两种方式,以及线程的生命周期状态。讲解了同步机制如sleep()、join()、守护线程和锁的使用,还展示了线程安全的银行取款模拟。最后,通过实例展示了如何从浏览器爬取图片文件,并利用Jsoup解析HTML获取多张图片。
摘要由CSDN通过智能技术生成

学习内容:学习JavaSE(Day16)

1、创建和启动线程
2、线程的生命周期
3、从浏览器多线程爬取文件


1、创建和启动线程

(1)进程:应用程序的执行。进程和进程之间资源是相互独立的。
线程:一个进程包含多个线程,资源可以共享。

操作系统是多线程,多任务的,可以同时运行多个进程。

同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。
异步是指:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。

同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。
异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。

(2)创建线程:
方法一:继承自java.lang.Thread类,并重写run()方法

public class MyThread extends Thread{ 
    @Override  
    public void run() {   
    //线程执行的任务   
    } 
}

方法二:实现java.lang.Runnable接口,并重写run()方法

public class MyThread2 implements Runnable{ 
    @Override 
    public void run() { 
	//线程执行的任务 
    } 
}

(3)启动线程:
在其它类中调用创建好的线程。此时同时执行三个线程,main线程和调用的thread、thread2线程。
方法一:类MyThread继承了父类Thread,可以直接使用start方法。

MyThread thread = new MyThread(); 
thread.start(); 

方法二:类MyThread2调用的Runnable接口中只有run方法,没有start方法,所以启动需要借助Thread父类的start方法。

Thread thread2 = new Thread(new MyThread2());
thread2.start();

方法一继承Thread类和方法二实现Runnable接口的区别:
1.继承Thread类后,无法再继承其他类,但是实现Runnable接口,还可以继承其他类。
2.实现Runnable接口,必须通过Thread.currentThread()方法访问当前线程。继承Thread类后,可以使用this 访问当前线程

2、线程的生命周期

(1)线程的生命周期如下图所示:
在这里插入图片描述

新建状态:当采用new关键字创建一个线程对象的的时候,此时线程还没有运行。 比如:Thread t=new Thread(new MyThread2())

就绪状态:当调用了start(),系统为该线程分配除了cpu之外所有的资源。此时该线程具备运行的机会,此时处于就绪状态。此时线程是没有运行的。

运行状态:处于就绪状态的线程,获得了cpu的使用资格,才开始运行。Thread.yield() 方法,使当前线程由执行状态,变成为就绪状态,让出cpu时间,在下一个线程执行时候,此线程有可能被执行,也有可能没有被执行。

阻塞状态:一个正在运行的线程由于某种原因丧失了cpu的使用权,不能继续运行,就由运行状态进入阻塞状态。处于阻塞状态的线程在得到某个特点的事件后会转入可运行状态。
A、线程调用了sleep()方法进入睡眠状态,sleep()方法是静态的,只能通过类来调用,Thread.sleep(1000)。
B、线程执行到一个I/O操作,如果IO还没有完成,线程就被阻塞。
C、线程视图得到一个锁,而该锁正被其他的线程所持有。
D、线程执行了suspend() 方法,线程被挂起。但是容易导致死锁。已过时,基本不用。resume()方法恢复。
E、线程在执行过程中调用了其他线程的join()方法,则当前线程被阻塞,直到被join()方法加入的线程完成为止。
F、线程调用Object类中的wait()方法导致当前的线程等待,直到其他线程调用此对象的 notify() 或 notifyAll() 方法唤醒线程。

死亡状态:1.run()方法执行完成,线程正常结束
2.线程出现一个未捕获Exception或者Error
3.直接调用线程的stop()方法(禁止使用)
线程一旦死亡,则不能再次调用start()方法让其重新执行。线程和线程之间有一个线程死亡不影响其它线程继续运行。

(2)线程休眠:此时每经过1000毫秒,该线程执行一次。

public void run() {
    for(int i = 0;i < 100;i++){
        try {
            Thread.sleep(1000);//sleep方法存在一个检查时异常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("MyThread2: " + i);
    }
}

(3)join线程:Thread提供了让一个线程等待另一个线程完成的方法:join()。当某个程序在执行过程中调用了其他线 程的join()方法,则当前线程被阻塞,直到被join()方法加入的线程完成为止。

public static void main(String[] args) { 
    for (int i = 0; i < 100; i++) { 
        System.out.println(Thread.currentThread().getName() +"\t" + i); 
        if(i == 5) { 
            TheThread t1 = new TheThread(); 
            t1.start(); t
            1.join();
         } 
     } 
 }

(4)守护线程:setDaemon(true)
主线程死亡,无论守护线程是否执行完毕,都会死亡 除了特别的设置外,我们创建的线程都是非守护线程。

public static void main(String[] args) { 
    TheThread3 t3 = new TheThread3(); 
    t3.setDaemon(true); 
    t3.start(); 
    for (int i = 0; i < 10; i++) { 
        System.out.println(i); 
    } 
}

(5)使用synchronized关键字,可以在多线程环境下用来作为线程安全的同步锁。
线程同步,模拟多人同时在银行取钱
创建Account类

public class Account {
    private Double money = 2000d;
    public void getMoney(String userName, Double money) {
            if (money > this.money) {
                System.out.println(userName + ":余额不足,取款失败,当前余额为:" + this.money);
            } else {
                this.money -= money;
                System.out.println(userName + ": 取款成功,当前余额为:" + this.money);
            }
    }
}

创建User类:

public class User extends Thread{
    private String userName;
    private Double money;
    private Account account;
    public User(){}
    public User(String userName,Double money,Account account){
        this.userName = userName;
        this.money = money;
        this.account = account;
    }
    @Override
    public void run() {
        account.getMoney(userName,money);
    }
}

模拟取钱:

public class Test {
    //当多个线程同时对同一个实例中的同一个实例变量进行操作时,会引发线程安全问题
    public static void main(String[] args) {
        Account account = new Account();
        User user1 = new User("Rose",1500d,account);
        user1.start();
        User user2 = new User("Alex",1500d,account);
        user2.start();
        User user3 = new User("Hanks",1500d,account);
        user3.start();
    }
}

结果可能会出现:
Rose: 取款成功,当前余额为:500.0
Alex:取款成功,当前余额为:500.0
Hanks:余额不足,取款失败,当前余额为:500.0

取钱过程想要变成线程安全的可以使用synchronizedg关键字,可以在多线程环境下用来作为线程安全的同步锁。

//同步方法:
public synchronized void getMoney(String name,float money) { }
//同步代码块:
public void getMoney(String name,float money) { 
    synchronized (this) { } 
}

仅同步会发生线程安全的代码,比同步整个方法性能高。

同步方法:

public class Account {
    private Double money = 2000d;
    Object obj = new Object();
    //同步方法
    public synchronized void getMoney(String userName, Double money) {
        //锁对象
    if (money > this.money) {
         System.out.println(userName + ":余额不足,取款失败,当前余额为:" + this.money);
    } else {
         this.money -= money;
         System.out.println(userName + ": 取款成功,当前余额为:" + this.money);
         }  
    }
}

同步代码块:

public class Account {
    private Double money = 2000d;
    Object obj = new Object();
    public void getMoney(String userName, Double money) {
        //同步代码块
        synchronized (obj) {
            if (money > this.money) {
                System.out.println(userName + ":余额不足,取款失败,当前余额为:" + this.money);
            } else {
                this.money -= money;
                System.out.println(userName + ": 取款成功,当前余额为:" + this.money);
            }
        } 
    }
}

(6)使用lock()锁。

import java.util.concurrent.locks.ReentrantLock;
public class Account {
    private Double money = 2000d;
    Object obj = new Object();
    ReentrantLock lock = new ReentrantLock();
    public void getMoney(String userName, Double money) {
        //锁对象
        lock.lock(); //开启锁
        try {
            if (money > this.money) {
                System.out.println(userName + ":余额不足,取款失败,当前余额为:" + this.money);
            } else {
                this.money -= money;
                System.out.println(userName + ": 取款成功,当前余额为:" + this.money);
            }
        }finally {
//释放锁  使用try finally方法更加安全,不论运行是否成功都能解锁
            lock.unlock();
        }
    }
}

3、从浏览器爬取文件

(1)爬取一张图片:

public class NetTest {
    public static void main(String[] args) throws IOException {
        //获取浏览器图片文件的url
        URL url = new URL("https://pic3-nc.pocoimg.cn/image/poco/works/63/2021/0720/11/16267526425235838_173636313_H480.jpg");
        //和指定的url地址获取连接        
        URLConnection conn = url.openConnection();
        //从连接中获取输入流
        InputStream inputStream = conn.getInputStream();
        FileOutputStream fos = new FileOutputStream(new File("D:/Pictures/Thread/1.jpg"));
        int len = -1;
        while((len = inputStream.read()) != -1){
            fos.write(len);
        }
        inputStream.close();
        fos.flush();
        fos.close();
    }
}

(2)爬取网页中的多张图片。
jsoup:Java解析HTML的一个工具类,它使用最好的 HTML5 DOM 方法和 CSS 选择器提供了一个非常方便的 API,用于获取 URL 以及提取和操作数据。
导入jsoup的jar包,从网上搜索jsoup下载jsoup的jar包,点击idea的文件,选择项目结构(Project Structure),选择库(Libraries),点“+”号,添加一个Java,选择刚刚下载的jar包,点击确定。添加好后在外部库里会多出jsoup的jar包。

public class NetTest2 {
    public static void main(String[] args) throws IOException, InterruptedException {
    //获取网页的所有源代码
        Document doc = Jsoup.connect("https://www.poco.cn/activity/detail_online?id=340&status=1").get();
        System.out.println(doc);
        //获取标签是img的元素集合
        Elements list = doc.select("img");
        for (Element element : list) {
        //获取元素是data-src的字符串
            String link = element.attr("data-src");
            if (link.length() == 0) {
                System.out.println("link is empty!");
            } else {
            //给字符串加上https协议
                String newLink = "https:" + link;
                System.out.println(newLink);
                Thread.sleep(2000);
                //创建url连接
                URL url = new URL(newLink);
                URLConnection conn = url.openConnection();
                //设置浏览器请求头,创建连接时提交给服务器
                conn.setRequestProperty("user-agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4589.0 Safari/537.36");
                InputStream is = conn.getInputStream();

                //获取link 的后缀名
                int index = link.lastIndexOf("/");
                System.out.println(index);
                String picName = link.substring(index + 1);
                System.out.println(picName);

                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("D:/Pictures/Thread/" + picName)));

                byte[] buffer = new byte[512];
                int len = -1;
                while ((len = is.read(buffer)) != -1) {
                    bos.write(buffer,0,len);
                }
                is.close();
                bos.flush();
                bos.close();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值