Java多线程——生命周期、并发、临界资源问题

简介: 是进程执行的最小单元。这样说是不是很抽象?比方拿我们的浏览器来说,一个浏览器可以同时下载几幅图片,可以边听歌的同时边聊天、边播放视频的同时还可以打印文件,这每一个行为我们都可看作是一个不同的线程,不同的线程可以同时运行。

进程是什么?


对程序进行占用各种资源的集合。我们打开自己电脑的任务管理器就能看到不同的进程在不停的进行切换,我们可以把每一个.exe文件的执行认为是一个进程


e9bc335b11829a74812cd7b064255630.png


线程是什么?


是进程执行的最小单元。这样说是不是很抽象?比方拿我们的浏览器来说,一个浏览器可以同时下载几幅图片,可以边听歌的同时边聊天、边播放视频的同时还可以打印文件,这每一个行为我们都可看作是一个不同的线程,不同的线程可以同时运行。


那这样做(同时运行)有什么优点呢?(为什么要有多线程?)


随着技术发展,操作系统演变的会为每个进程分配CPU资源,由于CPU运行速度非常快,线程之间频繁切换,使得我们看起来所有的线程都在同一个时刻执行


那什么时候需要使用多线程?


1、程序需要同时执行两个或多个任务


2、需要一些后台运行的程序时


当我们在访问浏览器看网页的时候,我们正在等待图片下载完成,等着百度服务器那边给我们返回结果,那等待的这个过程中其实浏览器是一直占着CPU的,在等待的间隙我们就可以让我们的浏览器先干点其他的事情释放掉CPU资源,不要一直霸占着,等百度服务器把结果响应给我们之后再继续回来执行,这样既提高了程序处理速度,也提高了CPU的利用率


那线程和进程的关系是什么?


我们每打开一个程序就在计算机里增加了一个进程,计算机要给程序分配不同的系统资源,比方说CPU,每个进程所需要的cpu大小也不一样,比方说谷歌浏览器、xmind占的cpu资源比较少就少分配一些,游戏软件或者idea这种就需要分配多一些cpu资源。而一个软件也可能会干不同的事情,比方说QQ,听歌的同时还能QQ截图、收消息,而听歌、截图等这些都可以看成是一个一个的线程


注:进程和进程之间内存空间不共享,但线程和线程共享堆内存和方法区,但是栈内存独立,一个线程一个栈,如:


阿里巴巴和京东他们就可以看作是两个不同的进程,他们之间不共享资源


那线程和进程之间有什么区别?


进程:每个进程都有独立的代码和数据空间,进程间切换开销大


线程:同一个进程中的线程共享资源,每个线程有独立的运行栈和PC,线程切换开销小


我们通过一张图来表示一下它们之间的关系:


一个进程正在运行时至少有一个线程正在运行,比方说main()方法


781db3e27934040d9a29ffe964203d7d.png


线程的生命周期有哪些?


一个线程也是有生到死的过程的,一共有5个线程,我们来看看它们分别都做了些什么吧


309c27e614be2b466fcaba87e8c290b6.png


当我们的程序执行需要两种资源:CPU(谁干活)、内存(要占的地方)


目标就是怎么让我们的cpu在不同程序之间快速切换,怎么用进程实现并发?看似并发实际是切换很快有一定顺序


接下来我们来看看线程具体是怎么使用的吧!


实战演练:


线程的创建


方法一:继承Thread,重写run()方法


public class ThreadCreate {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        //myThread.run();
        System.out.println("主线程执行结束");
    }
}
class MyThread extends Thread {
    //重写run方法,需要并发的任务写到run方法中
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程1--" + i);
        }
    }
}


start()和run()的区别?


继承重写run()方法,run方法就被当成了一个普通方法来运行,在jvm就只有main方法这一个主线程,需要等到run方法体逻辑走完才能执行下面的逻辑。没有实现多线程


缺点:java是单继承,继承Thread类重写run()方法,无法继承其他的类


start方法会产生新线程,用来启动线程,当调用start方法后此时线程变为了就绪态,说明具有争夺CPU的权利,当获得CPU之后便开始执行run方法体内逻辑。实现多线程


8d417417f7b4f241dbcd7b0c2eb04bc3.png


