JAVA多线程————一篇文章让你彻底征服多线程开发(一)
2017-06-29 14:48
495 查看
多线程的基本概念
什么是进程
多进程有什么作用
什么是线程
多线程有什么作用
java 程序的运行原理
线程生命周期
线程的调度与控制
线程优先级
sleep
停止一个线程
yield
join
synchronized
死锁
守护线程
Timerschedule
每个进程是一个应用程序,都有独立的内存空间
同一个进程中的线程共享其进程中的内存和资源(共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的。)
进程。在 java 的开发环境下启动 JVM,就表示启动了一个进程。现代的计算机都是支持多
进程的,在同一个操作系统中,可以同时启动多个进程。
玩电脑,一边玩游戏(游戏进程)一边听音乐(音乐进程)。
对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程是同时在运行吗?不是。
因为计算机的 CPU 只能在某个时间点上做一件事。由于计算机将在“游戏进程”和“音乐
进程”之间频繁的切换执行,切换速度极高,人类感觉游戏和音乐在同时进行。
多进程的作用不是提高执行速度,而是提高 CPU 的使用率。
进程和进程之间的内存是独立的。
线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈。
可以给现实世界中的人类一种错觉:感觉多个线程在同时并发执行。
新建:采用 new 语句创建完成
就绪:执行 start 后
运行:占用 CPU 时间
阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合
终止:退出 run()方法
多线程不是为了提高执行速度,而是提高应用程序的使用率.
线程和线程共享”堆内存和方法区内存”.栈内存是独立的,一个线程一个栈.
可以给现实世界中的人类一种错觉 : 感觉多线程在同时并发执行.
很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
并行与并发:
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:
同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
Java命令会启动Java虚拟机,启动JVM,等于启动了一个应用程序,表示启动了一个进程,该进程会自动启动一个”主线程”,
然后主线程去调用某个类的main()方法,所以main()方法运行在主线程中.
通常我们的计算机只有一个 CPU,CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。在单 CPU 的机器上线程不是并行运行的,只有在多个 CPU 上线程才可以并行运行。Java 虚拟机要负责线程的调度,取得 CPU 的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java 使用抢占式调度模型。分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
分时调度模型: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调度模型: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些.
本文所有代码已经完整附上,如果想查看运行结果,可以把代码直接复制到开发工具运行查看
通常定义一个标记,来判断标记的状态停止线程的执行
异步编程模型 : t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁.
同步编程模型 : t1线程和t2线程执行,t2线程必须等t1线程执行结束之后,t2线程才能执行,这是同步编程模型.
什么时候要用同步呢?为什么要引入线程同步呢?
1.为了数据的安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制.
线程同步机制使程序变成了(等同)单线程.
2.什么条件下要使用线程同步?
第一: 必须是多线程环境
第二: 多线程环境共享同一个数据.
第三: 共享的数据涉及到修改操作.
其它所有的用户线程结束,则守护线程退出!
守护线程一般都是无限执行的.
设置为守护线程后,当主线程结束后,守护线程并没有把所有的数据输出完就结束了,也即是说守护线程是为用户线程服务的,当用户线程全部结束,守护线程会自动结束
什么是进程
多进程有什么作用
什么是线程
多线程有什么作用
java 程序的运行原理
线程生命周期
线程的调度与控制
线程优先级
sleep
停止一个线程
yield
join
synchronized
死锁
守护线程
Timerschedule
多线程的基本概念
线程指进程中的一个执行场景,也就是执行流程,那么进程和线程有什么区别呢?每个进程是一个应用程序,都有独立的内存空间
同一个进程中的线程共享其进程中的内存和资源(共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的。)
什么是进程?
一个进程对应一个应用程序。例如:在 windows 操作系统启动 Word 就表示启动了一个进程。在 java 的开发环境下启动 JVM,就表示启动了一个进程。现代的计算机都是支持多
进程的,在同一个操作系统中,可以同时启动多个进程。
多进程有什么作用?
单进程计算机只能做一件事情。玩电脑,一边玩游戏(游戏进程)一边听音乐(音乐进程)。
对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程是同时在运行吗?不是。
因为计算机的 CPU 只能在某个时间点上做一件事。由于计算机将在“游戏进程”和“音乐
进程”之间频繁的切换执行,切换速度极高,人类感觉游戏和音乐在同时进行。
多进程的作用不是提高执行速度,而是提高 CPU 的使用率。
进程和进程之间的内存是独立的。
什么是线程?
线程是一个进程中的执行场景。一个进程可以启动多个线程。多线程有什么作用?
多线程不是为了提高执行速度,而是提高应用程序的使用率。线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈。
可以给现实世界中的人类一种错觉:感觉多个线程在同时并发执行。
java 程序的运行原理?
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,表示启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。线程生命周期
线程是一个进程中的执行场景,一个进程可以启动多个线程新建:采用 new 语句创建完成
就绪:执行 start 后
运行:占用 CPU 时间
阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合
终止:退出 run()方法
多线程不是为了提高执行速度,而是提高应用程序的使用率.
线程和线程共享”堆内存和方法区内存”.栈内存是独立的,一个线程一个栈.
可以给现实世界中的人类一种错觉 : 感觉多线程在同时并发执行.
很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
并行与并发:
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:
void transferMoney(User from, User to, float amount){ to.setMoney(to.getBalance() + amount); from.setMoney(from.getBalance() - amount); }
同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
Java命令会启动Java虚拟机,启动JVM,等于启动了一个应用程序,表示启动了一个进程,该进程会自动启动一个”主线程”,
然后主线程去调用某个类的main()方法,所以main()方法运行在主线程中.
线程的调度与控制
线程的调度模型分为: 分时调度模型和抢占式调度模型,Java使用抢占式调度模型通常我们的计算机只有一个 CPU,CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。在单 CPU 的机器上线程不是并行运行的,只有在多个 CPU 上线程才可以并行运行。Java 虚拟机要负责线程的调度,取得 CPU 的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java 使用抢占式调度模型。分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
分时调度模型: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调度模型: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些.
public class ThreadTest { public static void main(String[] args) { ThreadTest1(); ThreadTest2(); ThreadTest3(); ThreadTest4(); ThreadTest5(); } /** * 三个方法: 获取当前线程对象:Thread.currentThread(); 给线程起名: t1.setName("t1"); 获取线程的名字: t.getName(); */ private static void ThreadTest1() { Thread t = Thread.currentThread();// t保存的内存地址指向的线程为"主线程" System.out.println(t.getId()); Thread t1 = new Thread(new Processor1()); // 给线程起名 t1.setName("t1"); t1.start(); Thread t2 = new Thread(new Processor1()); t2.setName("t2"); t2.start(); } /** * 线程优先级高的获取的CPU时间片相对多一些 优先级: 1-10 最低: 1 最高: 10 默认: 5 */ private static void ThreadTest2() { Thread t1 = new Processor2(); Thread t2 = new Processor2(); t1.setName("t1"); t2.setName("t2"); System.out.println(t1.getPriority()); System.out.println(t2.getPriority()); t1.setPriority(1); t2.setPriority(10); t1.start(); t2.start(); } /** * 1.Thread.sleep(毫秒); 2.sleep方法是一个静态方法 3.该方法的作用: 阻塞当前线程,腾出CPU,让给其它线程 */ private static void ThreadTest3() { Thread t = new Thread(new Processor3()); t.start(); for (int i = 0; i < 11; i++) { System.out.println(Thread.currentThread().getName() + "========>" + i); try { t.sleep(5000);// 等同于Thread.sleep(5000);阻塞的还是当前线程,和t线程无关. } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 某线程正在休眠,如何打断它的休眠 以下方式依靠的是异常处理机制 */ private static void ThreadTest4() { try { Thread t = new Thread(new Processor4()); t.start(); Thread.sleep(5000);// 睡5s t.interrupt();// 打断Thread的睡眠 } catch (InterruptedException e) { e.printStackTrace(); } } /** * 如何正确的更好的终止一个正在执行的线程 需求:线程启动5s之后终止. */ private static void ThreadTest5() { Processor5 p = new Processor5(); Thread t = new Thread(p); t.start(); // 5s之后终止 try { Thread.sleep(5000); p.isRun = false; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class Processor1 implements Runnable { @Override public void run() { Thread t = Thread.currentThread();// t保存的内存地址指向的线程为"t1线程对象" System.out.println(t.getName()); System.out.println(t.getId()); } } class Processor2 extends Thread { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName() + "----------->" + i); } } } class Processor3 implements Runnable { /** * Thread中的run方法不能抛出异常,所以重写runn方法之后,在run方法的声明位置上不能使用throws 所以run方法中的异常只能try...catch... */ @Override public void run() { for (int i = 0; i < 11; i++) { System.out.println(Thread.currentThread().getName() + "========>" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Processor4 implements Runnable { @Override public void run() { try { Thread.sleep(1000000000); System.out.println("能否执行这里"); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 11; i++) { System.out.println(Thread.currentThread().getName() + "========>" + i); } } } class Processor5 implements Runnable { boolean isRun = true; @Override public void run() { for (int i = 0; i < 11; i++) { if (isRun) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "========>" + i); } } } }
本文所有代码已经完整附上,如果想查看运行结果,可以把代码直接复制到开发工具运行查看
线程优先级
线 程 优 先 级 主 要 分 三 种 : MAX_PRIORITY( 最 高 级 );MIN_PRIORITY ( 最 低 级 )NORM_PRIORITY(标准)默认//设置线程的优先级,线程启动后不能再次设置优先级 //必须在启动前设置优先级 //设置最高优先级 t1.setPriority(Thread.MAX_PRIORITY);
sleep
sleep 设置休眠的时间,单位毫秒,当一个线程遇到 sleep 的时候,就会睡眠,进入到阻塞状态,放弃 CPU,腾出 cpu 时间片,给其他线程用,所以在开发中通常我们会这样做,使其他的线程能够取得 CPU 时间片,当睡眠时间到达了,线程会进入可运行状态,得到 CPU 时间片继续执行,如果线程在睡眠状态被中断了,将会抛出 IterruptedExceptionpublic class ThreadTest05 { public static void main(String[] args) { Runnable r1 = new Processor(); Thread t1 = new Thread(r1, "t1"); t1.start(); Thread t2 = new Thread(r1, "t2"); t2.start(); } } class Processor implements Runnable { public void run() { for (int i=0; i<100; i++) { System.out.println(Thread.currentThread().getName() + "," + i); if (i % 10 == 0) { try { //睡眠 100 毫秒,主要是放弃 CPU 的使用,将 CPU 时间片交给其他线程使用 Thread.sleep(100); }catch(InterruptedException e) { e.printStackTrace(); } } } } }
停止一个线程
如果我们的线程正在睡眠,可以采用 interrupt 进行中断通常定义一个标记,来判断标记的状态停止线程的执行
yield
它与 sleep()类似,只是不能由用户指定暂停多长时间,并且 yield()方法只能让同优先级的线程有执行的机会,采用 yieid 可以将 CPU 的使用权让给同一个优先级的线程join
当前线程可以调用另一个线程的 join 方法,调用后当前线程会被阻塞不再执行,直到被调用的线程执行完毕,当前线程才会执行synchronized
线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 后,再让线程二来使用 s 变量异步编程模型 : t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁.
同步编程模型 : t1线程和t2线程执行,t2线程必须等t1线程执行结束之后,t2线程才能执行,这是同步编程模型.
什么时候要用同步呢?为什么要引入线程同步呢?
1.为了数据的安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制.
线程同步机制使程序变成了(等同)单线程.
2.什么条件下要使用线程同步?
第一: 必须是多线程环境
第二: 多线程环境共享同一个数据.
第三: 共享的数据涉及到修改操作.
//synchronized 是对对象加锁 //采用 synchronized 同步最好只同步有线程安全的代码 //可以优先考虑使用 synchronized 同步块 //因为同步的代码越多,执行的时间就会越长,其他线程等待的时间就会越长 //影响效率 public synchronized void run() { //使用同步块 synchronized (this) { for (int i=0; i<10; i++) { s+=i; } System.out.println(Thread.currentThread().getName() + ", s=" + s); s = 0; }
public class SynchronizedTest { public static void main(String[] args) { SynchronizeTest1(); } private static void SynchronizeTest1() { Account account=new Account("Actno-001",5000.0); Thread t1=new Thread(new Processor(account)); Thread t2=new Thread(new Processor(account)); t1.start(); t2.start(); } } /** * 取款线程 */ class Processor implements Runnable{ Account act; Processor(Account act){ this.act=act; } @Override public void run() { act.withdraw(1000.0); System.out.println("取款1000.0成功,余额: "+act.getBalance()); } } class Account { private String actno; private double balance; public Account() { super(); } public Account(String actno, double balance) { super(); this.actno = actno; this.balance = balance; } public String getActno() { return actno; } public void setActno(String actno) { this.actno = actno; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } /** * 对外提供一个取款的方法 对当前账户进行取款操作 */ public void withdraw(double money) { //把需要同步的代码,放到同步语句块中. //遇到synchronized就找锁,找到就执行,找不到就等 /** * 原理: t1线程和t2线程 * t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁, * 如果找到this对象锁,则进入同步语句块中执行程序,当同步语句块中的代码执行结束之后, * t1线程归还this的对象锁. * * 在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到synchronized关键字, * 所以也去找this对象锁,但是该对象锁被t1线程持有,只能在这等待this对象的归还. * * synchronized关键字添加到成员方法上,线程拿走的也是this的对象锁. * */ synchronized (this) { double after = balance - money; try { //延迟 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //更新 this.setBalance(after); } } }
public class SynchronizedTest2 { public static void main(String[] args) throws InterruptedException { MyClass mc1=new MyClass(); MyClass mc2=new MyClass(); Thread t1=new Thread(new Runnable1(mc1)); Thread t2=new Thread(new Runnable1(mc2)); t1.setName("t1"); t2.setName("t2"); t1.start(); //延迟,保证t1先执行 Thread.sleep(1000); t2.start(); } } class Runnable1 implements Runnable{ MyClass mc; Runnable1(MyClass mc){ this.mc=mc; } @Override public void run() { if("t1".equals(Thread.currentThread().getName())){ MyClass.m1();//因为是静态方法,用的还是类锁,和对象锁无关 } if("t2".equals(Thread.currentThread().getName())){ MyClass.m2(); } } } class MyClass{ //synchronized添加到静态方法上,线程执行此方法的时候会找类锁,类锁只有一把 public synchronized static void m1(){ try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m1()............"); } /** * m2()不会等m1结束,因为该方法没有被synchronized修饰 */ // public static void m2(){ // System.out.println("m2()........"); // } /** * m2方法等m1结束之后才能执行,该方法有synchronized * 线程执行该方法需要"类锁",而类锁只有一个. */ public synchronized static void m2(){ System.out.println("m2()........"); } }
死锁
public class DeadLock { public static void main(String[] args) { Object o1 = new Object(); Object o2 = new Object(); Thread t1 = new Thread(new T1(o1, o2)); Thread t2 = new Thread(new T2(o1, o2)); t1.start(); t2.start(); } } class T1 implements Runnable { Object o1; Object o2; T1(Object o1, Object o2) { this.o1 = o1; this.o2 = o2; } @Override public void run() { synchronized (o1) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2) { } } } } class T2 implements Runnable { Object o1; Object o2; T2(Object o1, Object o2) { this.o1 = o1; this.o2 = o2; } @Override public void run() { synchronized (o2) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1) { } } } }
守护线程
从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束,例如 java 中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。其它所有的用户线程结束,则守护线程退出!
守护线程一般都是无限执行的.
public class DaemonThread { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable2()); t1.setName("t1"); // 将t1这个用户线程修改成守护线程.在线程没有启动时可以修改以下参数 t1.setDaemon(true); t1.start(); // 主线程 for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "----->" + i); Thread.sleep(1000); } } } class Runnable2 implements Runnable { @Override public void run() { int i = 0; while (true) { i++; System.out.println(Thread.currentThread().getName() + "-------->" + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
设置为守护线程后,当主线程结束后,守护线程并没有把所有的数据输出完就结束了,也即是说守护线程是为用户线程服务的,当用户线程全部结束,守护线程会自动结束
Timer.schedule()
/** * 关于定时器的应用 作用: 每隔一段固定的时间执行一段代码 */ public class TimerTest { public static void main(String[] args) throws ParseException { // 1.创建定时器 Timer t = new Timer(); // 2.指定定时任务 t.schedule(new LogTimerTask(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").parse("2017-06-29 14:24:00 000"), 10 * 1000); } } // 指定任务 class LogTimerTask extends TimerTask { @Override public void run() { System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date())); } }
相关文章推荐
- 一篇文章让你彻底征服多线程开发
- java多线程性能浅析 - 一篇把线程描述得很形象的文章
- 看到一篇专门讲JAVA多线程的文章,挺不错
- 一篇不错的介绍Java Socket编程的文章-Java基础-Java-编程开发
- 用生动形象的语句来阐述java多线程(转载自高人)我被彻底征服了
- 一篇文章彻底了解Java垃圾收集(GC)机制
- Java开发中的23种设计模式,很好的一篇文章
- 对我很有帮助的一篇关于“扩展 Eclipse 的 Java 开发工具”的文章
- 一篇文章彻底搞懂java动态代理的实现
- 用Delphi2005和DUnit搭建敏捷开发平台(一篇被到处转贴的文章,不过看起来不错,有空应该尝试一下)
- 一篇不错的介绍Java Socket编程的文章
- 看到一篇文章《==与equals()的区别》(java)
- 搭建.net framwork 3.0开发环境的一篇文章
- 一篇写用winpcap开发arp的好文章。
- 收藏文章一篇:开发高效的 OpenLaszlo 应用
- 转载:一篇java与C#的对比文章(英文)
- 一篇不错的讲解Java异常的文章
- 搭建.net framwork 3.0开发环境的一篇文章
- 搭建.net framwork 3.0开发环境的一篇文章
- 我从网上搜来的一篇关于Eclipse上进行C/C++开发的文章