目录

​一、线程相关概念​

​1、程序​

​2、进程​

​3、线程​

​2、多线程应用​

​1、创建线程的两种方式​

​2、代码示例​

​ 三、Runnable 接口​

​1、为什么要实现接口​

​2、代码示例:如何使用​

​3、静态代理模式:模拟极简的Tread类​

​4、继承Tread类 和 实现Runnable接口的区别​

 ​​四、线程常用方法​

​1、补充-线程终止​

​2、常用方法​

​3、守护线程​

 ​​五、线程的生命周期​

​ 1、线程的几种状态​

 ​​2、线程状态转换图​

​ 3、代码示例:查看线程状态​

​六、线程同步机制​

​ 1、基本介绍​

 ​​2、synchronized 关键字​

 ​​3、互斥锁​

 ​​4、线程死锁​

 ​​5、释放锁​


一、线程相关概念

1、程序

        程序是一个指令序列。是为完成特定任务、 用某种语言编写的一组指令的集合。简单的说:就是我们写的代码

        它以某些程序设计语言编写,运行于某种目标结构体系上。打个比方,程序就如同以英语(程序设计语言)写作的文章,要让一个懂得英语的人(编译器)同时也会阅读这篇文章的人(结构体系)来阅读、理解、标记这篇文章。一般的,以英语文本为基础的计算机程序要经过编译、链接而成为人难以解读,但可轻易被计算机所解读的数字格式,然后放入运行。

2、进程

        进程是指运行中的程序, 比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。 当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
        进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程 : 有它自身的产生 存在和消亡的过程

3、线程

(1)基本介绍

        线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

        线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

        一个进程可以有很多线程,每条线程并行执行不同。

        同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。


(2)单线程

        同一个时刻, 只允许执行一个线程。(单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。)


(3)多线程

        同一个时刻, 可以执行多个线程,比如 : 一个qq进程, 可以同时打开多个天窗口,一个迅需进程, 可以同时下载多个文件


(4)并发

        同一个时刻,多个任务交替执行,造成一种“貌似同时” 的错觉, 简单的说, 单核cpu实现的多任务就是井发。


(5)并行

        同一个时刻,多个任务同时执行。多核cpu可以实现并行。


2、多线程应用

 1、创建线程的两种方式

(1)继承 Thread 类,重写 run 方法

(2)实现 Runnable 接口,重写 run 方法

(3)Thread 类示意图:

多线程重点知识归纳总结。_同步方法


2、代码示例


        ▶ 当一个类继承了 Thread 类, 该类就可以当做线程使用         ▶ 我们会重写 run 方法,写上自己的业务代码

public class Thread01 {
public static void main(String[] args) throws InterruptedException{//抛出异常

//创建一个Cat对象,当线程使用
Cat cat = new Cat();
//启动线程
cat.start();

//当主线程(main)启动一个子线程(Thread-0) ,主线程不会阻塞(即停止),会继续执行
//这时主线程和子线程是交替执行的(交替速度极快)
//Thread.currentThread().getName() 获取线程的名称
System.out.println("主线程继续执行" + Thread.currentThread().getName());

for (int i = 0; i < 60; i++){

System.out.println("主线程 i=" + i);

//让主线程休眠
Thread.sleep(1000);

}
}
}

//当一个类继承了 Thread类,该类就可以当做线程使用
//我们会重写run方法,写上自己的业务代码
class Cat extends Thread{

//记录次数,为了控制退出线程
int times = 0;

@Override
public void run() {

while (true) {

//该线程每隔一秒,在控制台输出子线程
System.out.println("子线程 :" + (++times) + "线程名=" + Thread.currentThread().getName());

//让该线程休眠一秒
//此处有异常,需要try-catch
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

//退出线程
if (times == 80){
break;//当times到80时,线程就退出了
}

}
}
}

3、底层

多线程重点知识归纳总结。_多线程_02



 三、Runnable 接口

1、为什么要实现接口

  • java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程显然不可能了。
  • java设计者们提供了另外一个方式创建线程,就是通过实现 Runnable 接口来创建线程

2、代码示例:如何使用

