专栏首页IT笔记分享Java多线程学习 (一)

Java多线程学习 (一)

1、进程和线程

1.1 进程

进程是操作系统运行程序的基本单位,是一次程序的执行。简单来说一个进程就是一个运行中的程序。

1.2 线程

线程可以认为是在进程中独立运行的子任务。一个进程会有多个线程。

1.3 进程和线程区别

进程和线程最大区别就是,各个进程是独立的,而线程却不一定,同一进程中的线程可能是相互影响的。进程属于操作心痛范围的,同一时间会运行多个程序,每个进程上的又会有多个线程在执行同个或不同的任务。

1.4 多线程

多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。

2、使用多线程

在Java的JDK开发包中,实现多线程主要有两种,一种是继承Thread类,另一种是实现Runnable接口。其实还有很多事项多线程方法,例如:使用ExecutorService、Callable、Future实现由返回结果的多线程,这是通过线程池创建的任务,可以参考《一篇搞懂线程池》。现在我们只来具体来讲一下前两种方法的使用。

2.1 继承Thread类

在创建多线程前,我们先来看一下Thread的类结构图:

可以看出Thread实现了Runnable接口,其实Runnable和Thread的区别就在于继承Thread创建多线程不支持多继承,为了多继承完全可以使用Runnable替换,两者创建的线程本质上没有区别。@FunctionalInterface是Java8的函数式接口,方便lambda表达式使用。

我们创建一个Mythread.java继承Thread,重写run方法:

package main.java.com.xiaosen.Mythread;

/**
 * @author xiaosen
 * @date 2019/2/24 10:36
 * @description
 */
public class MyThread extends Thread{

    @Override
    public void run() {
        super.run();
        System.out.println("this is MyThread");
    }


}

运行类的代码如下:

import main.java.com.xiaosen.Mythread.MyThread;

public class Main {

    public static void main(String[] args) {
        // 继承Thread
        MyThread myThread = new MyThread();
        myThread.start();
        // lambda表达式的使用
        new Thread(() -> System.out.println("this is lambda thread")).start();
        System.out.println("Hello World!");
    }
}

运行结果如下:

this is MyThread
Hello World!
this is lambda thread

这里一低昂要注意myThread调用的是start()方法,如果调用mythread.run()那么就是单纯的方法调用,而不是创建线程去执行。关于lambda表达式只给出使用方法,暂不做介绍。

从运行结果可以看见"Hello World!"的输出在两个线程中间,说明使用多线程和代码的顺序无关。CPU会以随机的时间来调用线程中的run方法。

2.3 实现Runnable接口

创建MyRunnable实现Runnable接口:

package main.java.com.xiaosen.myrunnable;

/**
 * @author xiaosen
 * @date 2019/2/24 11:20
 * @description
 */
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("run MyRunnable");
    }
}

main方法使用:

// 实现Runnable
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        System.out.println("Hello World!");

输出内容:

Hello World!
run MyRunnable

使用Runnable方法需要用到Thread的构造方法。

3、线程安全

自定义的实例变量有共享和不共享之分,在多线程交互的时候很重要。

3.1 不共享数据

创建一个类继承Thread:

public class NotShareThread extends  Thread{
    private int count = 5;
    public NotShareThread(String name){
        super();
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        while (count>0){
            count--;
            System.out.println("线程" + this.currentThread().getName() + ":count=" + count);
        }

    }
}

 public static void main(String[] args){
        NotShareThread notShareThread1 = new NotShareThread("A");
        NotShareThread notShareThread2 = new NotShareThread("B");
        NotShareThread notShareThread3 = new NotShareThread("C");
        notShareThread1.start();
        notShareThread2.start();
        notShareThread3.start();

    }

执行结果:

线程A:count=4
线程B:count=4
线程C:count=4
线程B:count=3
线程A:count=3
线程A:count=2
线程A:count=1
线程B:count=2
线程C:count=3
线程B:count=1
线程A:count=0
线程B:count=0
线程C:count=2
线程C:count=1
线程C:count=0

从输出结果可以看到每一个线程都有各自的count变量,彼此变量不共享。

3.2 共享数据

共享数据就是多个线程同时访问同一个变量,比如买火车票,多个线程同时操作剩余票数。

3.2.1 线程不安全

public class ShareThread extends Thread {
    private int count = 5;

    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("线程" + this.currentThread().getName() + ":count=" + count);
    }

    public static void main(String[] args){
        ShareThread shareThread = new ShareThread();
        Thread a = new Thread(shareThread, "A");
        Thread b = new Thread(shareThread, "B");
        Thread c = new Thread(shareThread, "C");
        Thread d = new Thread(shareThread, "D");
        Thread e = new Thread(shareThread, "E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

输出:

线程B:count=2
线程E:count=0
线程D:count=1
线程C:count=2
线程A:count=2

可以发现A,B,C三个线程的值都是2,产生非线程安全情况,我们应该避免这种情况发生。这是因为在执行i--操作分以下几步:

  1. 获取原有i值;
  2. 计算i-1;
  3. 堆i赋值。

在这三步中,如果有多线程同时访问,就会出现线程不安全情况。

3.2.2 线程安全

我们来看看线程安全的代码:

@Override
    public void run() {
        super.run();
        synchronized (ShareThread.class){
            count--;
            System.out.println("线程" + this.currentThread().getName() + ":count=" + count);
        }

    }

我们加上synchronized代码块加锁,那么只有拿到所锁的线程才可以执行代码块的内容,没拿到则不断尝试获取锁,知道拿到为止。

代码Github


本文分享自微信公众号 - IT笔记分享(xiaosen_javashare),作者:xiaosen L

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java多线程学习(二)——Thread类的方法使用

    停止线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前操作。有以下三种方法终止正在运行中的线程:

    小森啦啦啦
  • Java多线程学习(三)——synchronized(上)

    在前两节的《Java多线程学习(一)——多线程基础》和《Java多线程学习(二)——Thread类的方法介绍》中我们接触了线程安全和非线程安全的概念,这节就来学...

    小森啦啦啦
  • 一篇搞懂线程池

    在上一篇文章《spring boot使用@Async异步任务》中我们了解了使用@Async的异步任务使用,在这篇文章中我们将学习使用线程池来创建异步任务的线程。

    小森啦啦啦
  • 一文看透Java高并发:Synchronized锁的性质、原理及其缺陷

    同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。

    Java_老男孩
  • 深入volatile关键字

    在Java多线程中,有一个特殊的关键字volatile,这个通常成为一个“轻量级锁”,下面我们就来深入的了解这个关键的作用和原理。

    付威
  • ThreadPoolExcutor 线程池 异常处理 (上篇)

    最近看到crossoverJie的一篇文章:一个线程罢工的诡异事件 首先感谢原作者的分享,自己获益匪浅。然后是回想到自己的一次面试经历,面试官提问了线程池中的...

    一枝花算不算浪漫
  • 发生死锁怎么办

    我们使用锁来保证线程安全,但是使用不当与滥用可能就会引起死锁。并发程序一旦死锁,一般没有特别好的办法,很多时候只能重启。所以我们一定要比避免死锁。

    MageByte
  • 1037 在霍格沃茨找零钱 (20 分)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    韩旭051
  • 竟态条件 racing condition

    多个线程读时,线程是安全的。 当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。 我的理解,竞态条件就是一种情况。

    潇洒
  • 竞态条件 racing condition

    多个线程读时,线程是安全的。 当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。 我的理解,竞态条件就是一种情况。

    潇洒

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动