初识多线程
多线程
什么是多线程
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单元。
简单理解:应用软件中互相独立。可以同时运行的功能。
进程
进程是程序的基本执行实体。
多线程的应用场景
软件中的耗时操作,拷贝、迁移大文件,加载大量的资源文件,所有的聊天软件,所有的后台服务器等。
多线程的两个概念
并发
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行
并行:在同一时刻,有多个指令在多个CPU上同时执行。
多线程的实现方式
①继承Thread类的方式进行实现
package com.example.threadcase01;
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"HelloWorld");
}
}
}
package com.example.threadcase01;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式
* 1.自己定义一个类继承Thread类
* 2.重写run方法
* 3.创建子类的对象并启动线程
* */
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
②实现Runnable接口的方式进行实现
package com.example.theadcase02;
public class ThreadDemo {
public static void main(String[] args) {
/*
*多线程的第二种实现方式
* 1.自己定义一个类实现Runnable接口
* 2.重写run里面的方法
* 3.创建自己的类对象
* 4.创建一个Thread类的对象,并开启线程
* */
//创建MyRun对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
package com.example.theadcase02;
public class MyRun implements Runnable{
@Override
public void run() {
//书写线程里面要执行的代码
for (int i = 0; i < 100; i++) {
//获取当前线程的对象
/*Thread t = Thread.currentThread();
System.out.println(t.getName()+"HelloWorld");*/
System.out.println(Thread.currentThread().getName()+"HelloWorld");
}
}
}
③利用Callable接口和Future接口的方式实现
package com.example.threadcase03;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//1~100之间的和
int sum = 0;
for (int i = 0; i < sum; i++) {
sum += i;
}
return sum;
}
}
package com.example.threadcase03;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种实现方式
* 特点:可以获取到多线程运行的结果
* 1.创建一个类MyCallable实现Callable接口
* 2.重写call (是有返回值的,表示多线程运行的结果)
*
* 3.创建MyCallable对象(表示多线程要执行的任务)
* 4.创建FutureTask对象(作用:管理多线程运行的结果)
* 5.创建Thread对象,并启动(表示线程)
* */
//创建MyCallable对象
MyCallable mc = new MyCallable();
//创建FutureTask对象
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建Thread对象,并启动
Thread t1 = new Thread(ft);
t1.setName("线程1");
t1.start();
//获取线程的结果
Integer result = ft.get();
System.out.println(result);
}
}
多线程三种实现方式对比
常见的成员方法
package com.example.threadcase04;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
* String getName()
* void setName(String name) 设置线程名字(构造方法也可以设置名字)
* 细节:
* 1、如果我们没有给线程设置名字,线程也是也有默认的名字的
* 格式:Thread-X(X表示序号,从0开始)
* 2、如果我们要给线程设置名字,可以用set方法,也可以用构造方法设置
* static Thread currentThread() 获取当前线程对象
* 细节:
* 当jvm虚拟机启动后,会自动的启动多条线程
* 其中有一条线程就叫main线程
* 他的作用就是去调用 main方法,并执行里面所有的代码
* 在以前,我们写的所有代码,其实都是运行在mian线程当中
* static void sleep(long time) 让线程休眠指定时间,单位为毫秒
* 细节:
* 1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
* 2、方法的参数:就表示睡眠的时间,单位毫秒
* 1秒 = 1000毫秒
* 3、当时间到了之后,线程会自动醒来,继续执行下面的其他代码
* */
//1.创建线程对象
// MyThread t1 = new MyThread("唔西迪西");
// MyThread t2 = new MyThread("玛卡巴卡");
//
// //2.开启线程
// t1.start();
// t2.start();
// 哪条线程执行到这个方法,此时获取的就是哪条线程的对象
// Thread t = Thread.currentThread();
// String name = t.getName();
// System.out.println(name);
System.out.println("11111");
Thread.sleep(5000);
System.out.println("2222");
}
}
package com.example.threadcase04;
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);
}
}
}
线程的调度方式
抢占式调度
多个线程抢占CPU的执行权,体现随机性。
非抢占式调度
所有线程轮流执行
package com.example.Threadmethod3;
public class ThreadDemo {
public static void main(String[] args) {
/*
* final void setDeamon(boolean on) 设置为守护线程
* 细节:
* 当其他的非守护线程执行完毕之后,守护线程也会陆续结束
* 通俗易懂:
* 结合以下代码解释:当线程一(唔西迪西)结束了,那么玛卡巴卡线程也没有存在的必要了
* */
//1.创建线程对象
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
//2.给线程设置名字
t1.setName("唔西迪西");
t2.setName("玛卡巴卡");
//3.将线程2设置为守护线程(备胎线程)
t2.setDaemon(true);
//4.开启线程
t1.start();
t2.start();
}
}
package com.example.Threadmethod3;
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(getName()+"@"+i);
}
}
}
package com.example.Threadmethod3;
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName()+"@"+i);
}
}
}
package com.example.threadmethod4;
public class ThreadDemo {
public static void main(String[] args) {
/*
* public static void yield() 出让线程/礼让线程
* */
//1.创建两个线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//2.设置线程名称
t1.setName("唔西迪西");
t2.setName("玛卡巴卡");
//3.开启线程
t1.start();
t2.start();
}
}
package com.example.threadmethod4;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName()+"@"+i);
//出让当前CPU的执行权
Thread.yield();
}
}
}
package com.example.threadmethod5;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
* public final void join() 插入线程/插队线程
* */
MyThread t = new MyThread();
t.setName("土豆");
t.start();
//表示把t线程插入到当前线程之前
//t:土豆
//当前线程:main线程
t.join();
//执行在main线程当中的
for (int i = 1; i <= 10; i++) {
System.out.println("main线程"+i);
}
}
}
package com.example.threadmethod5;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName()+"@"+i);
}
}
}
线程的生命周期
线程安全的问题
案例导入
/** 需求:
*某电影院目前正在上映某国产大片,共有100张电影票,目前有3个窗口售卖,请你设计一个程序模拟该影院卖票* */
package com.example.threadsafe1;
public class MyThread extends Thread{
static int ticket = 0;
@Override
public void run() {
while (true){
if(ticket < 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket + "张票");
}else {
break;
}
}
}
}
package com.example.threadsafe1;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:
*某电影院目前正在上映某国产大片,共有100张电影票,目前有3个窗口售卖,请你设计一个程序模拟该影院卖票
* */
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
卖票引发的安全问题
①相同的票出现了多次。
②出现了超出范围的票。
原因
线程执行时,有随机性。
解决方案:
同步代码块
把操作共享数据的代码锁起来。
package com.example.threadsafe1;
public class MyThread extends Thread{
//表示这个类的所有对象,都可以共享ticked的数据
static int ticket = 0;
//锁对象,一定是唯一的
static Object obj = new Object();
@Override
public void run() {
while (true){
//同步代码块
synchronized (obj){
if(ticket < 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket);
}else {
break;
}
}
}
}
}
package com.example.threadsafe1;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:
*某电影院目前正在上映某国产大片,共有100张电影票,目前有3个窗口售卖,请你设计一个程序模拟该影院卖票
* */
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法
就是把synchronized关键字加到方法上。
package com.example.threadsafe2;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:
*某电影院目前正在上映某国产大片,共有100张电影票,目前有3个窗口售卖,请你设计一个程序模拟该影院卖票
* 利用同步方法完成
* 技巧:先写同步代码块,然后将同步代码块中的代码抽取成方法
* */
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package com.example.threadsafe2;
public class MyRunnable implements Runnable {
int ticked = 0;
@Override
public void run() {
//1.循环
while (true) {
//2.同步代码块(同步方法)
if (method()) break;
}
}
private synchronized boolean method() {
//3.判断共享数据是否到了末尾,如果到了末尾
if (ticked == 100) {
return true;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//4.判断共享数据是否到了末尾,如果没到末尾
ticked++;
System.out.println(Thread.currentThread().getName() + "在卖第" + ticked + "张票");
}
return false;
}
}
Lock锁
package com.example.threadsafe3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
//表示这个类的所有对象,都可以共享ticked的数据
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//同步代码块
// synchronized (MyThread.class){
lock.lock();
try {
if (ticket < 100) {
Thread.sleep(100);
ticket++;
System.out.println(getName() + "正在卖第" + ticket);
} else {
break;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
// }
}
}
}
package com.example.threadsafe3;
import com.example.threadsafe1.MyThread;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:
*某电影院目前正在上映某国产大片,共有100张电影票,目前有3个窗口售卖,请你设计一个程序模拟该影院卖票
*用JDK5Lock的方式实现
**/
com.example.threadsafe1.MyThread t1 = new com.example.threadsafe1.MyThread();
com.example.threadsafe1.MyThread t2 = new com.example.threadsafe1.MyThread();
com.example.threadsafe1.MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
死锁
死锁是一个错误,是由于锁的循环嵌套导致的。例如:有锁1、2,有线程A、B。线程A拿到锁1,线程B拿到锁2。线程A、B都在等对方释放锁,所以此时程序就会卡死,运行不下去。
生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式。
案例导入
假设桌子上有面条就是吃货执行,他负责吃,如果没有面条就是厨师执行。厨师负责做面条。
生产者和消费者的理想情况
厨师先抢到CPU的执行权,此时桌子上没有面条。所以厨师在一开始就要做一碗面条放到桌子上让吃货线程来吃。相当于厨师做一个,吃货吃一个。厨师再做一个吃货,再吃一个。
消费者等待
生产者等待
常见方法
代码实现
基本写法
package com.example.waitandnotify;
public class Cook extends Thread{
/*
* 1.循环
* 2.同步代码块(同步方法)
* 3.判断共享数据是否到了末尾(到了)
* 4.判断共享数据是否到了末尾(没到,执行核心逻辑)
*
* */
@Override
public void run() {
while (true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else {
//判断桌子上是否有食物
if(Desk.foodFlag == 1){
//如果有,等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//如果没有,就制作食物
System.out.println("厨师做了一碗面条");
//修改桌子上的食物状态
Desk.foodFlag = 1;
//叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
package com.example.waitandnotify;
public class Foodie extends Thread{
@Override
public void run() {
/*
* 1.循环
* 2.同步代码块(同步方法)
* 3.判断共享数据是否到了末尾(到了)
* 4.判断共享数据是否到了末尾(没到,执行核心逻辑)
*
* */
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else {
//判断桌子上是否有面条
if(Desk.foodFlag == 0){
try {
// 如果没有,就等待
Desk.lock.wait();//当前线程跟锁进行绑定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
// 把吃完的总数-1
Desk.count--;
// 如果有,就开吃
System.out.println("吃货在吃面条,还能再吃"+Desk.count+"碗面条!!");
// 吃完之后唤醒厨师继续做
Desk.lock.notifyAll();//唤醒这把锁绑定的所有线程
// 修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
package com.example.waitandnotify;
public class Desk {
/*
* 作用:控制生产者和消费者的执行
* */
//是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
package com.example.waitandnotify;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:完成生产者和消费者(等待唤醒机制)的代码
* 完成线程交替执行的代码的效果
* */
//创建线程对象
Cook c = new Cook();
Foodie f = new Foodie();
//给线程设置名字
c.setName("厨师");
f.setName("吃货");
//开启线程
c.start();
f.start();
}
}
阻塞队列方式实现
什么是阻塞队列
连接生产者和消费者之间的管道(阻塞队列)。厨师做好面条之后就可以把面条放进管道。左边的消费者就可以从管道获取面条去吃。我们可以规定管道当中最多可以放多少碗面条。如果最多可放1碗,那么运行结果同上。
阻塞队列的继承结构
代码实现
package com.example.waitandnotify1;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 需求:利用阻塞队列实现生产者和消费者(唤醒等待机制)的代码
* 细节:
* 生产者和消费者必须使用同一个阻塞队列
* */
//1.创建阻塞队列对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//2.创建线程对象,并把阻塞队列传递过去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
//3.开启线程
c.start();
f.start();
}
}
package com.example.waitandnotify1;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断的把面条放到阻塞队列当中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package com.example.waitandnotify1;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断的从阻塞队列获取面条
try {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
线程的状态
Java虚拟机没有定义运行状态。因为当线程抢占到CPU执行权的时候,它会把当前线程交给操作系统去管理。
线程池
package com.example.mythreadpool.threadpool1;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
package com.example.mythreadpool.threadpool1;
import java.util.concurrent.*;
public class MyThreadPoolDemo {
public static void main(String[] args) {
/*
*public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
*public static ExecutorService newFixedThreadPool() 创建一个有上限的线程池
*
* */
//1.创建线程池对象
// ExecutorService pool1 = Executors.newCachedThreadPool();
ExecutorService pool1 = Executors.newFixedThreadPool(3);
//2.提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
//3.销毁线程池
// pool1.shutdown();
}
}
自定义线程池的参数
拒绝策略
线程池多大合适
CPU密集型运算(计算比较多,读取文件比较少)
最大并行数+1
I/O密集型运算(读取本地文件或者数据库比较多)
最大并行数 ∗ 期望 C P U 利用率 ∗ [ 总时间( C P U 计算时间 + 等待时间) / C P U 计算时间 ] 最大并行数*期望CPU利用率*[总时间(CPU计算时间+等待时间)/CPU计算时间] 最大并行数∗期望CPU利用率∗[总时间(CPU计算时间+等待时间)/CPU计算时间]
总时间可以通过Thread dump进行测试。得到结果带入公式进行计算。
什么是最大并行数
4核8线程:好比CPU有四个大脑能同时的并行的去做8件事情。因特尔发明了超线程技术。它可以把原本的四个大脑虚拟成8个(8线程)。相当于最大并行数为8.
package com.example.mythreadpool.threadmethod2;
public class MyThreadPool {
public static void main(String[] args) {
//向Java虚拟机返回可用的处理器数量
int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);
}
}
原文地址:https://blog.csdn.net/m0_71513446/article/details/140690094
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!