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的总结,可以把一些零散的知识点汇总在一起,保证看问题的全面性。

目录
相关文章
|
7天前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
28 7
|
6天前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
6天前
|
存储 监控 安全
一天十道Java面试题----第三天(对线程安全的理解------>线程池中阻塞队列的作用)
这篇文章是Java面试第三天的笔记,讨论了线程安全、Thread与Runnable的区别、守护线程、ThreadLocal原理及内存泄漏问题、并发并行串行的概念、并发三大特性、线程池的使用原因和解释、线程池处理流程,以及线程池中阻塞队列的作用和设计考虑。
|
3天前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
6天前
|
安全 Java
Java模拟生产者-消费者问题。生产者不断的往仓库中存放产品,消费者从仓库中消费产品。其中生产者和消费者都可以有若干个。在这里,生产者是一个线程,消费者是一个线程。仓库容量有限,只有库满时生产者不能存
该博客文章通过Java代码示例演示了生产者-消费者问题,其中生产者在仓库未满时生产产品,消费者在仓库有产品时消费产品,通过同步机制确保多线程环境下的线程安全和有效通信。
|
5天前
|
缓存 前端开发 JavaScript
一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
【8月更文挑战第11天】一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
13 0
一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
|
6天前
|
缓存 监控 Java
Java性能优化:从单线程执行到线程池管理的进阶实践
在Java开发中,随着应用规模的不断扩大和用户量的持续增长,性能优化成为了一个不可忽视的重要课题。特别是在处理大量并发请求或执行耗时任务时,单线程执行模式往往难以满足需求,这时线程池的概念便应运而生。本文将从应用场景举例出发,探讨Java线程池的使用,并通过具体案例和核心代码展示其在实际问题解决中的强大作用。
22 1
|
8天前
|
缓存 Java 数据处理
Java中的并发编程:解锁多线程的力量
在Java的世界里,并发编程是提升应用性能和响应能力的关键。本文将深入探讨Java的多线程机制,从基础概念到高级特性,逐步揭示如何有效利用并发来处理复杂任务。我们将一起探索线程的创建、同步、通信以及Java并发库中的工具类,带你领略并发编程的魅力。
|
3天前
|
算法 安全 Java
深入解析Java多线程:源码级别的分析与实践
深入解析Java多线程:源码级别的分析与实践
|
5天前
|
Java 程序员 调度
深入浅出Java多线程编程
Java作为一门成熟的编程语言,在多线程编程方面提供了丰富的支持。本文将通过浅显易懂的语言和实例,带领读者了解Java多线程的基本概念、创建方法以及常见同步工具的使用,旨在帮助初学者快速入门并掌握Java多线程编程的基础知识。
4 0