多线程
一:线程和进程
1.1:线程和进程的概述
1. 进程:是内存中运行的一个个软件(比如:QQ, 微信)。
2. 线程:进程内部的一个独立执行单元。堆空间共享,栈空间是独立。
1.2:并发和并行的概述
1. 并行:在一个时间点同时发生多个事件。
2. 并发:在同一个时间段同时发生多个事件。
二:多线程实现
2.1:继承Thread类
代码的具体实现
public class MyThread extends Thread{
/*
* 利用继承中的特点
* 将线程名称传递 进行设置
*/
public MyThread(String name){
super(name);
}
/*
* 重写run方法
* 定义线程要执行的代码
*/
public void run(){
for (int i = 0; i < 20; i++) {
//getName()方法 来自父亲
System.out.println(getName()+i);
}
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("这里是main线程");
MyThread mt = new MyThread("小强");
mt.start();//开启了一个新的线程
for (int i = 0; i < 20; i++) {
System.out.println("旺财:"+i);
}
}
}
2.2:实现Runnable接口
- 基本实现
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public class Demo {
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t = new Thread(mr, "小强");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("旺财 " + i);
}
}
}
- 匿名内部类
public static void main(String[] args) {
//使用匿名内部类方式创建Runnable实例
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("输出"+i);
}
}
});
t1.start();
// lambda 表达式简化语法
Thread t2 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
System.out.println("输出"+i);
}
});
t2.start();
}
2.3:实现Callable接口
/**
* 实现Callable的接口
* 实现一个带返回值的任务
* @创建日期 2020/3/18 15:31
**/
public class Thread03_create03 {
public static void main(String[] args) {
//FutureTask包装我们的任务,FutureTask可以用于获取执行结果
FutureTask<Integer> ft = new FutureTask<>(new MyCallable());
//创建线程执行线程任务
Thread thread = new Thread(ft);
thread.start();
try {
//得到线程的执行结果
Integer num = ft.get();
System.out.println("得到线程处理结果:" + num);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
// 实现Callable接口,实现带返回值的任务
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int num = 0;
for (int i = 0; i < 1000; i++) {
System.out.println("输出"+i);
num += i;
}
return num;
}
}
}
三:Thread类方法
3.1:基本方法
a:构造方法
Thread();
Thread(Sting name);
Thread(Runnable myRunnable);
Thread(Runnable myRunnable,Sting name);
b:常用方法
getName(); //获取线程的名字
start(); //开启一个线程
c:静态方法
static sleep(int 毫秒值);
static currentThread();
四:多线程内存图
4.1:内存流程图
4.2:总结:(多线程在内存中的执行顺序)
第一:JVM运行程序,主方法进栈,从上往下依次执行。当运行到创建线程对象的时候,在堆中创建一个线程对象,根据
对象地址调用start方法,执行完毕后,在栈内存中创建一个新的栈,并把run()方法压到新的栈进行运行(两个栈互不影
响),CPU根据自己的爱好执行任意的栈。run()方法在新的栈运行。说白了就是。开启一个新的栈,把业务逻辑放到新的栈运行。
五:线程安全问题
5.1:为什么会出现线程安全问题
存在着两个或者两个以上的线程,多个线程共享了着一个资源,而且对资源进行操作。
5.2:解决线程安全
5.2.1:同步代码块(synchronized);
public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
}
}
5.2.2:同步方法(synchronized);
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
sellTicket();
}
}
/*
* 锁对象 是 谁调用这个方法 就是谁
* 隐含 锁对象 就是 this
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
}
}
5.2.3:Lock琐;
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket--);
}
lock.unlock();
}
}
}
5.3:总结
a: 线程安全问题:
在堆中有一个变量,有多个线程堆的变量进行加减。一个线程获取了变
量,另外一个线程突然获取CPU,执行自己的线程,获取一个变量,然后对变
量值进行改变,突然原线程抢到了CPU,执行自己的线程,然后改变变量值。
相当于一个变量被执行两次操作,和预期的不也一样,从而造成线程安全问题.
b:解决线程安全问题:
就是在把变量的获取和变量的改变这段程程序变为一个原子性,加锁。
六:线程间通信
6.1:线程间通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望
他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
6.2:等待唤醒机制的代码
//包子类
public class BaoZi {
private boolean flag;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
//包子铺
public class BaoZiPu implements Runnable {
BaoZi baozi ;
public BaoZiPu() {
}
public BaoZiPu(BaoZi baozi) {
this.baozi = baozi;
}
@Override
public void run() {
while(true) {
synchronized (baozi) {
if (!baozi.isFlag()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子做好了");
baozi.setFlag(true);
baozi.notify();
} else {
try {
baozi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//吃货类
public class ChiHuo implements Runnable {
BaoZi baozi;
public ChiHuo() {
}
public ChiHuo(BaoZi baozi) {
this.baozi = baozi;
}
@Override
public void run() {
while(true) {
synchronized (baozi) {
if (baozi.isFlag()) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包子吃完啦。");
baozi.setFlag(false);
baozi.notify();
} else {
try {
baozi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
4.总结(等待唤醒机制的描述)
前提:同一把锁,俩个线程不同的业务逻辑,共享一个资源(根据更改状态,来改变
线程的醒和睡)
过程(比如做包子和吃包子):根据包子的有无,来分别让做包子和吃包子执行休息
和唤醒。如果有包子,做包子抢到了就让他休眠,如果吃货抢到了就他吃,并把包子
状态变为无,吃完唤醒。如果没有包子的状态下,吃包子的抢到就叫他休眠。做包子
的抢到就让他做包子,把包子状态改为有,唤醒吃包子的。(释放琐,就是在同步代
码块执行完毕。)
七:线程状态
7.1:线程状态的六种
创建状态 运行状态 阻塞状态 等待状态 计时等待状态 中止状态
7.2:线程六种状态的转化
7.3:六种状态之间的转化文字描述
创建状态:创建多个线程。线程当得到琐进入运行状态,
没有得到琐进入堵塞状态。线程调用sleep()进行计时等待状
态。计时结束,得到琐,进行运行状态。没有得到琐,进入
堵塞状态。线程调用wait()方法进入等待状态,别的线程调用
唤醒机制。等待状态就被唤醒,继续执行该线程,或者进入
堵塞状态。线程执行完,进入中止状态。
八:线程池
8.1:线程池是什么
容纳多个线程的容器,线程可以重复使用,不需要重复的创建线程对象。
8.2:为什么使用线程池
a:节省资源消耗:线程池中的线程能被反复的使用,无需重复的创建线程对象。
b:提高效率:当线程任务来的时候,无需等到创建线程对象,直接使用线程。
c.便于线程管理:可以根据系统的承受能力,来决定线程池中的线程条数。
8.3:线程池的代码实现
概述:
a:Executors:是线程池的根接口,但是我们在开发时候用的接口为Executors的子接口ExecutorService。
b:创建一个任务对象。
c:用线程池工厂来创造一个线程池
d:提交任务
代码:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
//自己创建线程对象的方式
// Thread t = new Thread(r);
// t.start(); ---> 调用MyRunnable中的run()
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
//service.shutdown();
}
}