学习内容:学习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();
}
}
}
}
本文详细介绍了Java中线程的创建与启动,包括继承Thread类和实现Runnable接口两种方式,以及线程的生命周期状态。讲解了同步机制如sleep()、join()、守护线程和锁的使用,还展示了线程安全的银行取款模拟。最后,通过实例展示了如何从浏览器爬取图片文件,并利用Jsoup解析HTML获取多张图片。
(1、创建和启动线程 2、线程的生命周期 3、从浏览器多线程爬取文件)&spm=1001.2101.3001.5002&articleId=119205149&d=1&t=3&u=99cd05862bbf4ed5afddec98083e0af1)
447

被折叠的 条评论
为什么被折叠?



