第一节-线程简介
一、程序、进程、线程的简介
- 程序:是指令与数据的有序集合,本身没有任何运行的含义,是一个静态概念;
- 进程(Process):执行程序的一次执行过程,是一个动态概念,是系统分配资源的单位;
- 线程(Thread):在一个进程中包含若干个线程,线程是CPU调度和执行的单位;
注意:很多多线程是模拟出来的;真正的多线程是指有多个CPU(多核),如服务器。模拟出的多线程,在同一个时间点只能执行一块代码,但是由于切换速度很快,就给人一种同时执行的错觉!(main方法就是一个主线程)。
二、本节核心知识点
- 线程就是独立的执行路径;
- 程序运行时,即使没有创建线程,后台也会有多个线程,如主线程、GC线程;
- main()方法称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统密切相关的,先后顺序是不能人为干预的;
- 对同一份资源进行操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如CPU的调度时间,并发控制开销;
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致;
第二节-线程的创建
一、创建线程的三种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口(了解一下即可)
二、方式①继承Thread类的方法创建并开启线程
2.1 步骤
- 自定义一个类继承Thread类
- 在类中重写run()方法
- 创建线程对象,调用start()方法启动线程。(start方法的底层就是调用了run方法)
2.2 代码实现:
//首先声明一个类
public class ThreadTest extends Thread{
//继承Thread的类一定要重写run方法
@Override
public void run() {
for(int i = 0; i < 100; i ++){
System.out.println("I im IRON MAN!");
}
}
//然后在主方法中创建线程对象,用线程对象调用start()方法,即可开启线程!
public static void main(String[] args){
ThreadTest t1 = new ThreadTest();
t1.start();
/*
在这里需要注意:想要有多线程的效果,
必须使得主线程与多线程中相同的动作在多线程启动之后书写
*/
for(int i = 0; i < 100; i ++){
System.out.println("I im IRON MAN!");
}
}
}
注意:线程一旦启动并不一定立即执行,是由CPU安排调度的。
三、利用方式②创建多线程
3.1 步骤:
- 创建一个类,让这个类去实现Runnable接口;
- 在类中重写run()方法;
- 创建本类对象t1,由于本类对象不能直接调用start()方法,因此还需要一个Thread类对象;
- 创建线程对象-在构造方法中丢入上面的t1,然后即可调用start()方法来启动线程。
3.2 代码实现
public class C04_CreatThread_02 implements Runnable{
@Override
/*重写run方法*/
public void run() {
for(int i = 0; i < 249; i ++){
System.out.println("I am doing my job!");
}
}
/*主线程*/
public static void main(String[] args) {
/*创建一个本类对象*/
C04_CreatThread_02 t1 = new C04_CreatThread_02();
/*将本类对象丢入Thread类的创建中*/
Thread t = new Thread(t1);
/*用Thread去启动多线程*/
t.start();
/*这个是主线程的任务*/
for(int i = 0; i < 238; i ++){
System.out.println("I wang go to XIAN!");
}
}
}
像上面这种间接通过Thread类去调用start()方法,可以将Thread的对象称之为代理。
Thread与Runnable的关系是:Thread类实现了Runnable接口,更推荐使用实现Runnable接口的方式去实现多线程,因为Java是单继承的,第一种方式会限制了类不能再继承其他类!
3.3 方式①②的对比:
方式①:继承Thread | 方式②:实现Runnable接口 |
---|---|
子类继承Thread类,让子类拥有了多线程的能力; | 实现Runnable接口,使类有了多线程的能力; |
启动线程:子类对象.start(); | 启动线程:传入目标对象+Thread对象.start() |
不建议使用,避免OOP单继承局限性。 | 推荐使用,避免单继承局限,方便同一个对象被多个线程使用。 |
方式②的优越性:同一个对象,能被多个线程同时处理,提高速度:
如下代码:一个实现了Runnable()接口的类所创建的对象,通过代理被多个线程处理。
C04_CreatThread_02 tt = new C04_CreatThread_02();
Thread t1 = new Thread(tt,"WanLi");//这里是把线程起名字为:WanLi
Thread t2 = new Thread(tt,"LuoYan");
t1.start();
t2.start();
3.4 需求实例
需求①:实现多个人买票的场景:
public class C01_ThreadCreatTest implements Runnable {
private int ticket = 10;
@Override
public void run() {
while (true){
if(ticket <= 0){
break;
}
//这里利用了Thread.currentThread().getName()来获取当前线程的名字
System.out.println(Thread.currentThread().getName() + "抢到了第" + (ticket --) + "票!");
}
}
public static void main(String[] args) {
/*在这里创建线程对象,并利用Thread代理来完成多线程*/
C01_ThreadCreatTest tt = new C01_ThreadCreatTest();
/*下面用三个线程,对这个对象的票进行操作。*/
Thread t1 = new Thread(tt, "WanLi");
Thread t2 = new Thread(tt, "LuoJia");
Thread t3 = new Thread(tt, "LuoYan");
/*三个线程开启*/
t1.start();
t2.start();
t3.start();
}
}
四、以实现Callable接口的方式实现多线程(了解一下即可)
4.1 步骤
/*第1步:实现Callable接口*/
/*第2步:重写call()方法,需要抛出异常,这里的返回值类型可以自己定,表示该方法执行时返回某个数据!*/
public Object call() throws Exception {
return null;
}
/*第3步:创建目标对象*/
/*第4步:创建执行服务*/
ExecutorService ser = Executors.newFixedThreadPool(1);//这里的数字代表开启几个线程
/*第5步:提交执行*/
Future<Boolean> result1 = ser.submit(tt);
/*第6步:获取结果:*/
Boolean r1 = result1.get();
/*第7步:关闭服务*/
ser.shutdownNow();
4.2 代码实现:
import java.util.concurrent.*;
/*第1步:实现Callable接口*/
public class C03_CreatThreadMethod_03Callable implements Callable {
/*第2步:重写call()方法,需要抛出异常*/
@Override
public Boolean call() throws Exception {
System.out.println("多线程开始了");
for(int i = 0; i <= 100; i ++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
return true;
}
public static void main(String[] args) {
/*第3步:创建目标对象*/
C03_CreatThreadMethod_03Callable t1 = new C03_CreatThreadMethod_03Callable();
C03_CreatThreadMethod_03Callable t2 = new C03_CreatThreadMethod_03Callable();
/*第4步:创建执行服务*/
ExecutorService ser = Executors.newFixedThreadPool(2);
/*第5步:提交执行*/
Future<Boolean> result1 = ser.submit(t1);
Future<Boolean> result2 = ser.submit(t2);
/*第6步:获取结果:*/
try {
/*经过测试可知,下面的get()才在底层调用了call()方法,让其执行,并获取到了返回值*/
boolean r1 = result1.get();
boolean r2 = result2.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
/*第7步:关闭服务*/
ser.shutdownNow();
}
}
}
五、静态代理简述
实现一个需求,来体验静态代理的作用;
需求:某人要结婚,但是结婚找了婚庆公司代办。简而言之,婚庆公司就是一个代理了。
public class C04_StaticDaiLi {
public static void main(String[] args) {
/*我们在测试类中,让某个人结婚*/
MayyrPerson person = new MayyrPerson("WanLi");
/*创建婚庆公司对象,让他代理WanLi的结婚事宜*/
MarryCompany company = new MarryCompany(person);
company.marry();
}
}
/*创建结婚接口*/
interface Marry{
void marry();
}
/*创建即将结婚的人的类,显然这个类要去实现Marry接口*/
class MayyrPerson implements Marry{
private String name;
public MayyrPerson() {
}
public MayyrPerson(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void marry() {
System.out.println(name + "结婚中。。。。。很开心!");
}
}
/*创建婚庆公司的类,这个类也去实现结婚接口,婚庆公司就是一个代理*/
class MarryCompany implements Marry{
private MayyrPerson person;
public MarryCompany() {
}
public MarryCompany(MayyrPerson person) {
this.person = person;
}
@Override
public void marry() {
before();
person.marry();
after();
}
private void after() {
System.out.println("正在准备" + person.getName() + "婚后事宜!");
}
private void before() {
System.out.println("正在准备" + person.getName() + "结婚前事宜!");
}
}
这样做的好处是,代理对象可以做很多真实对象不用做的事情,真实对象只用专注于做自己该做的事情就可以了。这其实就是线程的第二种实现方式的底层原理!
第三节-Lambda表达式
一、概念
- Functional Interface(函数式接口):任何接口,如果只包含一个抽象方法,那么它就是一个函数式接口;例如:
interface Runnable{
void m1();
}
- 那么针对这样的函数式接口,我们可以使用Lambda表达式来创建该接口的对象;
二、Lambda表达式的使用
使用Lambda表达式的前提是,目标接口必须是一个函数式接口。
2.1 Lambda表达式的语法格式:
接口名 引用名 = () -> {
方法体;
};//一定不要忘记这里的分号
注意,以上是为了简化函数式接口所做的操作,它的含义是:创建了一个实现了某接口的对象,这个对象调用了接口中唯一的那个方法。例如:
AInterface quote = () ->
System.out.println("06-I am a Lambda Expression!-Lambda表达式!");
2.2 有关Lambda表达式的简化
- 当Lambda表达式中方法体只有一行代码时,大括号可以省略;
- 当目标接口的方法体中有形参时,在Lambda表达式的括号里面可以写上参数类型,也可以选择不写,与参数个数无关;多个参数时,要么把参数类型都去掉,要么都不去掉。
2.3 一个案例搞懂内部类、Lambda表达式的创建对象方式
public class C01_LamdaTest {
/*Ⅱ-静态内部类,去实现函数式接口*/
static class InternalClass02 implements AInterface{
/*重写method方法*/
@Override
public void method() {
System.out.println("02-I am a Static Internal Class!-静态内部类");
}
}
/*Ⅲ-非静态内部类*/
class InternalClass03 implements AInterface{
@Override
public void method() {
System.out.println("03-I am a NullStatic Internal Class!-非静态内部类!");
}
}
public static void main(String[] args) {
/*Ⅳ-局部内部类*/
class InternalClass04 implements AInterface{
/*重写method方法*/
@Override
public void method() {
System.out.println("03-I am a Portion Internal Class!-局部内部类");
}
}
/*01-首先新建外部类对象,调用method()方法*/
ExternalClass00 ex00 = new ExternalClass00();
ex00.method();
/*02-创建静态内部类的对象,调用method()方法*/
InternalClass02 ic02 = new InternalClass02();
ic02.method();
/*03-实例内部类,这里需要注意非静态内部类必须要由本类对象去实例化*/
InternalClass03 ico3 = new C01_LamdaTest().new InternalClass03();
ico3.method();
/*04-创建局部内部类对象,调用method()方法*/
InternalClass04 ic04 = new InternalClass04();
ic04.method();
/*Ⅴ-05-匿名内部类,直接利用接口创建对象,创建之后写类体*/
AInterface ac = new AInterface() {
@Override
public void method() {
System.out.println("05-I am a Anonymous Internal Class!-匿名内部类");
}
};
ac.method();
/*Ⅵ-06-Lambda表达式,简化以上的操作,JDK1.8的新特性*/
AInterface lc = () -> {
System.out.println("06-I am a Lambda Expression!-Lambda表达式!");
};
lc.method();
}
}
/*声明一个函数式接口*/
interface AInterface{
void method();
}
/*Ⅰ-外部类,实现AInterface接口*/
class ExternalClass00 implements AInterface{
@Override
public void method() {
System.out.println("01-I am a External Class!-普通外部类");
}
}
第四节-线程的生命周期
一、线程的五大状态
- 新建状态:当一个线程对象被创建,就代表线程进入了新建状态;
- 就绪状态:某个线程对象调用了start()方法;
- 运行状态:某个线程的run()方法开始执行;
- 阻塞状态:当使用某些方法或者同步锁时,使线程阻塞;
- 死亡状态:当线程对象的run()方法弹栈后,就代表此线程已死亡。
二、图解线程生命周期
注意:一个线程对象,不能被启动两次,一旦线程处于死亡状态,该线程就无法使用了,应该是被回收了。
三、线程中的常用方法
3.1 停止线程的做法
- 建议让线程自然停止,或者利用次数,避免在线程中使用死循环;
- 建议使用标记,利用标记停止线程;
- 不要使用stop()/destroy()等已经过时的方法;
代码演示
public class C02_StopAThread implements Runnable{
/*在类中定义一个标记*/
private boolean flag = true;
@Override
public void run() {
int i = 0;
//利用标记来结束这个循环
while (flag){
System.out.println(Thread.currentThread().getName() + "---->正在运行" + i++);
}
System.out.println(Thread.currentThread().getName() + "已经结束了!" + "此时i的值为:" + i);
}
//主方法
public static void main(String[] args) {
C02_StopAThread css = new C02_StopAThread();
Thread cs = new Thread(css, "分支线程1");
cs.start();
for (int i = 0; i <= 1000; i++) {
//设定条件,当i=998的时候,让分支线程停止;
if(i == 998){
css.stop();
}
System.out.println(Thread.currentThread().getName() + i);
}
}
/*通过在外部自定义一个stop方法,来改变flag标记的值,让分支线程停止!*/
private void stop() {
this.flag = false;
}
}
3.2 线程休眠sleep()
- Thread.sleep(毫秒数);可以让当前线程阻塞一定的毫秒数,再进入就绪状态;
- 该方法存在异常InterruptedException;
- 每个对象都有一个锁,sleep()方法并不会释放锁;
- sleep()可以用来模拟网络延迟,倒计时等;
模拟倒计时:
public class C03_TimeCountDown implements Runnable {
@Override
public void run() {
int i = 10;
while (i >= 0){
try {
Thread.sleep(1000);
System.out.println(i --);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread tt = new Thread(new C03_TimeCountDown());
tt.start();
}
}
3.3 线程礼让方法yield()
- 是Thread类中的一个静态方法,Thread.yield();可以让当前正在执行的线程转为就绪状态,重新参与时间片的抢夺;
- 由于是进入了就绪状态,因此该线程有可能继续抢到了时间片,又开始运行。
3.4 join()方法
- 是Thread类中的一个实例方法,合并线程,待此线程执行完后,再执行其他线程,此时其他线程进入阻塞状态;
- 想象成插队。
代码演示:
public class C04_JoinTest implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
//主方法
public static void main(String[] args) {
Thread tt = new Thread(new C04_JoinTest());
tt.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + i);
if(i == 50){
try {
tt.join();//此时,主线程被迫进入阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
3.5 获取线程的状态getState()
- 线程的状态有以下六种情况:NEW, RUNNABLE, BLOCKED, WAATTING, TIMED_WATTING, TERMINATED
- 可以使用Thread类中实例方法getState()来获取线程的状态。
3.6 改变线程的优先级
- 在Java中提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定给不同线程分配不同的调度概率;
- 线程的优先级用数字表示,范围从1-10;
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
- 可以使用下列方法,改变线程的优先级:
getPriority().setPriority(int x);
但是要注意:
- 虽然可以把线程的优先级设置到最高10,但是该线程就不一定会先执行,与该线程是否抢夺到时间片有关,但是一般是线程优先级高的先执行。
- 当要设置某个线程的优先级时,设置优先级的操作一定是在线程启动之前完成的。
3.7 守护线程
- 线程分为用户线程和守护线程;
- 虚拟机需要确保用户线程执行完毕,可以不等待守护线程执行完毕;
- 常见的守护线程类型:后台操作日志、监控内存、垃圾回收等......
- 将某个线程设置为守护线程需要调用Thread类中setDaemon(boolean x)方法,x默认值为false,表示用户线程,设置为true,表示守护线程,同样需要在线程启动之前就设置好。