Java多线程高并发学习笔记(一)——Thread&Runnable

简介: 进程与线程 首先来看百度百科关于进程的介绍: 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

 进程与线程

首先来看百度百科关于进程的介绍:

进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。 

直观一点:

 

 

windows的任务管理里面,我们看到的eclipse和360等等,都是进程。(想深入了解的可以看看计算机操作系统)

什么是线程?

线程是在一个进程独立运行的子任务。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

打个比如说,我们都用的QQ软件,它是一个进程,我们可以和人一边聊天一边传输文件,这就是多线程。

为什么要使用多线程?

1.多线程可以充分的利用系统的cpu,达到更快的处理效果

2.提高系统的运行效率

想入更深入了解的可以重新学习计算机操作系统,这里就不多介绍了,接下来我们怎么用!(毕竟这才是关键)

 

Thread and Runnable

Java中实现线程有两个方式,一个是继承Thread,另一个是实现Runnable接口,首先来看继承Thread的第一个实例:

package com.chapter1;

public class FirstThread {

    public static void main(String[] args) {
        InnerThread thread = new InnerThread();
        // 线程启动
        thread.start();
    }

}

class InnerThread extends Thread {

    // Override run方法,写入自己想要实现的业务
    @Override
    public void run() {
        super.run();
        System.out.println("Hello World!");
    }
}

 继承run方法的时候,我们通常会重写run方法,来实现自己的业务,接下来看,如何使用Runnable实现线程:

package com.chapter1;

public class FirstRunnable {

    public static void main(String[] args) {
        MyRunnable myRunnable;
        myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
    }

}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("Hello World!");
    }
}

Runnable是一个接口,接口意味着更加灵活一些,也是推荐使用实现Runable接口来写线程的。

 现在我们看到的都是一个单线程的例子,接下来写一个多线程的:

 

package com.chapter1;
/**
 * 多个线程实例
 * @author tangj
 *
 */
public class ManyThread {

    public static void main(String args[]) {
        Runnable runnable = new MyRunnable2();
        new Thread(runnable, "a").start();
        new Thread(runnable, "b").start();
        new Thread(runnable, "c").start();
        new Thread(runnable, "d").start();
    }
}

class MyRunnable2 implements Runnable {

    int count = 0;
    @Override
    public void run() {
        autoIncrement();
        System.out.println(Thread.currentThread().getName()+"计算了"+"count:" + count);
    }
    // 执行自增操作
    private void autoIncrement(){
        count++;
    }
}

我们来看下运行结果:

 

再运行一次

可以看到,多线程的执行是无序的,而且这个结果有点奇怪,难道不是从1增加到4吗,怎么会出现重复的?

在JVM中,执行自增操作的分为三个步骤:

1.取得现有值count

2.执行count+1操作

3.将count+1赋值给count.

所以就会遇到这样的情况,一个线程在取得count值的时候,count操作正处于第二个步骤,上一个线程执行的还未进行赋值操作,这就涉及到线程安全问题。

 

下面给出上一个例子的解决方案                                                               

package com.chapter1;

/**
 * 多个线程实例
 * 
 * @author tangj
 *
 */
public class ManyThread {

    public static void main(String args[]) {
        Runnable runnable = new MyRunnable2();
        new Thread(runnable, "a").start();
        new Thread(runnable, "b").start();
        new Thread(runnable, "c").start();
        new Thread(runnable, "d").start();
    }
}

class MyRunnable2 implements Runnable {

    int count = 0;

    // 注意在这里增加了关键字 synchronized
    @Override
    synchronized public void run() {
        autoIncrement();
        System.out.println(Thread.currentThread().getName() + "计算了" + "count:" + count);
    }

    // 执行自增操作
    private void autoIncrement() {
        count++;
    }
}

来看输出结果(多执行几次):

我们可以看到:不管哪个线程先执行,最后的打印顺序肯定是递增的顺序。

 

 接下来我们看另外一中实现方式:使用重入锁(ReentrantLock)

package com.chapter1;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ManyThread2 {

    public static void main(String[] args) {
        Runnable runnable = new MyRunnable3();
        new Thread(runnable, "a").start();
        new Thread(runnable, "b").start();
        new Thread(runnable, "c").start();
        new Thread(runnable, "d").start();
    }
}

class MyRunnable3 implements Runnable{
    
    int count = 0;
    // 使用重入锁ReentrantLock
    Lock lock = new ReentrantLock();
    
    @Override
    public void run() {
        // 锁住
        lock.lock();
        count++;
        System.out.println(Thread.currentThread().getName() + "计算了" + "count:" + count);
        // 解锁
        lock.unlock();
    }
    
}

得到的结果也是整齐的递增顺序,说明它也是线程安全的。

 

总结

所以以后开发多线程的业务的时候,首先应该考虑的问题应该是这样的一个流程:

1.能否用单线程解决?(单线程本身就是线程安全的)

2.我开发的多线程业务是否线程安全?

线程安全是保证程序正常运行的关键,所以应该把线程安全作为开发多线程对于考虑的首要问题

 

最后说两句:

本文所以代码都更新到我的github中,大家可以去clone或者Fork,我会持续更新的。

点击这里进入我的Github
喜欢的朋友可以点击下方的推荐,或者写个评论我们共同探讨Java高并发!!!

 

 

 

 

 

个人博客网站 http://www.janti.cn
相关文章
|
1月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
44 7
|
13天前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
15天前
|
消息中间件 安全 大数据
Kafka多线程Consumer是实现高并发数据处理的有效手段之一
【9月更文挑战第2天】Kafka多线程Consumer是实现高并发数据处理的有效手段之一
71 4
【多线程面试题 二】、 说说Thread类的常用方法
Thread类的常用方法包括构造方法(如Thread()、Thread(Runnable target)等)、静态方法(如currentThread()、sleep(long millis)、yield()等)和实例方法(如getId()、getName()、interrupt()、join()等),用于线程的创建、控制和管理。
|
1月前
|
SQL 机器学习/深度学习 算法
【python】python指南(一):线程Thread
【python】python指南(一):线程Thread
36 0
|
2月前
|
Java C# Python
线程等待(Thread Sleep)
线程等待(Thread Sleep)
|
2月前
|
算法 Java 调度
高并发架构设计三大利器:缓存、限流和降级问题之使用Java代码实现令牌桶算法问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之使用Java代码实现令牌桶算法问题如何解决
|
2月前
|
监控 网络协议 Java
Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
44 0
|
2月前
|
设计模式 安全 NoSQL
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
43 0
|
4月前
|
消息中间件 Java Linux
2024年最全BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux(1),2024年最新意外的惊喜
2024年最全BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux(1),2024年最新意外的惊喜