public class Runnable01 {
public static void main(String[] args) {

Text text = new Text();

//一定要注意:这里不能直接调用start()

//需要创建Thread对象,把text对象(实现Runnable)放入到Thread
Thread thread = new Thread(text);

//然后再调用 start()方法
thread.start();

}
}

// Text类 实现 Runnable 接口
class Text implements Runnable {

//计数
int count = 0;

@Override
public void run() {//普通方法

while (true) {

System.out.println("hi" + (++count) + Thread.currentThread().getName());

try {//休眠1秒,有异常进行try-catch

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

//退出线程
if (count == 10) {
break;
}

}

}
}

3、静态代理模式:模拟极简的Tread类

public class Runnable01 {
public static void main(String[] args) {

//创建T1对象
T1 t1 = new T1();

//创建ThreadProxy 对象,参数是t1对象
ThreadProxy threadProxy = new ThreadProxy(t1);

//启动线程,调用start()方法
threadProxy.start();
}
}


class T {

}

class T1 extends T implements Runnable {

@Override
public void run() {
System.out.println("T1类");
}

}

//静态代理模式
//模拟了一个极简的Thread类
class ThreadProxy implements Runnable {

//属性,类型是 Runnable
private Runnable target = null;

//构造器
public ThreadProxy(Runnable target) {
this.target = target;
}


//start()方法
public void start() {

//调用start0()方法
start0();
}

//start0()方法
public void start0() {

//调用run方法
run();
}

@Override
public void run() {

if (target != null) {
target.run(); //动态绑定(运行类型T1)
}

}
}

运行顺序:

        ThreadProxy对象的start()方法 --> start0()方法 --> ThreadProxy对象的run() 方法 --> 创建ThreadProxy对象时,传入了t1对象,所以target 不为空 --> 运行类型是传入是t1对象,所以会调用 T1类 的 run()方法


4、继承Tread类 和 实现Runnable接口的区别

  • 从java的设计来看, 通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk文档中我们可以知道Thread类本身就实现了Runnable接口
  • 实现Runnable接口方式更加适合多个线程共享一个资源的情况 并且避免了单继承的限制,建议使用Runnable



四、线程常用方法

1、补充-线程终止

  • 线程终止就是当线程完成任务后,会自动退出。
  • 还可以通过使用变量来控制 run方法 退出的方式停止线程,即通知方式
  • 代码示例

public class Exit_ {
public static void main(String[] args) throws InterruptedException{
T t = new T();
t.start();

//通知方式

//让主线程休眠10秒
Thread.sleep(10 * 1000);

//如果希望main线程去控制t 线程的终止,只需要修改loop
//修改loop的值为false 让t退出run方法,而终止t线程
t.setLoop(false);
}
}

class T extends Thread{

//计数
int count = 0;

//设置一个控制变量,控制线程的退出
private boolean loop = true;

@Override
public void run() {

while (loop){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("线程正在运行...." + (++count));

}
}

//用来修改 loop 属性
public void setLoop(boolean loop){
this.loop = loop;
}
}

2、常用方法

方法名

功能

setName

设置线程称

getName

返回该线程的名称,使之与参数name相同

start

使该线程开始执行,即启动线程,Java 虚拟机底层调用该线程的start0方法。注意:底层会创建新的线程,调用run方法,run方法就是一个简单的方法调用,不会启动新线程

run

调用线程对象 run方法

setPriority

更改线程的优先级

getPriority

获取线程的优先级

sleep

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),(线程的静态方法)

interrupt

中断线程。注意中断线程, 是没有真正的结束线程的。所以一般用于中断正在休眠线程

yield

线程的礼让。 让出cpu, 让其他线程执行,但礼让的时间不确定 所以也不一定礼让成功

join

线程的插队。 插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务,然后再依次执行其他任务

join :线程插队代码示例

public class Method02 {
public static void main(String[] args) throws InterruptedException{
T2 t2 = new T2();
t2.start();

for (int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程执行...." + i);

if (i == 5){
System.out.println("主线程让子线程先执行");
//join --> 线程插队
t2.join();
System.out.println("子线程执行完,主线程继续执行");
}
}
}
}
class T2 extends Thread{

@Override
public void run() {

for(int i = 1;i <= 20; i++){

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("子线程在执行...." + i);

}

}

}

