目录
1.并发与并行
2.进程与线程
3.线程的调度
(1)分时调度
(2)抢占式调度
4.主线程
5.创建线程的方法
(1)Thread类
(2)创建线程第一种方式:继承Thread类
(3)多线程的内存图
(4)创建线程的第二种方式:实现Runnable接口
(5)创建线程的两种方式的对比
(6)策略模式在Runable接口与Thread类中的应用
(7)实现Runnable接口创建多线程程序的好处
(8)匿名内部类实现线程的创建
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。
并发:两个或多个事件在同一时间段内发生
并行:两个或多个事件在同一时刻发生(同时发生)
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度
win10下设置优先级:
主线程:执行main方法的线程
对于单线程程序,java程序只有一个线程,即主线程,执行main方法,从上到下顺序执行
示例:
Person.java
package MainDemo; public class Person { private String name; public Person() { } public Person(String name) { this.name=name; } public String getName() { return name; } public void setName(String name) { this.name = name; } void run() { for (int i = 0; i < 10; i++) { System.out.println("run:"+i); } } }MainDemo.java
package MainDemo; public class MainDemo { public static void main(String[] args) { Person p1=new Person("小明"); p1.run(); Person p2=new Person("旺财"); p2.run(); } }单线程程序的弊端:中途出现异常,没有被捕获的话,会停止执行
而在多线程下,一个线程发生异常不会影响另一个线程
main线程的查看方法:
查看线程的方法:
在JVM启动的过程中,会启动main线程,main线程是一个非守护线程,同时JVM会启动一些其他的守护线程
cmd进入命令提示符工具中
jdk中jps命令可以显示当前运行的java进程以及进程运行的各种参数
通过Jconsole打开Java程序监控工具的GUI,查看Java程序的一些信息
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。
每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。实现步骤:
1.创建一个Thread类的子类2.在Thread类的子类中重写Thread类的run方法,设置线程任务,即线程要干什么3.创建Thread类的子类对象4.调用Thread类的start方法,开启线程,执行run方法 结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。示例代码1:
MyThread.java
package Demo01; //1.创建一个Thread类的子类 public class MyThread extends Thread{ //2.在Thread类的子类中重写Thread类的run方法,设置线程任务,即线程要干什么 @Override public void run() { for (int i = 0; i <10 ; i++) { System.out.println("run:"+i); } } }ThreadDemo01.java
package Demo01; public class ThreadDemo01 { public static void main(String[] args) { //3.创建Thread类的子类对象 MyThread mt=new MyThread(); //4.调用Thread类的start方法,开启线程,执行run方法 mt.start(); for (int i = 0; i < 10; i++) { System.out.println("main:"+i); } } }注意:上述直接调用run()方法,只会在当前线程中执行,只有调用start()方法时才会开辟新的线程的栈空间
实现步骤:
1.创建一个Runnable接口的实现类2.在实现类中重写Runnable接口中的run方法,设置线程任务3.创建一个Runnable接口的实现类对象4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象5.调用Thread类中的start方法,开启新的线程执行run方法示例代码:
RunnableImpl.java
package Demo02; //1.创建一个Runnable接口的实现类 public class RunnableImpl implements Runnable{ //2.在实现类中重写Runnable接口中的run方法,设置线程任务 @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"---"+i); } } }Main.java
package Demo02; public class Main { public static void main(String[] args) { //3.创建一个Runnable接口的实现类对象 RunnableImpl runnable=new RunnableImpl(); //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象 Thread t=new Thread(runnable); //5.调用Thread类中的start方法,开启新的线程执行run方法 t.start(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"---"+i); } } }示例:
采用多线程模拟银行叫号(注意此案例并非真实情况,只要求三个窗口操作同一个共享变量,不要求完全按照顺序叫号,仅仅为了演示Runnable的好处)
版本1:
TicketWindow.java
package Demo02; public class TicketWindow extends Thread { private final String name; private final int MAX = 50; private int index = 1; public TicketWindow(String name) { this.name = name; } @Override public void run() { while(index <= MAX) { System.out.println(name+"当前号码:"+(index++)); } } }Bank.java
package Demo02; public class Bank { public static void main(String[] args) { TicketWindow ticketWindow1 = new TicketWindow("一号柜台"); ticketWindow1.start(); TicketWindow ticketWindow2 = new TicketWindow("二号柜台"); ticketWindow2.start(); TicketWindow ticketWindow3 = new TicketWindow("三号柜台"); ticketWindow3.start(); } }此种情况下每个柜台都会叫1-50,因为每个线程都会创建一个TicketWindow对象,而三个对象有各自的成员变量index和成员常量MAX,并没有共享一个变量,这种情况下,我们要让多个对象共享一个变量,可以将成员变量变为类变量即static变量,为类的多个对象所共享,这样三个柜台就能操作同一个变量index了
package Demo02; public class TicketWindow extends Thread { private final String name; private static final int MAX = 50; private static int index = 1; public TicketWindow(String name) { this.name = name; } @Override public void run() { while(index <= MAX) { System.out.println(name+"当前号码:"+(index++)); } } }虽然多个对象能操作同一个变量,但是static变量声明周期比较长
版本2:
TicketWindowRunnable.java
package Demo02; public class TicketWindowRunnable implements Runnable{ private int index = 1; private final static int MAX = 50; @Override public void run() { while(index <= MAX) { System.out.println(Thread.currentThread()+"当前号码:"+(index++)); } } }Bank.java
package Demo02; public class Bank { public static void main(String[] args) { final TicketWindowRunnable ticketWindow = new TicketWindowRunnable(); Thread windowThread1 = new Thread(ticketWindow,"一号柜台"); Thread windowThread2 = new Thread(ticketWindow,"二号柜台"); Thread windowThread3 = new Thread(ticketWindow,"三号柜台"); windowThread1.start(); windowThread2.start(); windowThread3.start(); } } 此种方式将业务逻辑和线程控制进行了分离,业务逻辑定义在Runnable的接口的实现类中,在使用时,只初始化一次,传给Thread,然后被多个线程同时去执行同一份业务逻辑备注:这段代码本身存在线程安全问题,而这里演示是为了演示Runnbale实现将业务逻辑和线程控制进行了分离,关于线程安全的讨论见后续博客如下,是对策略模式的模拟:
TaxCalculator.java
package Demo02; public class TaxCalculator { private final double salary; private final double bonus; private CalculatorStrategy calculatorStrategy; public TaxCalculator(double salary, double bonus) { this.salary = salary; this.bonus = bonus; } protected double calculTax() { return calculatorStrategy.calculate(salary,bonus); } public double calculate() { return this.calculTax(); } public double getSalary() { return salary; } public double getBonus() { return bonus; } public void setCalculatorStrategy(CalculatorStrategy calculatorStrategy) { this.calculatorStrategy = calculatorStrategy; } }CalculatorStrategy.java
package Demo02; public interface CalculatorStrategy { double calculate(double salary,double bonus); }SimpleCalculatorStrategy.java
package Demo02; public class SimpleCalculatorStrategy implements CalculatorStrategy{ private static final double SALAY_RATE = 0.1; private static final double BONUS_RATE = 0.15; @Override public double calculate(double salary, double bonus) { return salary*SALAY_RATE + bonus*BONUS_RATE; } }TaxCalculatorMain.java
package Demo02; public class TaxCalculatorMain { public static void main(String[] args) { /** * TaxCalculator类似于Thread类 * 在创建TaxCalculator对象的时候,重写calculate方法(类似于在创建Thread的时候,重写run方法) * 此种情况下业务逻辑calculate和TaxCalculator本身有高耦合性(run方法与Thread同理) * * */ /* TaxCalculator taxCalculator = new TaxCalculator(1000d,2000d){ @Override public double calculate() { return getSalary()*0.1 + getBonus()*0.15; } }; double tax = taxCalculator.calculTax(); System.out.println(tax); */ /** * 此种方式使用了策略模式 * 此种方式将业务逻辑的实现分离到CalculatorStrategy接口中(类似于分离出Runnable接口,将线程逻辑和业务逻辑分离) * 线程逻辑是不变的,业务逻辑是变化的,通过接口(Runnable接口)和传入参数的方式(将Runnable的实现类传给Thread)将它们进行分离,降低了它们之间的耦合性 */ TaxCalculator taxCalculator = new TaxCalculator(1000d,2000d); CalculatorStrategy strategy = new SimpleCalculatorStrategy(); taxCalculator.setCalculatorStrategy(strategy); System.out.println(taxCalculator.calculTax()); } }匿名:没有名字
内部类:写在其他类内部的类
匿名内部类作用:简化代码,
把子类继承父类,重写父类方法,创建子类对象合为一步完成把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成匿名内部类的最终实现产物:子类/实现类对象,而这个类没有名字
格式:
new 父类/接口(){ //重写父类接口中的方法 }示例代码1:
package Demo02; public class Main { public static void main(String[] args) { //1.继承Thread的匿名内部类 new Thread(){ //重写run方法,设置线程任务 @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"---"+i); } } }.start(); //2.实现Runnable接口的匿名内部类 Runnable r=new Runnable(){ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"---"+i); } } }; new Thread(r).start(); //实现接口的简化版 new Thread( new Runnable(){ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"---"+i); } } } ).start(); } }示例代码2:
package Demo01; public class TryConcurrency { public static void main(String[] args) { //定义一个线程,只有在调用start方法后才叫线程 //读线程 new Thread("Read-Thread") { @Override public void run() { readFromDataBase(); } }.start(); //写线程 new Thread("Write-Thread") { @Override public void run() { readFromDataBase(); } }.start(); } //从数据库中读取文件 private static void readFromDataBase() { try { pringln("Begin read data from db."); Thread.sleep(1000*300L); pringln("Read data done and start handle it."); } catch (InterruptedException e) { e.printStackTrace(); } pringln("The data handle finish and successfully"); } private static void writeDataToFile() { try { pringln("Begin write data to file."); Thread.sleep(1000*300L); pringln("write data done and start handle it."); } catch (InterruptedException e) { e.printStackTrace(); } pringln("The data handle finish and successfully"); } private static void pringln(String message) { System.out.println(message); } }可以看到Read-Thread线程和Write-Thread线程正在执行,main线程已经执行结束
见我的另一篇博客:待补充链接
见我的另一篇博客:待补充链接