多线程的学习
目录
- 简单了解多线程
- 线程相关的概念
- 多线程的实现方式
- 线程类的常见方法
- 线程的安全问题
- 死锁
- 生产者消费者
第一章 初识多线程
简单了解多线程
多线程是指,从软件或者硬件上实现多个线程并发执行的技术。
具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提高性能。
简单来说就是cpu在多个应用程序之间做高速的切换,给人一种同时执行的感觉。
对多线程技术的初步理解,总结:
1.多线程技术就是同时执行多个应用程序。
2.多线程技术需要硬件支持。
第二章 线程相关概念
实现多线程
并发和并行
- 并行:在同一时刻,有多个指令在多个cpu上同时执行。
- 并发:在同一时刻,有多个指令在单个cpu上交替执行
进程和线程
- 进程:是正在运行的软件。
它有三个特性:
1.独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的基本单位。
2.动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
3.并发性:任何进程可以同其他进程一起执行。
- 线程:是进程中的单个顺序控制流,是一条执行路径。
1.单线程:一个进程如果只有一条执行路径,则称为单线程程序。
2.多线程:一个进程如果有多条执行路径,则称为多线程程序。
总结:
进程:就是操作系统中正在运行的一个应用程序。
线程:就是应用程序中做的事情。比如:某杀毒软件中的,杀毒,扫描木马,清理垃圾等。
第三章 多线程的实现方式
多线程的实现方案
- 继承Thread类的方式进行实现
- 实现Runnable接口的方式进行实现。
- 利用callable和Future接口方式实现
方案1:继承thread类
四个步骤:
1.定义一个MyThread类继承Thread
2.在MyThread类中重写run()方法
3.创建MyThread类的对象
4.启动线程
上代码:
package com.company; public class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("线程开启了" + i); } } } class Test { public static void main(String[] args) { // 创建一个线程对象 MyThread t1 = new MyThread(); // 创建一个线程对象 MyThread t2 = new MyThread(); // 开启一条线程 t1.start(); // 开启第二条线程 t2.start(); } }
执行结果具有随机性,每次结果都不一样。
两个小问题:
1.为什么要重写run()方法?
因为run()是用来封装被线程执行的代码的。
2.run()方法和start()方法的区别是什么?
run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。
start():启动线程,然后由JVM调用此线程的run()方法。
方案2:实现Runnable接口
五个步骤:
1.定义一个类MyRunnable实现Runnable接口。
2.在MyRunnable类中重写run()方法。
3.创建MyRunnable类的对象。
4.创建Thread类的对象,MyRunnable对象作为构造方法的参数。
5.启动线程。
上代码:
package com.thread.demo2; /** * 1.定义一个类MyRunnable实现Runnable接口。 * 2.在MyRunnable类中重写run()方法。 * 3.创建MyRunnable类的对象。 * 4.创建Thread类的对象,MyRunnable对象作为构造方法的参数。 * 5.启动线程。 */ // 定义一个类MyRunnable实现Runnable接口。 public class MyRunnable implements Runnable { // 在MyRunnable类中重写run()方法。 @Override public void run() { for (int i = 0; i <= 100; i++) { System.out.println("第二种方法实现多线程:" + i); } } } package com.thread.demo2; public class Test { public static void main(String[] args) { MyRunnable rn1 = new MyRunnable(); MyRunnable rn2 = new MyRunnable(); Thread t1 = new Thread(rn1); Thread t2 = new Thread(rn2); t1.start(); t2.start(); } }
方案3:Callable和Funture
六个步骤:
1.定义一个MyCallable实现Callable接口
2.在MyCallable类中重写call()方法
3.创建MyCallable类对象
4.创建Thread类的对象,把FutureTask对象,作为构造方法的参数
5.创建thread类的对象,把FutureTask对象作为构造方法的参数
6.启动线程
package com.thread.demo3; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * 六个步骤: * 1.定义一个MyCallable实现Callable接口 * 2.在MyCallable类中重写call()方法 * 3.创建MyCallable类对象 * 4.创建Thread类的对象,把FutureTask对象,作为构造方法的参数 * 5.创建thread类的对象,把FutureTask对象作为构造方法的参数 * 6.启动线程 */ public class MyCallable implements Callable<String> { @Override public String call() throws Exception { for (int i = 0; i < 100; i++) { System.out.println("跟女孩表白的次数" + i); } // 返回值表示运行完毕返回的结果 return "答应"; } } class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable mc = new MyCallable(); FutureTask<String> ft = new FutureTask<>(mc); Thread t1 = new Thread(ft); // ft.get() 获取线程返回的结果 t1.start(); System.out.println(ft.get()); } }
三种实现方式的对比:
第四章 线程类的常见方法
获取和设置线程的名称
获取线程的名字:
String getName():返回此线程的名称
Thread类中设置线程的名字:
void setName(String name):将此线程的名称更改为等于参数name
通过构造方法也可以设置线程名称
上代码:
package com.thread.demo4; /** * 获取线程名字 */ public class MyThread extends Thread { public MyThread() { } public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + "@@@" + i); } } } class Test { public static void main(String[] args) { MyThread mt1 = new MyThread("小强"); MyThread mt2 = new MyThread("小刚"); // mt1.setName("小强"); // mt2.setName("小刚"); mt1.start(); mt2.start(); } }
获得当前线程的对象
public static Thread correntThread():返回当前正在执行的线程对象的引用。
package com.thread.demo5; public class Test { // main 方法其实也是由main线程调用的,而main线程是由jvm虚拟机启动的时候创建的 public static void main(String[] args) { String name = Thread.currentThread().getName(); // 这样就打印出 main 线程的名名字了 System.out.println(name); } }
Thread的sleep 方法
package com.thread.demo6; public class Test { public static void main(String[] args) throws InterruptedException { System.out.println("睡觉前"); // 当前没有其他线程,所以main线程会睡眠 // 因此 会先打印 睡觉前 间隔一秒 打印 睡醒了 Thread.sleep(1000); System.out.println("睡醒了"); } }
那如何让自己创建的线程睡眠呢,看代码:
注意:如果一个类或者接口中的方法,没有抛异常,那么他们的子类/实现类重写的方法,就不能抛异常,必须自己trycatch
package com.thread.demo6; public class Myrunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { // 注意:如果一个类或者接口中的方法,没有抛异常, // 那么他们的子类/实现类重写的方法,就不能抛异常,必须自己trycatch try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " --- " + i); } } } package com.thread.demo6; public class Test { public static void main(String[] args) throws InterruptedException { Myrunnable mr = new Myrunnable(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); t1.start(); t2.start(); } }
后台线程/守护线程
public final void setDaemoon(boolean):设置为守护线程。
package com.thread.demo7; // 第一个线程 public class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + " --- " + i); } } } package com.thread.demo7; // 第二个线程 public class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(getName() + " --- " + i); } } } package com.thread.demo7; public class Test { public static void main(String[] args) { MyThread mt = new MyThread(); MyThread2 mt2 = new MyThread2(); mt.setName("女神"); mt2.setName("备胎"); mt.setDaemon(true); mt.start(); mt2.start(); } }
线程调度
多线程的并发运行:
计算机中的cpu,在任意时刻只能执行一条机器命令,每个线程只有获得cpu的使用权才能执行代码,各个线程轮流获得cpu的使用权,分别执行各自的任务。
线程调度有两种类型:
分时调度模型:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间片
抢占调度模型:优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的cpu时间片相对多一些
例子:
线程的优先级
设置线程的优先级:public final void setPriority(int newPriority)
获取线程的优先级:public final int getPriority()
// 注意:优先级范围是1-10,默认值是5 ,值越高抢到cpu的几率越高,并不是优先执行完
package com.thread.demo8; import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<String> {
@Override public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " === " + i);
}
return "线程执行完毕了";
}
}
class Test {
public static void main(String[] args) {
// 优先级范围是1-10,默认值是5
MyCallable mc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft); t1.setPriority(1);
t1.setName("飞机"); t1.start();
MyCallable mc2 = new MyCallable();
FutureTask<String> ft2 = new FutureTask<>(mc2);
Thread t2 = new Thread(ft2); t2.setPriority(10);
t2.setName("坦克"); t2.start();
}
}
线程生命周期
第五章 线程安全问题