【JavaSE】一文搞懂Java多线程,代码示例,清晰明了(下)

简介: 【JavaSE】一文搞懂Java多线程,代码示例,清晰明了(下)

前言


本文为Java多线程相关知识,Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~

本文上接:【多线程】一文搞懂Java多线程,代码示例,清晰明了(上)


五、守护线程

1.守护线程


线程分为用户线程和守护线程

守护线程要在thread.start()之前设置

主线程结束,守护线程自动销毁

虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕

如:后台记录日志操作,监控内存,垃圾回收等待等…

代码展示:

package com.wang.多线程;
public class DaemonTest implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("守护线程守护着你!!!");
        }
    }
    public static void main(String[] args) {
        DaemonTest daemonTest = new DaemonTest();
        You you = new You();
        Thread thread = new Thread(daemonTest);
        thread.setDaemon(true);  // 默认为false,即正常线程
        thread.start(); // 守护线程启动
        new Thread(you).start();
    }
}
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 365; i++) {
            System.out.println(i + ":<<<<<<<<<<活着!!!>>>>>>>>>>>");
        }
        System.out.println("<<<<<<<<<<<<<<<<<Good By>>>>>>>>>>>>>>>>>");
    }
}

六、线程同步


概念:线程同步问题指的是多个线程操作同一个资源对象,如果不进行线程同步,将会报错,具体错误是由于内存存储空间中一块内存中存储的信息在同一时刻保持一致,但是被取走后信息会更新,多个线程对于同一个对象进行操作后会导致重复错误。


1.买票问题


线程不安全情况下会出现有人买到第-1张票的问题。

代码展示:


package com.wang.多线程;
// 买票问题
public class ThreadSynchronization1 implements Runnable{
    private int count = 10;
    boolean flag = true;
    public static void main(String[] args) {
        ThreadSynchronization1 ts = new ThreadSynchronization1();
        new Thread(ts, "李白").start();
        new Thread(ts, "杜甫").start();
        new Thread(ts, "王维").start();
    }
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    // synchronized同步方法 锁的是this
    public synchronized void buy() {
        if (count <= 0) {
            flag = false;
            return;
        }
        // 模拟延时
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "买到了" + count--);
    }
}
// 打印
李白买到了10
李白买到了9
李白买到了8
李白买到了7
李白买到了6
李白买到了5
李白买到了4
李白买到了3
李白买到了2
李白买到了1



2.银行取钱问题


线程不安全情况下会出现最终余额出现负值情况的问题。

代码展示:

package com.wang.多线程;
public class ThreadSynchronization2{
    public static void main(String[] args) {
        // 创建转户
        Account account = new Account(2000, 202209);
        Bank bank = new Bank(account, 1500, "李白");
        Bank bank1 = new Bank(account, 500, "杜甫");
        bank.start();
        bank1.start();
    }
}
// 转户类
class Account {
    public int money;
    public int id;
    public Account(int money, int id) {
        this.money = money;
        this.id = id;
    }
}
// 银行:模拟取款
class Bank extends Thread {
    public Account account; // 转户
    public int subMoney; // 取多少钱
    public int newMoney; // 现在手里多少钱
    public Bank(Account account, int subMoney, String name) {
        super(name);
        this.account = account;
        this.subMoney = subMoney;
    }
    // 取钱
    @Override
    public void run() {
        /** synchronized默认锁的是this,也就是当前对象
         *  所以使用同步块指定锁的对象
         *  锁的对象是变化的量,即增删改的对象
         */
        synchronized (account) {
            // 先判断转户还有没有钱
            if (account.money - subMoney <= 0) {
                System.out.println(this.getName() + "钱不够了,取不了");
                return;
            }
            // 延迟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 余额
            account.money -= subMoney;
            // 手里现在的钱
            newMoney += subMoney;
            System.out.println(account.id + "的余额为" + account.money);
            System.out.println(this.getName() + "手里的钱有" + newMoney);
        }
    }
}
// 打印
202209的余额为500
李白手里的钱有1500
杜甫钱不够了,取不了

3.List集合问题


线程不安全情况下会出现多个线程在同一时间将元素添加到了数组的同一位置导致最终数组的大小并不是期望的情况。


代码展示:

package com.wang.多线程;
import java.util.ArrayList;
import java.util.List;
public class ThreadSynchronization3 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打印List集合的大小
        System.out.println(list.size());
    }
}
// 
10000

4.同步方法与同步块


对于同步问题,由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制 ,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。


同步方法:


public siynchronized void method(int args) {};

synchronized方法控制对对象的访问,每个对象对应一把锁;每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行就独占该锁直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行;

缺陷:若将一个大的方法申明为synchronized 将会影响效率


同步块:


synchronized (Obj ){};

Obj称之为同步监视器;

Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器;

同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class;

同步监视器的执行过程:(1)第一个线程访问,锁定同步监视器,执行其中代码;(2)第二个线程访问,发现同步监视器被锁定,无法访问;(3)第一个线程访问完毕,解锁同步监视器;(4)第二个线程访问, 发现同步监视器没有锁,然后锁定并访问。


七、死锁


1.什么是死锁


死锁的定义:多个线程互相抱着对方的需要资源,然后形成僵持。


2.产生死锁的必要条件


互斥条件:一个资源每次只能被一个进程使用

请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

不剥夺条件:进程以获得的资源,在未使用完之前,不能强行剥夺

循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系


PS:根据上面的四个死锁必要条件,我们可以想办法避免死锁


代码展示:

