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

目录
相关文章
|
3天前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
1天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
11 5
|
1天前
|
消息中间件 安全 Java
理解Java中的多线程编程
【5月更文挑战第18天】本文介绍了Java中的多线程编程,包括线程和多线程的基本概念。Java通过继承Thread类或实现Runnable接口来创建线程,此外还支持使用线程池(如ExecutorService和Executors)进行更高效的管理。多线程编程需要注意线程安全、性能优化和线程间通信,以避免数据竞争、死锁等问题,并确保程序高效运行。
|
1天前
|
存储 Java
【Java】实现一个简单的线程池
,如果被消耗完了就说明在规定时间内获取不到任务,直接return结束线程。
9 0
|
1天前
|
安全 Java 容器
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第18天】随着多核处理器的普及,并发编程变得越来越重要。Java提供了丰富的并发编程工具,如synchronized关键字、显式锁Lock、原子类、并发容器等。本文将深入探讨Java并发编程的核心概念,包括线程安全、死锁、资源竞争等,并分享一些性能优化的技巧。
|
1天前
|
安全 Java 开发者
Java中的多线程编程:理解与实践
【5月更文挑战第18天】在现代软件开发中,多线程编程是提高程序性能和响应速度的重要手段。Java作为一种广泛使用的编程语言,其内置的多线程支持使得开发者能够轻松地实现并行处理。本文将深入探讨Java多线程的基本概念、实现方式以及常见的并发问题,并通过实例代码演示如何高效地使用多线程技术。通过阅读本文,读者将对Java多线程编程有一个全面的认识,并能够在实际开发中灵活运用。
|
1天前
|
算法 Java 程序员
Java中的线程同步与并发控制
【5月更文挑战第18天】随着计算机技术的不断发展,多核处理器的普及使得多线程编程成为提高程序性能的关键。在Java中,线程是实现并发的一种重要手段。然而,线程的并发执行可能导致数据不一致、死锁等问题。本文将深入探讨Java中线程同步的方法和技巧,以及如何避免常见的并发问题,从而提高程序的性能和稳定性。
|
2天前
|
存储 安全 Java
Java多线程基础知识总结,36岁老码农现身说法
Java多线程基础知识总结,36岁老码农现身说法
|
2天前
|
Java 测试技术
Java多线程的一些基本例子
【5月更文挑战第17天】Java多线程允许并发执行任务。示例1展示创建并启动两个`MyThread`对象,各自独立打印"Hello World"。示例2的`CounterExample`中,两个线程(IncrementThread和DecrementThread)同步地增加和减少共享计数器,确保最终计数为零。这些例子展示了Java线程的基本用法,包括线程同步,还有如Executor框架和线程池等更复杂的用例。
10 0
|
3天前
|
Java
阅读《代码整洁之道》总结(1),java多线程面试
阅读《代码整洁之道》总结(1),java多线程面试