方法二:使用Runnable接口


好处:上面我们讲到有由于java是单继承,为了支持多继承的特点,我们可以实现Runnable接口


package com.example;
public class ThreadCreate {
    public static void main(String[] args) {
        Runnable r1 = () -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程2--" + i);
            }
        };
        Thread t2 = new Thread(r1);
        t2.start();
        System.out.println("主线程执行结束");
    }
}


下面我们再来一起看看线程常用的一些设置


线程的常用方法——线程命名、优先级、礼让


class MyThreads extends  Thread{
    public MyThreads(String name){
        this.setName(name);
    }
}


1、线程命名


public class ThreadName {
    public static void main(String[] args) {
        //方法一:重写构造函数,将名称通过实例化对象的同时传入复制
        MyThreads thread = new MyThreads("young");
        //方法二:通过set方法设置
        Thread t1= new Thread();
        t1.setName("t1");
        //方法三:自带的无参构造函数设置
        Thread t2= new Thread("t2");
        //方法四:实例化线程对象的同时,通过构造方法对线程进行命名
        Thread t3= new Thread(()->{},"t3");
    }
}


2、线程优先级


优先级:1-10,一个线程的缺省优先级是5


  • Thread.MIN_PRIORITY =1
  • Thread.MAX_PRIORITY=10
  • Thread.NORM_PRIORITY=5(默认)


注意:线程的优先级只是说明了线程可以抢到CPU的概率,并不是说就能抢到CPU或者谁的优先级高就先把这个线程执行完


public class ThreadLevel {
    public static void main(String[] args) {
        Runnable r = () -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        };
        //创建连个线程分别叫t1和t2
        Thread t1 = new Thread(r, "Thread-1");
        Thread t2 = new Thread(r, "Thread-2");
        //设置线程t1的优先级比t2的优先级高
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}


3、线程礼让


让出当前的运行状态的线程释放自己CPU资源,运行态——>就绪态


public class ThreadYield {
    public static void main(String[] args) {
        Runnable r= new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                    if(i==3){
                        Thread.yield();
                    }
                }
            }
        };
        Thread t1= new Thread(r,"t1-");
        Thread t2= new Thread(r,"t2-");
        t1.start();
        t2.start();
    }
}


4、线程睡眠


sleep单位是毫秒,每间隔2秒执行一次


class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程执行---" + i);
            try {
                //sleep单位是毫秒,每间隔2秒执行一次
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class ThreadSleep {
    public static void main(String[] args) {
        MyThread2 myThread = new MyThread2();
        myThread.start();
        System.out.println("主线程执行结束");
    }
}


sleep和wait区别?


1、sleep():休眠了自己可以醒过来。使用sleep是别的线程也不可以访问锁定的对象


2、wait():休眠了自己无法醒过来。需要用notify()方法使线程醒过来。使用wait时别的线程可以访问锁定的对象


3、stop方法。泼凉水,直接把进程杀死。比方说正在打开一个程序,stop方法,这个程序可能造成程序就一直关不上了。很粗暴!!


O.waite()方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁


O.notify()方法只会通知,不会释放之前占有的o对象的锁。


什么时候数据在多线程并发的环境下会存在安全问题呢?


1、多线程并发


2、有共享数据


3、共享数据有修改行为


960fec03e368fd6408405a3ce60183cb.png


上图是经典的银行取钱问题,当张三和张三媳妇同时去银行取钱,张三拿着银行卡去柜台取钱,张三媳妇在不知情的情况下拿着存折去银行取钱,他们同时都要取1000元,这时候多线程的情况下就出现了数据不一致的情况,他们共从银行拿走了2000元,而账户余额只有1000元。


那如何解决出现的安全问题?为了更好的讲述线程安全以及解决线程安全的问题,下面我从一个售票员卖票的例子讲起


临界资源问题


什么是临界资源?


多个线程同时争夺资源


实战演练:


class TicketCenter {
    //剩余票数
    static int restCount = 100;
}


package com.example;
import java.util.concurrent.locks.ReentrantLock;
public class CriticalResource {
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount +
                        "张");
            }
        };
        Thread t1 = new Thread(r, "t1--");
        Thread t2 = new Thread(r, "t2--");
        Thread t3 = new Thread(r, "t3--");
        Thread t4 = new Thread(r, "t4--");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
}


