【JavaSE】Java基础语法(三十五):多线程实战(1)

简介: 1. 多线程入门1.1 多线程相关概念并发与并行并行:在同一时刻,有多个任务在多个CPU上同时执行。并发:在同一时刻,有多个任务在单个CPU上交替执行。进程与线程进程:就是操作系统中正在运行的一个应用程序。线程:就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。

1. 多线程入门

1.1 多线程相关概念

  • 并发与并行
  • 并行:在同一时刻,有多个任务在多个CPU上同时执行。
  • 并发:在同一时刻,有多个任务在单个CPU上交替执行。
  • 进程与线程
  • 进程:就是操作系统中正在运行的一个应用程序。
  • 线程:就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。

1.2 什么是多线程

  • 是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
  • 好处 : 提高任务的执行性能。

1.3 多线程的创建方式

1.3.1 继承 Thread 的方式

//  基本步骤 :
//  1 创建一个类继承Thread类。
//  2 在类中重写run方法(线程执行的任务放在这里)
//  3 创建线程对象,调用线程的start方法开启线程。
public class MyThread01 {
    public static void main(String[] args) {
        // 创建线程对象,调用线程的start方法开启线程。
        MyThread mt = new MyThread();
        mt.start();
        // main方法中的任务
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
    }
}
// 创建一个类继承Thread类。
class MyThread extends Thread {
    // 在类中重写run方法(线程执行的任务放在这里)
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
    }
}

1.3.2 实现 Runnable 接口的方式

//  基本步骤 :
//  1 定义任务类实现Runnable,并重写run方法
//  2 创建任务对象
//  3 使用含有Runnable参数的构造方法,创建线程对象并指定任务。
//  4 调用线程的start方法,开启线程
public class MyThread02 {
    public static void main(String[] args) {
        // 创建线程对象,调用线程的start方法开启线程。
        MyRunnable mr = new MyRunnable();
        Thread thread= new Thread(mr);
        thread.start();
        // main方法中的任务
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
    }
}
// 1 定义任务类实现Runnable,并重写run方法
class MyRunnable implements Runnable {
    // 在类中重写run方法(线程执行的任务放在这里)
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("i:" + i);
        }
    }
}

1.3.3 实现 Callable 接口的方式

public class Thread3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadThree threadThree = new ThreadThree();
        FutureTask task = new FutureTask(threadThree);
        Thread thread = new Thread(task);
        thread.start();
        //System.out.println(task.get());
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}
class ThreadThree implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
        return "end ";
    }
}

1.3.4 Thread 类中常用方法

String getName():返回此线程的名称

Thread类中设置线程的名字

void setName(String name):将此线程的名称更改为等于参数 name

通过构造方法也可以设置线程名称

public static Thread currentThread():返回对当前正在执行的线程对象的引用

public static void sleep(long time):让线程休眠指定的时间,单位为毫秒

线程有两种调度模型

分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

1.3.5 sleep() 方法 和 wait() 方法区别:

sleep方法是Thread类的静态方法,wait()是Object超类的成员方法

调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁。sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。在调用sleep()方法的过程中,线程不会释放对象锁。

因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。

而当调用wait()方法的时候,线程会放弃对象锁,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。

sleep方法需要抛异常,wait方法不需要

sleep方法可以在任何地方使用,wait方法只能在同步方法和同步代码块中使用

2. 线程安全

2.1 线程安全产生的原因

多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了

举例:略

问题出现的原因 : 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了

2.2 线程的同步

概述 : java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性


分类


同步代码块

同步方法

锁机制,Lock

2.3 同步代码块

同步代码块 : 锁住多条语句操作共享数据,可以使用同步代码块实现


第一部分 : 格式

synchronized(任意对象) {
  多条语句操作共享数据的代码         
}

第二部分 : 注意

1 默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭

2 当线程执行完出来了,锁才会自动打开


第三部分 : 同步的好处和弊端

好处 : 解决了多线程的数据安全问题

弊端 : 当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率


注意:当该多线程类实现方式是继承Thread时,创建多个线程对象的时候,并且锁对象是 this 的时候 那么这个锁对象其实不是唯一的,会有问题滴。

