为什么会有线程的出现?
荐阅:https://blog.csdn.net/Su_Levi_Wei/article/details/80629737
在上面这篇文章中,可以发现,刚开始时,电脑是单个CPU,随着科技的发展,CPU的性能也提升了,如果CPU只能运行一个应用的话,这样对CPU的性能太浪费了。
于是科学家们就在想,能不能同时运行多个应用,把这些应用隔开来,并且根据各自的使用情况使用对应额度的CPU资源。
很快的,概念就出来了,首先出来的这个概念是进程。
进程出来了,循环又开始了,CPU性能跟着摩尔定律发展了,性能越来越猛,于是线程的概念也出来了,对应的技术也出来了。
什么是线程?
线程这个玩意听起来有点玄乎,作为开发人员,往往知道是什么,但是却无法解释出来,究竟线程是什么?
还是老规矩,从生活中看下这玩意究竟是怎么样存在的。
用手机下载应用程序,例如微信,在下载到手机后,这个东西是以文件的形式存储在手机的磁盘上的,这个东西是死。
点击手机中的微信这个应用程序时,这个程序就活了,会帮助自动查找手机通讯录的人,会查找好友,会查找哪些好友发了朋友圈,这个时候是以进程的形式存在手机中。
在使用微信时,会发现可以同时和多个人聊天,但是在打电话时,也只能和一个聊天,其实微信是对当前正在聊天的多个人都建立了一条电话线,就是线程。
区分
程序:保存在计算机中的指令代码,以文件形式存储在磁盘上(下载微信)。
进程:运行在计算机中的程序,分配了内存空间,操作系统进行调度(打开微信)。
线程:运行在进程中的执行单元,可以获得CPU执行权,分配相应的资源(与某人聊天),一个进程中至少有一个线程。
注:如果同时有多个线程在运行自己的任务,叫多线程。
优缺点
优点:可以同时处理多个任务,提高了CPU的利用率,但并不能提升速度。
缺点:多线程同时操作,必然需要一个专门维护和管理多线程的工作,并且付出额外的资源;线程切换会带来CPU资源损耗;会引发线程安全问题;会出现线程死锁的问题。
注:操作系统能同时运行多个程序(进程),实际上是由于CPU分时机制(时间片轮法、短作业优先、先进先出)的作用,使得每个程序都能循环获得自己的CPU时间片,但由于CPU的轮换速度(运行速度)非常快,使得所有程序好像是在同时运行一样。
Java的多线程入门
Java这个语言本身就内置了多线程,每个Java应用至少有一个线程,这就是主线程,由JVM创建并调用java应用程序的main方法。
JVM还会创建一些其他的线程,不过这些线程是不可见的,如在垃圾回收机制上也运行了一个线程,对象终止或其他JVM处理任务相关的线程。
java.lang.Object.finalize():对象被垃圾回收线程回收时,会主动调用这个方法。
System.gc():手动的运行垃圾回收器。
public class GcTest { public static void main(String[] args) throws Throwable { //一个对象如果使用完毕,则只要符合垃圾回收机制的规则,就会执行回收 new TestGCA();
//运行垃圾回收器 System.gc(); new TestGCA();
System.out.println("主线程结束"); } } class TestGCA { @Override protected void finalize() throws Throwable { System.out.println(this + " - 回收"); } } |
创建方式
public class Create { public static void main(String[] args) throws Throwable {
//方式一:启动线程 – 要新建一个Thread对象 Way01 way01 = new Way01(); Thread thread = new Thread(way01); thread.start();
//方式二:启动线程 – 直接启动 Way02 way02 = new Way02(); way02.start();
//方式三:匿名内部类 Thread thread2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("Way03:" + i); } } }); } } /** * 方式一、实现Runnable接口 */ class Way01 implements Runnable {
/* * 要同步运行的业务逻辑 * @overridden @see java.lang.Runnable#run() */ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("Way01:" + i); } }
} /** * 方式二、直接继承Thread类 * @see java.lang.Thread 会发现Thread类也是实现了Runnable接口。 */ class Way02 extends Thread {
/* * 要同步运行的业务逻辑 * @overridden @see java.lang.Thread#run() */ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("Way02:" + i); } } } |
Runnable接口中实现的run()方法是一个纯粹的方法,没有包含其他的东西,也符合面向对象的分离设计思想,保持程序风格的一致性。
实现Runnable接口可以解决Java单一继承的不足,如果继承了Thread类,就不能继承其他类了。
继承Thread类的话,可以直接操纵线程类中的方法,简单。
public class J03Example { public static void main(String[] args) { //兔子线程 Thread thread = new Thread(new Rabbit(),"兔子");
//乌龟线程 Thread thread2 = new Thread(new Runnable() { @Override public void run() { new Tortoise().climb(); } },"乌龟");
System.out.println("--------------:开跑"); thread.start(); thread2.start();
System.out.println("主线程(Main)运行结束");
} } /** * 兔子 */ class Rabbit implements Runnable {
@Override public void run() { for (int i = 0; i < 10; i++) {
String threadName = Thread.currentThread().getName();
if(i == 6) { try { System.out.println(threadName + " 开始睡一觉"); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }
System.out.println(threadName + "跑了 " + i + " 米");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }
} /** * 乌龟 */ class Tortoise { /** * 定义一个方法 */ public void climb() { for (int i = 0; i < 10; i++) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + "跑了 " + i + " 米");
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } |
如果主线程已经结束了,但其他线程还在运行,则整个进程并没有结束,等待所有线程都运行完成了,则整个进程结束,程序结束。
注:同一个线程不能调用start()方法两次,会抛出java.lang.IllegalThreadStateException异常(非法线程状态异常)
线程优先级
public class J04Priority { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { new PriorityA().test(); } },"t1");
Thread t2 = new Thread(new Runnable() { @Override public void run() { new PriorityA().test(); } },"t2");
Thread t3 = new Thread(new Runnable() { @Override public void run() { new PriorityA().test(); } },"t3");
//线程优先级:一个线程比其他线程的优先级更高的话,更容易获得CPU的执行权。 //取值范围:1-10,最低1,最高是10,默认是5
//Thread提供了三个常量MAX_PRIORITY(10)、MIN_PRIORITY(1)、NORM_PRIORITY(5) t1.setPriority(Thread.MAX_PRIORITY);
t1.start(); t2.start(); t3.start(); System.out.println("主线程(Main)运行结束"); } } class PriorityA { public void test() { for (int i = 0; i < 8; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + " " + i); } } } |
生命周期
1、启动线程,线程进入就绪状态。
2、线程开始抢夺CPU执行权。
3、获得CPU执行权,开始执行线程逻辑。
4、可能在执行过程中,需要让别的线程执行,当前线程进入阻塞,别的线程执行完了,则继续执行当前线程。
5、执行完毕。
public class TicketMain { public static void main(String[] args) { Thread t1 = new Thread(new Ticket(),"窗口1"); Thread t2 = new Thread(new Ticket(),"窗口2"); Thread t3 = new Thread(new Ticket(),"窗口3"); t1.start(); t2.start(); t3.start();
} } /** * 票 */ class Ticket implements Runnable {
/** * 线程共享变量,static修饰的在内存中只有一份JVM内部是线程安全的 */ static int num = 100;
@Override public void run() { while (num > 0) { System.out.println(Thread.currentThread().getName() + " - 卖出 " + num + "号座"); num--; } System.out.println(Thread.currentThread().getName() + "票已售空"); } } |
线程启动使用父类的start()方法
如果线程对象之间调用run()方法,JVM是不会当做线程来运行的,会认为是普通的方法调用。
线程启动只能有一次,即不能多次调用start()方法,否则会抛出异常。
可以之间创建Thread类的对象并启动该线程,但是如果没有重写run()方法,什么也不执行。
主线程出现异常,从JVM中结束,但其他线程并没有结束,所有线程都执行完毕了,JVM都会结束。
线程的任务都是封装在Runnable接口子类对象的run()方法中,所以要在线程对象创建时,就明确要运行的任务。