多线程与并发编程【守护线程、线程同步】(三)-全面详解(学习总结---从入门到深化)

简介: 多线程与并发编程【守护线程、线程同步】(三)-全面详解(学习总结---从入门到深化)

守护线程



什么是守护线程


 在Java中有两类线程:


       User Thread(用户线程):就是应用程序里的自定义线程。

       Daemon Thread(守护线程):比如垃圾回收线程,就是最典型的守护线程。


守护线程(即Daemon Thread),是一个服务线程,准确地来说 就是服务其他的线程,这是它的作用,而其他的线程只有一种,那 就是用户线程。


守护线程特点:


     守护线程会随着用户线程死亡而死亡。


守护线程与用户线程的区别:


用户线程,不随着主线程的死亡而死亡。用户线程只有两种情况会 死掉,1在run中异常终止。2正常把run执行完毕,线程死亡。

守护线程,随着用户线程的死亡而死亡,当用户线程死亡守护线程 也会随之死亡。


守护线程的使用


/**
* 守护线程类
*/
class Daemon implements  Runnable{
    @Override
    public void run() {
        for(int i=0;i<20;i++){
              System.out.println(Thread.currentThread().getName()+" "+i);
            try {
                Thread.sleep(2000);
           } catch (InterruptedException e){
                e.printStackTrace();
           }
       }
   }
}
class UsersThread implements Runnable{
    @Override
    public void run() {
        Thread t = new Thread(new Daemon(),"Daemon");
        //将该线程设置为守护线程
        t.setDaemon(true);
        t.start();
        for(int i=0;i<5;i++){
          System.out.println(Thread.currentThread().getName()+" "+i);
            try {
                Thread.sleep(500);
           } catch (InterruptedException e){
                e.printStackTrace();
           }
       }
   }
}
public class DaemonThread {
    public static void main(String[] args)throws Exception {
        Thread t = new Thread(new UsersThread(),"UsersThread");
        t.start();
        Thread.sleep(1000);
        System.out.println("主线程结束");
   }
}


线程同步


什么是线程同步


线程冲突现象


同步问题的提出


现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里,只有一台电脑,多个人都想使用。天然的解决办法 就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。


线程同步的概念


处理多线程问题时,多个线程访问同一个对象,并且某些线程还想 修改这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其 实就是一种等待机制,多个需要同时访问此对象的线程进入这个对 象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再 使用。


线程冲突案例演示


我们以银行取款经典案例来演示线程冲突现象。 银行取钱的基本流程基本上可以分为如下几个步骤。


(1)用户输入账户、密码,系统判断用户的账户、密码是否匹配。

(2)用户输入取款金额

(3)系统判断账户余额是否大于或等于取款金额

(4)如果余额大于或等于取款金额,则取钱成功;如果余额小于取 款金额,则取钱失败。

/**
* 账户类
*/
class Account{
    //账号
    private String accountNo;
    //账户的余额
    private double balance;
    public Account() {
   }
    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
   }
    public String getAccountNo() { return accountNo;
   }
    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
   }
    public double getBalance() {
        return balance;
   }
    public void setBalance(double balance) {
        this.balance = balance;
   }
}
/**
* 取款线程
*/
class DrawThread implements Runnable{
    //账户对象
    private Account account;
    //取款金额
    private double drawMoney;
    public DrawThread(Account account,double drawMoney){
        this.account = account;
        this.drawMoney = drawMoney;
   }
    /**
     * 取款线程
 */
    @Override
    public void run() {
        //判断当前账户余额是否大于或等于取款金额
        if(this.account.getBalance() >= this.drawMoney){
          System.out.println(Thread.currentThread().getName()+" 取钱成功!吐出钞
票:"+this.drawMoney);
            try {
                Thread.sleep(1000);
           } catch (InterruptedException e){
                e.printStackTrace();
           }
            //更新账户余额
          this.account.setBalance(this.account.getBalance()- this.drawMoney);
            System.out.println("\t 余额为:"+this.account.getBalance());
       }else{
          System.out.println(Thread.currentThread().getName()+" 取钱失败,余额不足");
       }
   }
}
public class TestDrawMoneyThread {
    public static void main(String[] args) {
        Account account = new Account("1234",1000);
        new Thread(new DrawThread(account,800),"老公").start();
        new Thread(new DrawThread(account,800),"老婆").start();
   }
}


实现线程同步


由于同一进程的多个线程共享同一块存储空间,在带来方便的同 时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这 种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这 种问题。这套机制就是synchronized关键字。


synchronized语法结构:


synchronized(锁对象){ 
   同步代码
 }


synchronized关键字使用时需要考虑的问题:


  需要对那部分的代码在执行时具有线程互斥的能力(线程互斥:并行变串行)。

  需要对哪些线程中的代码具有互斥能力(通过synchronized锁对象来决定)。


它包括两种用法:


synchronized 方法和 synchronized 块。


1 synchronized 方法


通过在方法声明中加入 synchronized关键字来声明,语法如 下:

public  synchronized  void accessVal(int newVal);


synchronized 在方法声明时使用:放在访问控制符(public)之前 或之后。这时同一个对象下synchronized方法在多线程中执行 时,该方法是同步的,即一次只能有一个线程进入该方法,其他 线程要想在此时调用该方法,只能排队等候,当前线程(就是在 synchronized方法内部的线程)执行完该方法后,别的线程才能 进入。


2 synchronized块


synchronized 方法的缺陷:若将一个大的方法声明为 synchronized 将会大大影响效率。 Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范 围,提高效率。


修改线程冲突案例演示

/**
* 账户类
*/
class Account{
    //账号
    private String accountNO;
    //账户余额
    private double balance;
    public Account() {
   }
    public Account(String accountNO, double balance) {
        this.accountNO = accountNO;
        this.balance = balance;
   }
    public String getAccountNO() {
        return accountNO;
   }
    public void setAccountNO(String accountNO) {
        this.accountNO = accountNO;
}
    public double getBalance() {
        return balance;
   }
    public void setBalance(double balance) {
        this.balance = balance;
   }
}
/**
* 取款线程
*/
class DrawThread implements Runnable{
    //账户对象
    private Account account;
    //取款金额
    private double drawMoney;
    public DrawThread(){
   }
    public DrawThread(Account account,double drawMoney){
        this.account = account;
        this.drawMoney = drawMoney;
   }
    /**
     * 取款线程体
     */
    @Override
 public void run() {
        synchronized (this.account){
            //判断当前账户余额是否大于或等于取款金额
            if(this.account.getBalance() >= this.drawMoney){
              System.out.println(Thread.currentThread().getName()+" 取钱成功!突出钞票"+this.drawMoney);
                try {
                    Thread.sleep(1000);
               } catch(InterruptedException e) {
                    e.printStackTrace();
               }
                //更新账户余额
              this.account.setBalance(this.account.getBalance() - this.drawMoney);
                System.out.println("\t 余额为:"+this.account.getBalance());
           }else{
              System.out.println(Thread.currentThread().getName()+" 取钱失败,余额不足");
           }
       }
   }
}
public class TestDrawMoneyThread {
    public static void main(String[] args) {
        Account account = new Account("1234",1000);
        new Thread(new DrawThread(account,800),"老公").start();
        new Thread(new DrawThread(account,800),"老婆").start();
   }
}


线程同步的使用


使用this作为线程对象锁


语法结构:

synchronized(this){
      //同步代码
 }


public  synchronized  void accessVal(int newVal){
    //同步代码
}


/**
* 定义程序员类
*/
class Programmer{
    private String name;
    public Programmer(String name){
        this.name = name;
   }
    /**
     * 打开电脑
     */
    synchronized  public  void computer(){
            try {
                System.out.println(this.name + " 接通电源");
                Thread.sleep(500);
                System.out.println(this.name + " 按开机按键");
                Thread.sleep(500);
                System.out.println(this.name + " 系统启动中");
                Thread.sleep(500);
                System.out.println(this.name + " 系统启动成功");
           } catch (InterruptedException e){
                e.printStackTrace();
           }
   }
    /**
     * 编码
     */
    synchronized public void coding(){
            try {
                System.out.println(this.name + " 双击Idea");
                Thread.sleep(500);
                System.out.println(this.name + " Idea启动完毕");
                Thread.sleep(500);
                System.out.println(this.name + " 开开心心的写代码");
           } catch (InterruptedException e){
                e.printStackTrace();
           }
       }
}
/**
* 打开电脑的工作线程
*/
class Working1 extends Thread{
    private  Programmer p;
    public Working1(Programmer p){
        this.p = p;
   }
    @Override
    public void run() {
        this.p.computer();
   }
}
/**
* 编写代码的工作线程
*/
class Working2 extends Thread{
    private  Programmer p;
    public Working2(Programmer p){
        this.p = p;
   }
    @Override
    public void run() {
        this.p.coding();
   }
}
public class TestSyncThread {
    public static void main(String[] args) {
        Programmer p = new Programmer("张三");
        new Working1(p).start();
        new Working2(p).start();
   }
}


使用字符串作为线程对象锁


语法结构:

synchronized(“字符串”){
      //同步代码
 }


/**
* 定义程序员类
*/
class Programmer{
    private String name;
    public Programmer(String name){
        this.name = name;
   }
    /**
     * 打开电脑
  */
    synchronized  public  void computer(){
            try {
              System.out.println(this.name + " 接通电源");
                Thread.sleep(500);
              System.out.println(this.name + " 按开机按键");
                Thread.sleep(500);
              System.out.println(this.name + " 系统启动中");
                Thread.sleep(500);
              System.out.println(this.name + " 系统启动成功");
           } catch (InterruptedExceptione) {
                e.printStackTrace();
           }
   }
    /**
     * 编码
     */
    synchronized public void coding(){
            try {
              System.out.println(this.name + " 双击Idea");
                Thread.sleep(500);System.out.println(this.name + " Idea启动完毕");
                Thread.sleep(500);
              System.out.println(this.name + " 开开心心的写代码");
           } catch (InterruptedExceptione) {
                e.printStackTrace();
           }
       }
    /**
     * 去卫生间
     */
    public void wc(){
        synchronized ("suibian") {
            try {
              System.out.println(this.name + " 打开卫生间门");
                Thread.sleep(500);
              System.out.println(this.name + " 开始排泄");
                Thread.sleep(500);
              System.out.println(this.name + " 冲水");
                Thread.sleep(500);
              System.out.println(this.name + " 离开卫生间");
 } catch (InterruptedExceptione) {
                e.printStackTrace();
           }
       }
   }
}
/**
* 打开电脑的工作线程
*/
class Working1 extends Thread{
    private  Programmer p;
    public Working1(Programmer p){
        this.p = p;
   }
    @Override
    public void run() {
        this.p.computer();
   }
}
/**
* 编写代码的工作线程
*/
class Working2 extends Thread{
    private  Programmer p;
    public Working2(Programmer p){
        this.p = p;
   }
    @Override
    public void run() {
  this.p.coding();
   }
}
/**
* 去卫生间的线程
*/
class WC extends Thread{
    private  Programmer p;
    public WC(Programmer p){
        this.p = p;
   }
    @Override
    public void run() {
        this.p.wc();
   }
}
public class TestSyncThread {
    public static void main(String[] args)
{
        Programmer p = new Programmer("张三");
        Programmer p1 = new Programmer("李四");
        Programmer p2 = new Programmer("王五");
        new WC(p).start();
        new WC(p1).start();
        new WC(p2).start();
   }
}


目录
相关文章
|
6天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
23天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
16 3
|
23天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
16 2
|
23天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
23天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
23天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
33 1
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
43 1
C++ 多线程之初识多线程
|
23天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
24 1
|
2月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6