进程:应用程序在内存中分配的空间(正在运行中的程序)
线程: 进程中负责程序执行的执行单元,也称为执行路径 一个进程至少有一个线程负责该进程的执行,称主线程 如果一个进程启用了多个线程,就称该程序为多线程程序
多线程有时并不能提高效率,所以多线程存在的意义不是提高效率,多线程其实是合理使用资源 解决多部分代码同时执行的需求,合理使用CPU资源 多线程的运行是根据CPU的切换完成的,如何切换是由CPU决定的,所以多线程运行具有随机性 JVM中的线程至少有两个 一个是负责自定义代码运行的 一个是负责垃圾回收的
class Demo{ //定义垃圾回收方法 public void finalize(){ System.out.println("clear is ok"); } } public class GcTest{ public static void main(String args[]){ new Demo(); new Demo(); new Demo(); System.gc();//启动垃圾回收器 System.out.println("TEST OVER!"); } }可以看到,垃圾回收器启动了之后不一定执行,执行与否不确定,由CPU说了算 注:因为我的JDK版本较高,所以在编译时出现了警告,显示finalize方法已经过时 每个线程都有运行的代码内容,称为线程的任务。而之所以创建线程就是为了去运行指定的任务代码 线程的任务都封装在特定的区域中。例如:主线程运行的任务都定义在main方法中
单线程
//单线程示例 class People{ private String name; //初始化 People(String name){ this.name = name; } public void show(){ for(int x = 1;x<11;x++){ System.out.println(name+"-----"+x); } } } public class ThreadDemo{ public static void main(String args[]){ People p1 = new People("LILY"); People p2 = new People("LUCY"); p1.show(); p2.show(); } }创建多线程 1.继承Thread类 2.覆盖run方法 3.创建子类对象就是创建线程对象 4.调用Thread类中的start方法就可以执行线程,并会调用run方法
//继承Thread类 class People extends Thread{ private String name; //初始化 People(String name){ this.name = name; } public void show(){ for(int x = 1;x<11;x++){ System.out.println(name+"-----"+x); } } //覆盖run方法 public void run(){ show(); } } public class ThreadDemo{ public static void main(String args[]){ People p1 = new People("LILY"); People p2 = new People("LUCY"); p1.start();//调用start方法,start会调用run方法,run方法再调用show方法 p2.start(); } }可以看到,代码两次运行后的结果有区别,说明两个线程的运行是由CPU确定的,运行具有不确定性。
获取当前线程对象名 使用Thread类中的getName()
//单线程示例 class People extends Thread{ private String name; //初始化 People(String name){ this.name = name; } public void show(){ for(int x = 1;x<11;x++){ System.out.println(getName()+"-----"+name+"-----"+x); } } } public class ThreadDemo1{ public static void main(String args[]){ People p1 = new People("LILY"); People p2 = new People("LUCY"); p1.show(); p2.show(); } }Thread-0,Thread-1仅仅代表了当前对象的名字,不代表线程被开启。
获取当前线程路径,(开启线程) 使用Thread.currentThread()方法 注:需要调用start方法启动线程
class People extends Thread{ private String name; //初始化 People(String name){ this.name = name; } public void show(){ for(int x = 1;x<11;x++){ //获取自定义的两个线程当前路径 System.out.println(Thread.currentThread().getName()+"-------"+name+"-----"+x); } } //覆盖run方法 public void run(){ show(); } } public class ThreadDemo{ public static void main(String args[]){ People p1 = new People("LILY"); People p2 = new People("LUCY"); p1.start();//调用start方法,start调用run方法,run方法再调用show方法 p2.start(); //获取主线程当前路径 for(int i = 1;i<11;i++){ System.out.println(Thread.currentThread().getName()+"-----"+i); } } }当启动了p1.start()后是继续执行p2.start()还是执行run方法是不确定的,取决于CPU所分配的时间片,如果当前CPU在主线程上,则会继续启动p2。 可以看到,主线程和自定义的两个线程执行的顺序是不一致的,体现了CPU的随机性。
问题:调用start和调用run有什么区别 答:调用run多线程是没有被开启的,此时只有主线程在执行。而调用start是启动run里面自定义的线程对象 线程路径和线程对象名的区别 线程结束的条件是所属线程栈里没有其他方法
注:sleep需要指定睡眠时间,单位为毫秒。
首先,我们先写一个售票的小例子,假设总共有火车票100张,开启三个窗口去售卖这些车票
class TicketDemo extends Thread{ private int tickets = 100; public void run(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName()+"-----"+tickets--); } } } } public class SaleTicket{ public static void main(String args[]){ TicketDemo t1 = new TicketDemo(); TicketDemo t2 = new TicketDemo(); TicketDemo t3 = new TicketDemo(); //开启三个线程 t1.start(); t2.start(); t3.start(); } }但是运行结果似乎有一点点的不对劲,我们总共100张票,但是三个线程都输出了相同的数,就是说,一张票在不同的窗口被售卖了三次,这正常吗?明显不正常!
分析代码可以得知,我们在堆中new了三个TicketDemo,每个TicketDemo中都有100张票,但是我们在本例子中只需要一个TicketDemo,那么,如何在实例化一个对象的前提下,开启三个或者多个线程呢
此处需要注意:如果只实例化一个对象,那么当前线程只有2个,即主线程和自定义开启的线程,但要注意的是,多次启用一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动,如果线程已启动,会抛出IllegalThreadStateException 是否可以使用Thread类对象来创建四个线程呢?
public class SaleTicket{ public static void main(String args[]){ Thread t1 = new Thread(); Thread t2 = new Thread(); Thread t3 = new Thread(); //开启三个线程 t1.start(); t2.start(); t3.start(); } }运行代码会看到没有输出结果,这是为什么呢?这里需要注意的是,我们使用Thread创建线程,那么start调用的run方法会是父类自身的run方法,而该方法是一个空方法,所以没有任何输出结果。
创建多线程方式二-----实现Runnable接口 1.定义一个类实现Runnable。 2.覆盖Runnable接口中的run方法。 3.通过Thread类创建线程对象,并将实现了Runnable接口的对象作为Thread类的构造函数的参数进行传递。 4.调用start方法,启动线程。
//实现Runnable接口 class TicketDemo implements Runnable{ //extends Thread{ private int tickets = 100; public void run(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName()+"-----"+tickets--); } } } } public class SaleTicket{ public static void main(String args[]){ //TicketDemo t1 = new TicketDemo(); //TicketDemo t2 = new TicketDemo(); //TicketDemo t3 = new TicketDemo(); TicketDemo t = new TicketDemo(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); //开启三个线程 t1.start(); t2.start(); t3.start(); } }实现Runnable接口的好处 1.避免了继承Thread类的单继承的局限性 2.Runnable接口更符合面向对象,将线程单独进行对象的封装 3.Runnable接口降低了线程对象和线程任务的耦合性
CPU时间片的分配可能会导致程序运行时出错 测试代码
//实现Runnable接口 class TicketDemo implements Runnable{ //extends Thread{ private int tickets = 100; public void run(){ while(true){ if(tickets>0){ //让线程在该处暂停 try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"-----"+tickets--); } } } }可以看到,程序执行到最后时,出错了。出错的原因是当某一个线程在执行了if判断之后,CPU将时间片分配给了另一个进程,当时间片又分配给当前进程时,当前进程无需做if判断而直接输出,导致了输出结果异常。 解决方式-----同步代码块
//实现Runnable接口 class TicketDemo implements Runnable{ //extends Thread{ private int tickets = 100; //使用一个现有的类创建对象 Object obj = new Object(); public void run(){ while(true){ /*使用同步代码块,是一种内部机制,当有线程使用时,其他线程没有该对象的使用权 synchronized(对象){ 需要被同步的代码 } 同步机制其实就是锁的机制*/ synchronized(obj){ if(tickets>0){ //让线程在该处暂停 try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"-----"+tickets--); } } } } } public class SaleTicket{ public static void main(String args[]){ //TicketDemo t1 = new TicketDemo(); //TicketDemo t2 = new TicketDemo(); //TicketDemo t3 = new TicketDemo(); TicketDemo t = new TicketDemo(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); //开启三个线程 t1.start(); t2.start(); t3.start(); } }同步在目前状态下保证了一次只有一个线程在执行,其他进程无法进入。 好处:解决了多线程的安全问题 弊端:降低了效率,但是在可接受的范围,一种以效率换安全的方式 注意:synchronized(对象)中需要用公有的锁,例子中如果换成synchronized(new Obiect()),那么每个进程使用的都是自己的锁,无法做到同步。 同步锁使用的前提是首先判断程序是不是多线程,单线程无需用到同步锁,且多个线程在同步中必须使用同一个锁。
文章为学习笔记,如有不足之处还请指正