多线程
一、对于多线程的了解
1、对于程序的理解
1程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
2、什么是进程(process)
进程(process)是程序的一次执行过程,或是正常运行的一个程序。是一个动态的过程: 有它自身的产生、存在和消亡的过程
3、什么是线程(thread)
进程可进一步细化为线程,是一个程序内部的一条执行路径
二、进程和线程的区别
1、从属关系不同
进程是正在运行程序的实例,进程中包含了线程 线程不能包含进程
2、描述侧重点不同
进程是操作系统分配资源的基本单位,而线程是操作系统调度的基本单位
3、共享资源不同
多个进程间不能共享资源,每个进程有自己的堆、栈、虚存空间、文件描述符等信息, 而线程可以共享进程资源文件。
4、上下文切换速度不同
线程上下文切换速度快(上下文切换指的是:从一个进程切换到另一个进程),进程上下文切换速度慢
5、操作者不同
一般情况下,进程的操作者是操作系统,而线程的操作者是编程人员
三、多线程中继承Thread类常用的方法
/**
* 1、run() 重写的run方法 线程在被调动时执行的操作
*
* 2、start() 启动当前线程 调用当前线程的run方法
*
* 3、currentThread() 是一个静态方法 返回当前代码的线程
*
* 4、getName() 获取当前线程的名字
*
* 5、setName() 设置当前线程的名字
*
* 6、yield() 释放当前CPU的执行权 有可能下次执行的时候还会抢到执行权
*
* 7、join() 在线程a中调用线程b的join方法,线程a会进入阻塞状态,等线程b执行完以后, 线程
* a才会结束阻塞状态
*
* 8、sleep() 让当前线程睡眠指定的milliTime毫秒
*
* 9、isALive() 判断当前线程是否存活
*
* 10、stop() 已过时,不推荐使用
*/
/**
* 线程的优先级:
* MIN_PRIORITY = 1
* NORM_PRIORITY = 5 默认的优先级
* MAX_PRIORITY = 10
* 说明: 高优先级的线程抢占低优先级线程的执行权。但是只是从概率上讲,高优先级的线程高概率会被优先执行
*
* get priority()
* set priority(int p)
*/
四、线程的生命周期
新建:
thread类的子类的对象被声明并创建时,新生的线程对象处于被创建状态。
就绪:
处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行条件,只是还没分配到CPU资源。
运行:
当就绪的线程被调度,并且获得CPU资源时,便进入运行状态
阻塞:
在某种特殊情况下,被人为挂起或者执行输入输出操作时,让出CPU并临时终止自己的行为,进入阻塞状态
死亡:
线程完成了它的全部工作或线程提前被强制性的终止或出现异常导致结束。
五、创建多线程的方式—>>代码实现
1、继承thread类(方式一)
写法(一)
package com.gaokangxiang.thread;
/**
* 创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
*
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/25 22:20
*/
public class MyThread03 {
/**
* 多线程的创建一: 继承Thread类的方式
* 1、创建一个继承于thread类的子类
* 2、重写thread类的run方法、将此线程的操作执行在run方法中
* 3、创建thread类的子类的对象
* 4、对象名调用start方法
*
*/
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
Demo02 demo02 = new Demo02();
demo01.start();
demo02.start();
}
}
class Demo01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class Demo02 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" +i);
}
}
}
}
2、写法(二)
package com.gaokangxiang.thread;
/**
* 创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
*
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/25 22:20
*/
public class MyThread04 {
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
// 创建thread类的匿名子类
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
2、修改线程的名字
package com.gaokangxiang.thread;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/26 18:20
*
* 修改线程的名字和获取线程的名字
*/
public class MyThread07 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
// 修改线程的名字
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName());
MyThread07 myThread07 = new MyThread07();
myThread07.setName("子线程");
myThread07.start();
}
}
3、线程中断
package com.gaokangxiang.thread;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/26 18:43
*/
public class MyThread08 extends Thread{
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
if (i == 20) {
// 进程中断
Thread.currentThread().interrupt();
}
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "--" + i);
} catch (InterruptedException e) {
return;
}
}
}
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
MyThread08 myThread08 = new MyThread08();
myThread08.start();
}
}
2、实现Runnable接口(方式二)
package com.gaokangxiang.runnable;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/26 21:43
*
* 创建多线程的方式:实现Runnable接口
* 1、创建一个是实现Runnable接口的类
* 2、实现类去实现Runnable类的抽象方法:run方法
* 3、创建实现类的对象
* 4、将此对象作为参数传递到Thread类构造器中,创建Thread类的对象
* 5、通过Thread类的对象调用start方法
*
* 比较线程 thread类 和 runnable接口
* 开发中,优先选择Runnable接口的方式
* 原因:
* 1、实现的方式没有类的单继承性的局限性
* 2、实现的方式更适合来处理多个线程有共享数据的情况
*
* 两者之间的联系:
* public class Thread implements Runnable
*
* 相同点:
* 1、两种方式都需要重写run方法
* 2、将线程需要执行的逻辑写在run方法中
*/
public class MyRunnable01 implements Runnable {
@Override
public void run() {
// 遍历100以内的偶数
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
// 创建实现类的对象
MyRunnable01 myRunnable = new MyRunnable01();
// 将此对象作为参数传递到Thread类构造器中,创建Thread类的对象
Thread thread01 = new Thread(myRunnable);
// 在启动一个线程
Thread thread02 = new Thread(myRunnable);
// 修改名字
thread01.setName("线程一");
// 通过Thread类的对象调用start方法
thread01.start();
// 修改名字
thread02.setName("线程二");
// 通过Thread类的对象调用start方法
thread02.start();
}
}
3、实现Runnable接口—>>解决线程安全问题< 一 >
package com.gaokangxiang.runnable;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/27 12:54
* <p>
* Runnable接口
* 需求:创建三个窗口卖,总共卖100张票
* 问题:
* 在买票过程中,有可能会出现重票、错票的问题---->>线程不安全的问题
* <p>
* 原因:
* 当某个线程操作车票的时候,还没操作完成时,其他线程也参与进来操作车票
* <p>
* 解决:
* 当a线程操作车票还没操作完成时,其他线程不参与进来,直到a线程操作完成退出后,其他线程在参与进来
* 这种情况既是线程a出现阻塞,也不会被改变。
* <p>
* 在java中,我们通过同步机制,来解决线程的安全问题
* 方式一:
* synchronized(同步监视器){
* 需要被同步的代码----->>操作数据共享的代码就是需要被同步的代码
* }
* 注意:
* 1、共享数据--->>多个线程共同操作的变量
* 2、同步监视器--->>俗称"锁"。任何一个类的对象都可以充当锁
* 要求:多个线程必须共用一把锁
*
* 好处: 同步的方式解决了线程安全的问题
* 坏处: 操作代码时,只有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。
*
* 还可以使用当前类本身作为 synchronized(同步监视器)--->> MyRunnable02.class
*/
public class MyRunnable02 implements Runnable {
/**
* ticket 票有100张
* 三个窗口可以共享一个数据
*/
private int ticket = 100;
// 创建一个同步监视器
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) { // synchronized (MyRunnable02.class)
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":票号为--" + ticket);
ticket--;
} else {
break;
}
}
}
}
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
MyRunnable02 myRunnable02 = new MyRunnable02();
Thread thread01 = new Thread(myRunnable02);
thread01.setName("窗口一");
thread01.start();
Thread thread02 = new Thread(myRunnable02);
thread02.setName("窗口二");
thread02.start();
Thread thread03 = new Thread(myRunnable02);
thread03.setName("窗口三");
thread03.start();
}
}
4、实现Runnable接口—>>解决线程安全问题< 二 >
package com.gaokangxiang.runnable;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/27 12:54
* <p>
* Runnable接口
* 需求:创建三个窗口卖,总共卖100张票
* 问题:
* 在买票过程中,有可能会出现重票、错票的问题---->>线程不安全的问题
* <p>
* 原因:
* 当某个线程操作车票的时候,还没操作完成时,其他线程也参与进来操作车票
* <p>
* 解决:
* 当a线程操作车票还没操作完成时,其他线程不参与进来,直到a线程操作完成退出后,其他线程在参与进来
* 这种情况既是线程a出现阻塞,也不会被改变。
*
* 同步方法
* 方式二:
* 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的方法
* 1、同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
* 2、非静态的同步方法,同步监视器是: this
* 静态的同步方法,同步监视器是: 当前类本身
*/
public class MyRunnable03 implements Runnable {
/**
* ticket 票有100张
* 三个窗口可以共享一个数据
*/
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
// 同步方法synchronized 它的同步监视器就是:this
private synchronized void show(){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":票号为--" + ticket);
ticket--;
}
}
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
MyRunnable03 myRunnable03 = new MyRunnable03();
Thread thread01 = new Thread(myRunnable03);
thread01.setName("窗口一");
thread01.start();
Thread thread02 = new Thread(myRunnable03);
thread02.setName("窗口二");
thread02.start();
Thread thread03 = new Thread(myRunnable03);
thread03.setName("窗口三");
thread03.start();
}
}
5、实现Runnable接口—>>解决线程安全问题< 三 >
package com.gaokangxiang.runnable;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/28 11:55
*
* 解决线程安全问题的方式三:
* Lock锁 --->> jdk5.0后新增的
*
* synchronized 和 lock有什么不同?
* 答: synchronized机制在执行完相应的同步代码之后,会主动释放同步监视器
* lock机制需要手动锁定同步代码lock(),结束同步监视器也需要手动结束unlock()
*
* 解决线程安全问题有几种方法?
* 答: 三种
* 1、同步监视器
* 2、同步方法
* 3、lock锁
*/
public class MyRunnable05 implements Runnable{
/**
* 票数
*/
private int ticket = 100;
/**
* 实例化ReentrantLock对象
*/
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){
// 将需要被同步的代码块放入try结构中,finally结尾
try {
// 调用锁定的lock方法,保证只有一个线程进入
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为---" + ticket);
ticket--;
} else {
break;
}
} finally {
// 调用解锁的方法unlock
lock.unlock();
}
}
}
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
MyRunnable05 myRunnable05 = new MyRunnable05();
Thread thread01 = new Thread(myRunnable05);
Thread thread02 = new Thread(myRunnable05);
Thread thread03 = new Thread(myRunnable05);
thread01.setName("窗口一");
thread02.setName("窗口二");
thread03.setName("窗口三");
thread01.start();
thread02.start();
thread03.start();
}
}
6、演示线程的死锁问题
package com.gaokangxiang.runnable;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/28 11:23
*
* 演示线程的死锁问题
*/
public class MyRunnable04 {
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
// 创建thread类的匿名子类
new Thread() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append(1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s1.append(2);
}
}
}
}.start();
new Thread() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s1.append(3);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s1.append(4);
}
}
}
}.start();
}
}
7、关于线程通信的例子
package com.gaokangxiang.runnable;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/29 8:14
*
* 线程通信的例子: 使用两个线程打印1-100,使两个线程交替打印
*
* 这里涉及到三个方法:
* wait() : 一旦执行此方法,当前线程会进入阻塞状态,会释放同步监视器
* notify() : 一旦执行此方法,就会唤醒被wait的线程。如果多个线程被阻塞,就会唤醒优先级高的线程
* notifyAll() : 一旦执行此方法,就会唤醒所有被wait的线程
*
* wait() notify() notifyAll() : 这三个方法必须使用在同步代码块或同步方法中。
* wait() notify() notifyAll() : 这三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
* wait() notify() notifyAll() : 这三个方法是定义在object类中的。
*
* sleep方法和wait方法的异同?
* 相同点 : 都可以让线程进入阻塞状态
* 不同点 :
* 1、声明位置不同 sleep方法在thread类中声明 wait方法在object类中声明
* 2、调用要求不同 sleep方法可以在任意需要的场景下调用
* wait方法在同步代码块或同步方法中调用
* 3、关于是否释放同步监视器 如果两个方法都使用在同步代码块或同步方法中,
* sleep不会释放同步监视器,wait会释放同步监视器
*/
public class MyRunnable06 implements Runnable {
private int num = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (num <= 100) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
MyRunnable06 myRunnable06 = new MyRunnable06();
Thread thread01 = new Thread(myRunnable06);
Thread thread02 = new Thread(myRunnable06);
thread01.setName("线程一");
thread02.setName("线程二");
thread01.start();
thread02.start();
}
}
8、关于多线程的练习
package com.gaokangxiang.runnable;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/29 16:14
* <p>
* 需求:
* 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer) 从店员处取走产品,
* 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员
* 会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品
* 了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
* <p>
* 分析:
* 1、是否是多线程的问题? ---是
* 2、是否是共享数据? ---是 共享数据是店员
* 3、如何解决线程的安全问题? ---同步机制
* 4、是否涉及线程的通信? ---是
*/
public class MyRunnable07 {
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consume consume = new Consume(clerk);
producer.setName("生产者一");
producer.start();
consume.setName("消费者一");
consume.start();
}
}
class Clerk {
private int num = 0;
// 生产产品
public synchronized void produceProducer() {
if (num < 20) {
num++;
System.out.println(Thread.currentThread().getName() + ": 开始生产第" +
num + "个产品");
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费产品
public synchronized void consumeProducer() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + ": 开始消费第" +
num + "个产品");
num--;
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread { // 生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ": 开始生产产品---");
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProducer();
}
}
}
class Consume extends Thread { // 消费者
private Clerk clerk;
public Consume(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": 开始消费产品---");
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProducer(); // 调用consumeProducer()
}
}
}
9、创建多线程的方式三—>>实现Callable接口
注:这是JDK5.0后新增的创建多线程的方式
package com.gaokangxiang.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/29 21:56
*
* 创建线程的方式三 : 实现Callable接口
* 1、创建一个实现Callable的实现类
* 2、实现Call方法。将此线程需要执行的操作声明在Call方法中
* 3、创建Callable实现类的对象
* 4、将此Callable实现类的对象传递到FutureTask构造器中,创建FutureTask对象
* 5、将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法
*
* 如何理解实现callable接口的方式创建多线程比实现runnable接口的方式创建多线程方式强大
* 1、Call方法可以有返回值
* 2、Call方法可以抛出异常,被被外面的操作捕获,获取异常信息
* 3、Callable是支持泛型的
*/
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
int num = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 ==0){
System.out.println(i);
num += i;
}
}
return num;
}
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask(myCallable);
new Thread(futureTask).start();
try {
// get方法返回值是FutureTask构造器参数Callable实现类重写的Call方法的返回值
Object num = futureTask.get();
System.out.println(num);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
10、创建多线程的方式四—>>使用线程池
1、背景
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
2、思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
使用线程池的好处
➢提高响应速度[ (减少门创建新线程的时间)
➢降低资源消耗(重复利用线程池中线程,不需要每次都创建)
➢便于线程管理
corePoolSize: 核心池的大小
maximumPoolSize:最大线程数
keepAliveTime: 线程没有任务时最多保持多长时间后会终止
代码实现:
package com.gaokangxiang.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 遍历1-100以内的偶数和奇数
* @author 高康翔
* @version 1.0.0
* @Date 2022/4/29 23:18
*/
class MyThreadPool01 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
}
class MyThreadPool02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
}
public class MyThreadPool {
// 这是一个main方法,是程序的入口
public static void main(String[] args) {
// 1、提供指定线程数量的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// 2、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
pool.execute(new MyThreadPool01());// 适合适用于Runnable
pool.execute(new MyThreadPool02());// 适合适用于Runnable
//pool.submit();适用于callable接口
// 关闭连接池
pool.shutdown();
}
}