package com.wang.多线程;
public class DeadLockTest {
    public static void main(String[] args) {
        Makeup makeup = new Makeup(0, "李白");
        Makeup makeup1 = new Makeup(1, "杜甫");
        makeup.start();
        makeup1.start();
    }
}
class Lipstick {
}
class Mirror {
}
class Makeup extends Thread {
    // 需要的资源
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    // 选择
    int choice;
    // 使用的人
    String name;
    Makeup(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }
    @Override
    public void run() {
        // 使用
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // makeup方法
    public void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.name + "获得lipstick的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror) {
                System.out.println(this.name + "获得mirror的锁");
            }
        } else {
            synchronized (mirror) {
                System.out.println(this.name + "获得mirror的锁");
                Thread.sleep(2000);
            }
            synchronized (lipstick) {
                System.out.println(this.name + "获得lipstick的锁");
            }
        }
    }
}
//
李白获得lipstick的锁
杜甫获得mirror的锁
杜甫获得lipstick的锁
李白获得mirror的锁


八、lock锁


1.lock与synchronized对比


lock是显示锁(手动开启锁和关闭锁)synchronized是隐式锁,出来作用域自动关闭

lock只有代码块锁,synchronized有方法锁和代码块锁

使用lock锁。Jvm将花费更少的时间来调度线程,性能更好,并且具有更好的扩展性

优先使用顺序:Lock > 同步代码块>同步方法


代码展示:

package com.wang.多线程;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest_14 implements Runnable{
    private int count = 5;
    boolean flag = true;
    private ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        LockTest_14 lockTest = new LockTest_14();
        new Thread(lockTest, "李白").start();
        new Thread(lockTest, "杜甫").start();
        new Thread(lockTest, "王维").start();
    }
    @Override
    public void run() {
        while (true) {
            try {
                // 加锁
                lock.lock();
                if (count > 0) {
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(count--);
                }
                else {
                    break;
                }
            }finally {
                // 解锁
                lock.unlock();
            }
        }
    }
}
// 打印
5
4
3
2
1

九、线程协作


1.管程法


代码展示:

package com.wang.多线程;
public class Cooperation1 {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Production(buffer).start();
        new Consumption(buffer).start();
    }
}
// 生产者
class Production extends Thread {
    Buffer buffer;
    public Production(Buffer buffer) {
        this.buffer = buffer;
    }
    // 生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            buffer.push(new Product(i));
            System.out.println("生产了第" + i + "个产品");
        }
    }
}
// 消费者
class Consumption extends Thread {
    Buffer buffer;
    public Consumption(Buffer buffer) {
        this.buffer = buffer;
    }
    // 消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第" + buffer.pop().id + "个产品");
        }
    }
}
// 产品
class Product {
    int id;
    public Product(int id) {
        this.id = id;
    }
}
// 缓冲区
class Buffer {
    // 需要一个容器大小
    Product[] products = new Product[10];
    // 计数
    int count = 0;
    // 生产者放入产品方法
    public synchronized void push(Product p) {
        // 判断 如果产品满了
        if (count == products.length) {
            // 通知消费者来消费
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果没有满
        products[count] = p;
        count++;
        // 通知消费者消费
        this.notifyAll();
    }
    // 消费者方法
    public synchronized Product pop() {
        // 判断消费者能否消费
        if (count == 0) {
            // 等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果可以消费
        count--;
        Product product = products[count];
        // 消费完了,通知生产
        this.notifyAll();
        return product;
    }
}


2.信号灯法


代码展示:

package com.wang.多线程;
public class Cooperation2_16 {
    public static void main(String[] args) {
        TaoBao taoBao = new TaoBao();
        new Production1(taoBao).start();
        new Consumption1(taoBao).start();
    }
}
class Production1 extends Thread {
    TaoBao taoBao;
    public Production1(TaoBao taoBao) {
        this.taoBao = taoBao;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.taoBao.push(i);
        }
    }
}
class Consumption1 extends Thread {
    TaoBao taoBao;
    public Consumption1(TaoBao taoBao) {
        this.taoBao = taoBao;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.taoBao.buy();
        }
    }
}
class TaoBao {
    int id;
    boolean flag = true; // 标志位
    // 生产
    public synchronized void push(int id) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("生产了第" + id + "件物品");
        this.notifyAll(); // 通知消费者购买
        this.id = id;
        this.flag = !this.flag;
    }
    // 消费
    public synchronized void buy() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("用户购买了第" + this.id + "件物品");
        // 通知生产
        this.notifyAll();
        this.flag = !this.flag;
    }
}

十、线程池


1.线程池


经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。


使用线程池可以提高响应速度(减少了创建新线程的时间),降低资源消耗(重复利用线程池中线程,不需要每次都创建),便于线程管理。


参数:corePoolSize:核心池的大小;maximumPoolSize:最大线程数;keepAliveTime:线程没有任务时最多保持多长时间后会终止。

代码展示:

package com.wang.多线程;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool implements Runnable{
    public static void main(String[] args) {
        // 创建服务线程池(参数即为线程池大小)
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        executorService.execute(new ThreadPool());
        executorService.execute(new ThreadPool());
        executorService.execute(new ThreadPool());
        executorService.execute(new ThreadPool());
        // 关闭线程池
        executorService.shutdown();
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}


后记


Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~

相关文章
|
9天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
11天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
11天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
11天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
35 3
|
11天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
35 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
62 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
40 3
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
28 2
|
3月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
44 2
|
3月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
49 1