我们来看看控制台输出结果:


4e8e1222fffe5a090da46ea7257c2522.png


从输出结果我们可以看出来当多个线程并发的时候产生了数据不一致的情况,t4线程执行完毕之后剩余票数为91张,当t3线程开始执行的时候剩余票数却为97……


此时票数100就是那个临界资源,四个线程都在访问临界资源,导致数据不一致。那如何解决这个问题呢?我们可以通过三个办法来解决:同步代码段、同步方法、显示锁


解决方案有哪些?


方法一、同步代码段


添加synchronized锁,当剩余票数大于0的时候就给临界资源加上锁,此时其他线程在无法访问或修改临界资源,处于就绪状态排队等待,等待的线程需要等到当前线程释放了cpu才能进行访问


注意:当多个并发线程访问同一个同步代码段时,一段时间内只能有一个线程被执行


package com.example;
import java.util.concurrent.locks.ReentrantLock;
public class CriticalResource {
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                //需要在锁当中加一层判断,否则会出现票数剩余负三的情况。
                //原因:剩余票数大于0,线程进入循环,获得了抢占cpu的资格,看到资源加锁了进入排队等待;
                //如果三个线程都进入了排队状态,那么当这三个线程拿到cpu资源之后能够继续执行减减操作,导致票数出现了负三的情况
                synchronized ("") {
                    //方法一:同步代码段
                    //当票数剩余0的时候退出当前循环,表示票已经卖完
                    if (TicketCenter.restCount <= 0) {
                        return;
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount +
                    "张");
                }
            }
        };
        Thread t1 = new Thread(r, "t1--");
        Thread t2 = new Thread(r, "t2--");
        Thread t3 = new Thread(r, "t3--");
        Thread t4 = new Thread(r, "t4--");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
class TicketCenter {
    //剩余票数
    static int restCount = 100;
}


方法二、同步方法


package com.example;
import java.util.concurrent.locks.ReentrantLock;
public class CriticalResource {
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                synchronized ("") {
                    //方法二:同步方法,将要加锁的方法抽成方法,不影响实际效果
                    soldTicket ();
                }
            }
        };
        Thread t1 = new Thread(r, "t1--");
        Thread t2 = new Thread(r, "t2--");
        Thread t3 = new Thread(r, "t3--");
        Thread t4 = new Thread(r, "t4--");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
    //方法二:同步方法
    //需要在锁当中加一层判断,否则会出现票数剩余负三的情况。
    //原因:剩余票数大于0,线程进入循环,获得了抢占cpu的资格,看到资源加锁了进入排队等待;
    //如果三个线程都进入了排队状态,那么当这三个线程拿到cpu资源之后能够继续执行减减操作,导致票数出现了负三的情况
    private synchronized static void soldTicket() {
        if (TicketCenter.restCount <= 0) {
            return;
        }
        System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount + "张");
    }
}
class TicketCenter {
    //剩余票数
    static int restCount = 100;
}


方法三、显示锁


多个线程看到的锁是同一个


package com.example;
import java.util.concurrent.locks.ReentrantLock;
public class CriticalResource {
    public static void main(String[] args) {
        //方法三:显示锁
        Runnable r1 = () -> {
            while (TicketCenter.restCount > 0) {
                lock.lock();
                if (TicketCenter.restCount <= 0) {
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余" + --TicketCenter.restCount + "张");
                lock.unlock();
            }
        };
        Thread t1 = new Thread(r, "t1--");
        Thread t2 = new Thread(r, "t2--");
        Thread t3 = new Thread(r, "t3--");
        Thread t4 = new Thread(r, "t4--");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
class TicketCenter {
    //剩余票数
    static int restCount = 100;
}


如果有想要交流的内容欢迎在评论区进行留言,如果这篇文档受到了您的喜欢那就留下你点赞+收藏+评论脚印支持一下博主~

相关文章
|
4天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
3天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
9天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
32 9
|
12天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
9天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
12天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
26 3
|
11天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
12天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
12天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
40 1
|
16天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
下一篇
无影云桌面