Java多线程总结(1)

简介: 本文不打算介绍过多多线程的基本知识,旨在总结一下使用多线程中需要注意的东西。大家都知道要写多线程代码可以实现Runnable接口或者继承Thread类。

本文不打算介绍过多多线程的基本知识,旨在总结一下使用多线程中需要注意的东西。

大家都知道要写多线程代码可以实现Runnable接口或者继承Thread类。关于二者的区别,大家都知道java是单继承机制的,继承了Thread可能会让你无法再继承其他父类。可能没有考虑内存相关的问题,导致多线程失效。

直接用代码来讨论,以一个卖票系统为例,总票数15张,模拟3个窗口同时卖票。

实现Runnable

/**
 * 不加入同步
 * @author
 *
 */
public class Ticket1 implements Runnable {
    
    // 总票数
    private int total = 15;
    
    public void run() {
        while(true) {
            if(total > 0) {
                try {
                    Thread.sleep(200);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " sales ticket_" + total--);
            } else {
                break;
            }
        }
    }

    public static void main(String[] args) {
        Ticket1 t = new Ticket1();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

这段代码的问题很明显,没有加入同步控制,输出大致如下:

t3 sales ticket_15
t2 sales ticket_14
t1 sales ticket_13
t2 sales ticket_12
t3 sales ticket_11
t1 sales ticket_10
t3 sales ticket_9
t1 sales ticket_7
t2 sales ticket_8
t3 sales ticket_6
t1 sales ticket_5
t2 sales ticket_4
t2 sales ticket_3
t3 sales ticket_2
t1 sales ticket_1
t2 sales ticket_0
t3 sales ticket_-1

加入同步控制保证买票正常:

package com.cmsz.upay.thread;

/**
 * 加入同步
 * @author 
 *
 */
public class Ticket2 implements Runnable {
    
    // 总票数
    private int total = 15;
    
    public void run() {
        while(true) {
            synchronized (this) {
                if(total > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " sales ticket_" + total--);
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        Ticket2 t = new Ticket2();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

这段代码可以保证卖票正常:

t1 sales ticket_15
t1 sales ticket_14
t1 sales ticket_13
t1 sales ticket_12
t1 sales ticket_11
t1 sales ticket_10
t1 sales ticket_9
t1 sales ticket_8
t1 sales ticket_7
t1 sales ticket_6
t1 sales ticket_5
t3 sales ticket_4
t3 sales ticket_3
t3 sales ticket_2
t3 sales ticket_1

继承Thread

先上一段有问题的代码,应该是比较多初学者不会考虑到的:

/**
 * 继承thread
 * @author 
 *
 */
public class Ticket3 extends Thread {
    
    // 总票数
    private int total = 15;
    
    public void run() {
        while(true) {
            synchronized (this) {
                if(total > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " sales ticket_" + total--);
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        Ticket3 t1 = new Ticket3();
        Ticket3 t2 = new Ticket3();
        Ticket3 t3 = new Ticket3();
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

运行结果大致如下:

t2 sales ticket_15
t1 sales ticket_15
t3 sales ticket_15
t3 sales ticket_14
t2 sales ticket_14
t1 sales ticket_14
t2 sales ticket_13
t3 sales ticket_13
t1 sales ticket_13
t3 sales ticket_12
t2 sales ticket_12
t1 sales ticket_12
t2 sales ticket_11
t3 sales ticket_11
t1 sales ticket_11
t2 sales ticket_10
t1 sales ticket_10
t3 sales ticket_10
t1 sales ticket_9
t2 sales ticket_9
t3 sales ticket_9
t1 sales ticket_8
t2 sales ticket_8
t3 sales ticket_8
t2 sales ticket_7
t1 sales ticket_7
t3 sales ticket_7
t2 sales ticket_6
t1 sales ticket_6
t3 sales ticket_6
t2 sales ticket_5
t1 sales ticket_5
t3 sales ticket_5
t2 sales ticket_4
t3 sales ticket_4
t1 sales ticket_4
t2 sales ticket_3
t3 sales ticket_3
t1 sales ticket_3
t2 sales ticket_2
t1 sales ticket_2
t3 sales ticket_2
t2 sales ticket_1
t3 sales ticket_1
t1 sales ticket_1

很明显每个线程都卖了15张票,跟我们预期的不符合,我们在学习面向对象的时候知道了两个概念:类变量和实例变量,不展开细说二者的区别,实例变量就是每new一个实例出来之后,变量都会有一个自己的内存区域,不受他人影响;而类变量就是所有通过该类实例化的对象共享一个变量。
Ticket3的问题之处就是将票总数total声明为实例变量了,这样我们每新建一个实例(线程)total都会有一个自己的内存区域,所以每次卖的都是自己那15张票。

OK,发现问题解决问题,将票数声明为类变量问题应该就能解决了,试一下:

package com.cmsz.upay.thread;

/**
 * 继承thread,共享变量
 * @author 
 *
 */
public class Ticket4 extends Thread {
    
    // 总票数
    private static int total = 15;
    
    public void run() {
        while(true) {
            synchronized (this) {
                if(total > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " sales ticket_" + total--);
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        Ticket4 t1 = new Ticket4();
        Ticket4 t2 = new Ticket4();
        Ticket4 t3 = new Ticket4();
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

运行结果如下:

t2 sales ticket_15
t1 sales ticket_14
t3 sales ticket_13
t1 sales ticket_12
t2 sales ticket_11
t3 sales ticket_10
t2 sales ticket_9
t1 sales ticket_8
t3 sales ticket_7
t2 sales ticket_5
t1 sales ticket_6
t3 sales ticket_4
t2 sales ticket_3
t1 sales ticket_2
t3 sales ticket_1
t1 sales ticket_0
t2 sales ticket_-1

坑爹的问题出现了,虽然是大家共同卖15张票,但是卖出了0和-1号的票,也就是我们买了17张票,现实是这样的话估计抢座要打起来了……

分析一下原因,共享变量没问题,那就是同步出现问题了,同步有什么问题呢?
问题出现在synchronized(this),获取锁的对象是this,很明显3个Thread的this对象是不同的,说白了就是这个加锁根本没有锁住共享变量。

知道了问题的原因,我们只要保证synchronized(syncObject)中的syncObject唯一即可,声明一个类变量即可。

/**
 * 继承thread,共享变量,锁相同对象
 * @author 
 *
 */
public class Ticket5 extends Thread {
    
    private static Object syncObject = new Object();
    
    // 总票数
    private static int total = 15;
    
    public void run() {
        while(true) {
            synchronized (syncObject) {
                if(total > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " sales ticket_" + total--);
                } else {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        Ticket5 t1 = new Ticket5();
        Ticket5 t2 = new Ticket5();
        Ticket5 t3 = new Ticket5();
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

运行即可保证正常。

本文总结主要针对编写多线程代码过程中共享变量的问题和锁机制的一些细节,初学者容易犯错或者欠考虑,通过几个Demo的总结,可以把一些零散的知识点汇总在一起,保证看问题的全面性。

目录
相关文章
|
8天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
79 38
|
5天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
7天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
1天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
8 1
|
5天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
12 3
|
7天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。
|
5天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
8 2
|
6天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。
|
8天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
36 4
|
8天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
30 3