public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    @Override
    public void run() {
        while (true) {
            synchronized (Ticket.class) {
                // 如果票的数量为0 , 那么停止买票
                if (ticketCount <= 0) {
                    break;
                } else {
                    // 模拟出票的时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 有剩余的票 , 开始卖票
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
                }
            }
        }
    }
}

2.4 同步方法

同步方法:就是把synchronized关键字加到方法上

格式:修饰符 synchronized 返回值类型 方法名(方法参数) { }

同步代码块和同步方法的区别:

1 同步代码块可以锁住指定代码, 同步方法是锁住方法中所有代码

2 同步代码块可以指定锁对象, 同步方法不能指定锁对象

注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。

1 对于非 static 方法, 同步锁就是this。

2 对于 static 方法, 我们使用当前方法所在类的字节码对象(类名.class)。 Class类型的对象


/*
    同步方法:就是把synchronized关键字加到方法上
    格式:修饰符 synchronized 返回值类型 方法名(方法参数) {    }
    同步代码块和同步方法的区别:
        1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
        2 同步代码块可以指定锁对象,同步方法不能指定锁对象
    注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
        1 对于非static方法,同步锁就是this。
        2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。   Class类型的对象
 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    @Override
    public void run() {
        while (true) {
            if (method()) {
                break;
            }
        }
    }
    private synchronized boolean method() {
        // 如果票的数量为0 , 那么停止买票
        if (ticketCount <= 0) {
            return true;
        } else {
            // 模拟出票的时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 有剩余的票 , 开始卖票
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
            return false;
        }
    }
}

2.5 Lock 锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,SO ,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock


Lock 中提供了获得锁和释放锁的方法

void lock():获得锁

void unlock():释放锁


Lock 是接口不能直接实例化,这里采用它的实现类 ReentrantLock 来实例化

ReentrantLock 的构造方法

ReentrantLock():创建一个 ReentrantLock 的实例


注意:多个线程使用相同的 Lock 锁对象,需要多线程操作数据的代码放在 lock() 和 unLock()方法之间。一定要确保 unlock 最后能够调用

import java.util.concurrent.locks.ReentrantLock;
/*
    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
    为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
    Lock中提供了获得锁和释放锁的方法
        void lock():获得锁
        void unlock():释放锁
    Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
        ReentrantLock的构造方法
        ReentrantLock():创建一个ReentrantLock的实例
    注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用
 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    private static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();// 加锁
                // 如果票的数量为0 , 那么停止买票
                if (ticketCount <= 0) {
                    break;
                } else {
                    // 模拟出票的时间
                    Thread.sleep(100);
                    // 有剩余的票 , 开始卖票
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();// 释放锁
            }
        }
    }
}


相关文章
|
4月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
246 1
|
4月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
260 1
|
5月前
|
Java
Java基础语法与面向对象
重载(Overload)指同一类中方法名相同、参数列表不同,与返回值无关;重写(Override)指子类重新实现父类方法,方法名和参数列表必须相同,返回类型兼容。重载发生在同类,重写发生在继承关系中。
182 1
|
5月前
|
存储 SQL NoSQL
Redis-常用语法以及java互联实践案例
本文详细介绍了Redis的数据结构、常用命令及其Java客户端的使用,涵盖String、Hash、List、Set、SortedSet等数据类型及操作,同时提供了Jedis和Spring Boot Data Redis的实战示例,帮助开发者快速掌握Redis在实际项目中的应用。
360 1
Redis-常用语法以及java互联实践案例
|
5月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
216 0
|
5月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
401 16
|
6月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
6月前
|
算法 Java 测试技术
零基础学 Java: 从语法入门到企业级项目实战的详细学习路线解析
本文为零基础学习者提供完整的Java学习路线,涵盖语法基础、面向对象编程、数据结构与算法、多线程、JVM原理、Spring框架、Spring Boot及项目实战,助你从入门到进阶,系统掌握Java编程技能,提升实战开发能力。
365 0
|
6月前
|
存储 Java 容器
Java基本语法详解
本文深入讲解了Java编程的基础语法,涵盖数据类型、运算符、控制结构及数组等核心内容,帮助初学者构建坚实的编程基础。