2020.5.31
多线程
- 什么是单线程,代码的执行路径只有一条路径。
- 缺点:有可能某个代码耗时时间长需要等待,其他程序不能进行,用户感觉差。
- 多线程:代码的执行路径有多条,这种执行环境称之为多线程环境。一般在支线程运行耗时代码。
线程的概述
- 线程依赖进程,没有进程也就谈不上线程。
- 进程开启之后就会执行很多任务,每个任务就是一个线程。
- 线程是CPU调度的基本单位。
- 我们Java编程,就是线程编写,因为虚拟机就是进程,不能在虚拟机里面运行进程。只能在虚拟机里面运行线程。
- 意义:多线程提高对CPU的使用率。
- 线程的调度是随机性的,没有规律。
进程
- 进程就是正在运行的应用程序。
- 计算机在同一时间点上,多个进程不是同时进行。同一个时间点上只能执行一个进程。感觉同时进行,因为(单核)CPU是在多个进程中高速切换,人的感官不会感觉出来。
- 开启一个进程,就是拥有资源。
并发和并行
- 并发:指的时多个任务,高速的交替执行。同一个实体上的多个事件。多台处理器上同时处理多个任务
- 并行:多个任务在同一时刻同时执行,不同实体上的多个事件。一台处理器上同时处理多个任务
java运行原理
- Java程序依靠虚拟机运行,虚拟机是一个进程,我们开启一个进程后,虚拟机就会调用主线程,所以我们编写的是多线程程序,Java是不能直接调用系统功能的,我们没法直接实现多线程,但是Java虚拟机可以直接调用C/C++写好的程序调用多线程,
- 因为CPU调度的基本单位是线程,在运行多线程程序时,单核CPU执行线程的方式是
怎么创建线程(线程开启的第一种方式)
- 我们可以通过Java提供的Thread类创建线程,和开启线程。
- 线程不要重复开启,重复开启会抛出异常。
- 线程调用start方法开启线程
- run()方法里面封装的是线程要执行的逻辑。
- 可以给线程起名和获取名字。
- 获取主线程的名字和修改名字。
- 多个线程就是并发执行。
- 多个线程的优先级设置。一般不设置,理论上说有效果。优先级高的抢占的概率高一些。
- 线程默认优先级是:5
- 线程的优先级最大是10
- 线程的优先级最小是1
线程的第二种开启方式
- 实现Runable接口,重写run方法,如果一个类继承了另一个类,要想创建多线程,就不能继承Thread类。Java提供了一个接口,实现了此接口就可以创建线程。
线程开启的第三种方法
- 实现Callable接口,重写call方法,执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
- 特点是:当子线程执行结束之后,可以返回线程执行的结果。
- 创建的步骤:
- 创建一个类实现Callable 接口 重写接口中的call方法
- 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
- 创建Thread类, 将FutureTask对象作为参数传进去
Runable/Callable的差别
- Runable:没有返回值,不能抛出异常,只能捕获处理
- Callable:有返回值,可以抛出异常。
代码
1.package org.westos.demo1;
/*Author:LH
CreatTime:2020.05.31.15:13*/
public class Test1 {
public static void main(String[] args) {
System.out.println("主线程前面代码");
System.out.println("主线程前面代码");
System.out.println("主线程前面代码");
MyThread myThread = new MyThread();
// 开启支线程,是创建支线程的实例对象并调用start()方法。
// 注意这里不是调run()方法,如果是调用此方法,就是普通的main()方法调用,还是主线程。
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("A");
}
// 同一个支线程的实例对象不能被开启两次。否则会报错。
MyThread myThread1 = new MyThread();
myThread1.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程后面代码");
}
}
}
//创建分支线程,让一个类继承Thread,并重写run方法。run方法体内的代码就是支线程
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("B");
}
}
}
2.package org.westos.demo2;
/*Author:LH
CreatTime:2020.06.05.15:44*/
public class Test2 {
public static void main(String[] args) {
Mythread th1 = new Mythread("窗口1");
Mythread th2 = new Mythread("窗口2");
Mythread th3 = new Mythread("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Mythread extends Thread {
// 重写构造方法,将线程名称传入有参构造。
public Mythread(String name) {
super(name);
}
//将共享的数据变成静态变量。所有线程共同操作买100张票
static int num = 100;
@Override
public void run() {
while (num > 0) {
// 获取当前线程的名称
System.out.println(this.getName()+"卖出第" + (num--) + "张票");
}
}
}
3.package org.westos.demo4;
/*Author:LH
CreatTime:2020.06.05.16:38*/
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建MyCallable对象
MyCallable myCallable = new MyCallable();
// 实例化 FutureTask类,并将MyCallable对象作为参数传进去。
FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable);
// 创建 Thread类,并将FutureTask类的子类对象传进去
Thread th1 = new Thread(futureTask);
th1.start();
// 获取线程执行的结果
Integer integer = futureTask.get();
System.out.println(integer);
System.out.println("主线程");
System.out.println("主线程");
System.out.println("主线程");
System.out.println("主线程");
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
线程休眠
- 线程在开始之后调用sleep()方法,可以使线程休眠。可以用于阻塞线程
package org.westos.demo2;
/*Author:LH
CreatTime:2020.06.05.14:38*/
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程进来了");
// 获取主线程的对象
Thread thread = Thread.currentThread();
// 设置主线程名称
thread.setName("主线程");
// 设置主线程休眠时间
thread.sleep(2000);
System.out.println(thread.getName());
// 第二中方法创建线程
MyRunable runable = new MyRunable();
Thread th1 = new Thread(runable);
// 设置线程名称
th1.setName("线程1");
th1.start();
// th1.sleep(1000); //设置线程1启动后休眠1秒,在线程启动后设置
Thread th2 = new Thread(runable);
th2.setName("线程2");
// 设置线程为守护线程,伴随着主线程死亡而结束。
th2.setDaemon(true);
th2.start();
System.out.println("主线程");
System.out.println("主线程");
System.out.println("主线程");
}
}
//创建线程的第二种方法,实现Runnable接口,重写run方法。
class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
Thread thread = Thread.currentThread();
String name = thread.getName();
// 判断如果是线程1,就让其每次抢到时间片执行时就休眠0.1秒
if (name.equals("线程1")){
try {
thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程名字"+name+"线程方法");
}
}
}
加入线程
- join();可以让多个线程从并发执行变成串行。
- 这个方法要在线程启动之后调。
package org.westos.demo2;
/*Author:LH
CreatTime:2020.06.05.16:04*/
public class Test3 {
public static void main(String[] args) throws InterruptedException {
numThread th1 = new numThread("th1");
numThread th2 = new numThread("th2");
numThread th3 = new numThread("th3");
// System.out.println("主线程执行");
th1.start();
// th1开启了加入了串行执行,所以先执行th1之后再执行System.out.println("主线程执行");
th1.join();
th2.start();
// th2和th3不会按照串行执行,
th3.start();
System.out.println("主线程执行");
}
}
class numThread extends Thread {
public numThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(this.getName() + "分支线程执行l");
}
}
}
守护线程
- 主线程(用户线程)循环结束时,守护线程也要结束。
- 守护线程设置在线程开始前,setDaemon(true).
package org.westos.demo2;
/*Author:LH
CreatTime:2020.06.05.14:38*/
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程进来了");
// 获取主线程的对象
Thread thread = Thread.currentThread();
// 设置主线程名称
thread.setName("主线程");
// 设置主线程休眠时间
thread.sleep(2000);
System.out.println(thread.getName());
// 第二中方法创建线程
MyRunable runable = new MyRunable();
Thread th1 = new Thread(runable);
// 设置线程名称
th1.setName("线程1");
th1.start();
// th1.sleep(1000); //设置线程1启动后休眠1秒,在线程启动后设置
Thread th2 = new Thread(runable);
th2.setName("线程2");
// 设置线程为守护线程,伴随着主线程死亡而结束。
th2.setDaemon(true);
th2.start();
System.out.println("主线程");
System.out.println("主线程");
System.out.println("主线程");
}
}
//创建线程的第二种方法,实现Runnable接口,重写run方法。
class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
Thread thread = Thread.currentThread();
String name = thread.getName();
// 判断如果是线程1,就让其每次抢到时间片执行时就休眠0.1秒
if (name.equals("线程1")){
try {
thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程名字"+name+"线程方法");
}
}
}
清除阻塞状态/让线程死亡/线程礼让
- interrupt():清除阻塞状态。线程在sleep()和wait()时都是阻塞状态。
- stop():让线程死亡。
- yield():线程礼让,就是暂停当前的执行对象,执行其他线程。但是由于礼让的时间短暂,当礼让时,其他线程没有抢到执行权,那么当前线程就会重新和另一个线程抢执行权。所以礼让效果不是很明显(理论上是你执行一次,我执行一次)
package org.westos.demo3;
/*Author:LH
CreatTime:2020.06.05.14:38*/
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程进来了");
// 获取主线程的对象
Thread thread = Thread.currentThread();
// 设置主线程名称
thread.setName("主线程");
// 设置主线程休眠时间
thread.sleep(2000);
System.out.println("主线程名称:"+thread.getName());
// 第二中方法创建线程
MyRunable runable = new MyRunable();
Thread th1 = new Thread(runable);
// 设置线程名称
th1.setName("线程1");
th1.start();
// 线程休眠也是一种阻塞状态,这里可以使用interrupt清除阻塞状态。
th1.interrupt();
// th1.sleep(1000); //设置线程1启动后休眠1秒,在线程启动后设置
Thread th2 = new Thread(runable);
th2.setName("线程2");
// 设置线程为守护线程,伴随着主线程死亡而结束。
th2.setDaemon(true);
th2.start();
// 让主线程死亡,下面的程序就不能执行了
thread.stop();
System.out.println("主线程");
System.out.println("主线程");
System.out.println("主线程");
}
}
//创建线程的第二种方法,实现Runnable接口,重写run方法。
class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
Thread thread = Thread.currentThread();
String name = thread.getName();
// 判断如果是线程1,就让其每次抢到时间片执行时就休眠0.1秒
if (name.equals("线程1")){
try {
thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程名字:"+name+" 线程方法");
}
}
}
//线程礼让
package homework.demo1;
class Test extends Thread {
public static void main(String[] args) {
Test t1 = new Test();
Test t2 = new Test();
t1.start();
t2.start();
}
public void run() {
System.out.println("1");
// 线程礼让
yield();
System.out.println("2");
System.out.println("3");
System.out.println("4");
System.out.println("5");
System.out.println("6");
}
}
//结果:F:\ruanjian\JDK\jdk-13.0.2\bin\java.exe "-javaagent:F:\ruanjian\IEDA\IntelliJ IDEA 2020.1.1\lib\idea_rt.jar=58283:F:\ruanjian\IEDA\IntelliJ IDEA 2020.1.1\bin" -Dfile.encoding=UTF-8 -classpath F:\untitled\2020.5.31-多线程\out\production\2020.5.31-多线程 homework.demo1.Test
1
1
2
3
4
5
6
2
3
4
5
6
卖票程序
package org.westos.demo5;
/*Author:LH
CreatTime:2020.06.05.15:44*/
public class Test2 {
//三个窗口共同卖100张票
public static void main(String[] args) {
Mythread th1 = new Mythread("窗口1");
Mythread th2 = new Mythread("窗口2");
Mythread th3 = new Mythread("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Mythread extends Thread {
// 重写构造方法,将线程名称传入有参构造。
public Mythread(String name) {
super(name);
}
//将共享的数据变成静态变量。所有线程共同操作买100张票
static int num = 100;
@Override
public void run() {
while (num > 0) {
// 获取当前线程的名称
Thread thread = currentThread();
try {
thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"卖出第" + (num--) + "张票");
}
}
}
买票程序,模拟网络延迟
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eowCWHeI-1591348787323)(https://pic.downk.cc/item/5eda0a15c2a9a83be5302087.jpg)]
- 多线程环境下对共享数据操作出现了线程安全问题,票数出现了负数等错误数据。
@Override
public void run() {
while (num > 0) {
// 获取当前线程的名称
Thread thread = currentThread();
try {
thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+“卖出第” + (num–) + “张票”);
}
}
}
### 买票程序,模拟网络延迟
[外链图片转存中...(img-eowCWHeI-1591348787323)]
1. 多线程环境下对共享数据操作出现了线程安全问题,票数出现了负数等错误数据。