解释:主线程和子线程会先交替执行,当 i = 5 时子线程会线程插队,然后只执行子线程,当子线程执行完毕,再继续执行主线程。

3、守护线程

  • 用户线程 : 也叫工作线程,当线程的任务执行完毕或通知的方式结束
  • 守护线程 : 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  • 常见的守护线程 : 垃圾回收机制
  • 代码示例:

public class Method03 {
public static void main(String[] args) throws InterruptedException{

MyDaemonThread myDaemonThread = new MyDaemonThread();

//如果我们希望主线程结束后,子线程自动结束,只需将子线程设置成守护线程即可
myDaemonThread.setDaemon(true);

myDaemonThread.start();



for (int i = 1; i <= 10; i++) {
System.out.println("主线程在执行....");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread{
@Override
public void run() {

//无限循环
for (; ;) {

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("子线程在执行....");
}

}
}

关键点:在启动线程之前,把 setDaemon()方法设置成 true


五、线程的生命周期

 1、线程的几种状态

多线程重点知识归纳总结。_java_03

2、线程状态转换图

多线程重点知识归纳总结。_主线程_04

 3、代码示例:查看线程状态

public class State {
public static void main(String[] args) throws InterruptedException{

T t = new T();

//查看初始状态,调用方法getState()
System.out.println(t.getName() + " 状态" + t.getState());

//启动线程
t.start();

//循环查看线程的状态
while (Thread.State.TERMINATED != t.getState()){

System.out.println(t.getName() + " 状态" + t.getState());

Thread.sleep(500);

}

System.out.println(t.getName() + " 状态" + t.getState());
}
}
class T extends Thread{
@Override
public void run() {

while (true){

for (int i = 0; i < 10; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

break;
}

}

}



六、线程同步机制

 1、基本介绍

  • 在多线程编程时,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
  • 也可以这样理解 : 线程同步, 即当有一个线程在对内存进行操作时, 其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作

2、synchronized 关键字

(1)同步代码块
        synchronized (对象){//得到对象的锁,才能操作同步代码

                //需要被同步代码

        }
(2)同步方法

        public synchronized void m1(String name){

                //需要被同步的代码

        }


3、互斥锁

(1)基本介绍

  • Java语言中,引入了对象互斤锁的概念, 来保证共享数据操作的完整性。
  • 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
  • 关键字synchronized 来与对象的互斥锁联系。 当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
  • 同步的局限性 : 导致程序的执行效率要降低
  • 同步方法(非静态的)的锁可以是this, 也可以是其他对象(要求是同一个对象)
  • 同步方法 (静态的)的锁为当前类本身。

(2)注意事项

  • 同步方法如果没有使用static修饰 : 默认锁对象为this
  • 如果方法使用static修饰, 默认锁对象 :当前类.class
  • 实现的落地步骤 : ①需要先分析上锁的代码 ②选择同步代码块或同步方法  ③要求多个线程的锁对象为同一个即可!

(3)代码示例

public class SellTicket {
public static void main(String[] args) {

SellTicket03 sellTicket03 = new SellTicket03();

new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}

//使用Runnable方式
class SellTicket03 implements Runnable{

//控制线程退出
boolean loop = true;

private static int ticketNum = 100; //让多个线程共享ticketnum

//在同一时刻只能有一个线程来执行sell方法,加synchronized关键字
public synchronized void sell(){
if (ticketNum <= 0){
System.out.println("售票结束。。。");
loop=false;
return;
}

System.out.println("剩余票数" + (--ticketNum));
}

@Override
public void run() {

while (loop){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用方法,实现售票
sell();
}

}
}

4、线程死锁

  • 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。

5、释放锁

  • 当前线程的同步方法、 同步代码块执行结束。就会释放锁
  • 当前线程在同步代码块、 同步方法中遇到break,return。就会释放锁
  • 当前线程在同步代码块、同步方法中出现了未处理的Error 或 Exception,导致异常结束。就会释放锁
  • 当前线程在同步代码块、 同步方法中执行了线程对象的 wait()方法, 当前线程暂停,并释放锁
  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方
    法暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,
    该线程不会释放锁
  • 注意:应尽量避免使用suspend()和resume()来控制线程, 方法不再推荐使用