Java并发编程系列(一)-线程的基本使用

最近在学习java并发编程基础.一切从简,以能理解概念为主.

并发编程肯定绕不过线程.这是最基础的.

那么就从在java中,如何使用线程开始.

继承Thread类

继承Thread类,重写run方法,new出对象,调用start方法.

在新启的线程里运行的就是重写的run方法.

 1 /**
 2  * 集成Thread类 实现run()
 3  */
 4 public class C1 extends Thread {
 5
 6     @Override
 7     public void run() {
 8         try {
 9             Thread.sleep(100);
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12         }
13         System.out.println(Thread.currentThread().getName() + " run()");
14     }
15
16     public static void main(String[] args) {
17         C1 c1 = new C1();
18         c1.start();
19     }
20 }

run方法里先睡100毫秒,然后打印当前线程名称+run()

运行结果:

实现Runnable接口

实现Runnable接口run方法
 1 /**
 2  * 实现Runnable接口run()
 3  */
 4 public class C2 implements Runnable {
 5
 6     @Override
 7     public void run() {
 8         try {
 9             Thread.sleep(100);
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12         }
13         System.out.println(Thread.currentThread().getName() + " run()");
14     }
15
16     public static void main(String[] args) {
17         C2 c2 = new C2();
18         new Thread(c2, "thread0").start();
19     }
20 }

运行结果:

Lambda表达式

Lambda表达式本质上还是实现了Runnable的run方法,但是写起来相当的方便.注:java8或以上版本才支持

java中Lambda表达式的语法:

1.(parameters) -> expression

2.(parameters) ->{ statements; }

3.对象名::方法名 注:方法名后不加小括号

 1 /**
 2  * Lambda表达式 java8或以上版本
 3  */
 4 public class C3 {
 5
 6     public void foo() {
 7         try {
 8             Thread.sleep(100);
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12         System.out.println(Thread.currentThread().getName() + " foo()");
13     }
14
15     public static void main(String[] args) {
16         C3 c3 = new C3();
17         new Thread(() -> c3.foo()).start();
18         new Thread(() -> {
19             c3.foo();
20         }).start();
21         new Thread(c3::foo).start();
22     }
23 }

运行结果:

可以看到三种写法是等价的.执行无参数的方法,用第三种双冒号的方式最简单,有参数的可以用第一种方式,执行多行的代码片段只能用第二种方式.

start() run() join()

首先看一段代码吧

 1 /**
 2  * start() run() join()
 3  */
 4 public class C4 {
 5
 6     public void foo() {
 7         try {
 8             Thread.sleep(100);
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12         System.out.println(Thread.currentThread().getName() + " foo()");
13     }
14
15     public static void main(String[] args) {
16         C4 c4 = new C4();
17         //一个线程不能同时执行多次start() 否侧会抛出IllegalThreadStateException
18         Thread tStart=new Thread(c4::foo);
19         tStart.start();
20         tStart.start();
21         //同时执行多次start()没抛出IllegalThreadStateException 因为不在同一线程内 是new出来的
22         new Thread(c4::foo).start();
23         new Thread(c4::foo).start();
24
25         try {
26             Thread.sleep(1000);
27         } catch (InterruptedException e) {
28             e.printStackTrace();
29         }
30         System.out.println("---分割线---");
31         //run() 调用的就是重写的那个run 所以没有开启线程 是在主线程里执行的
32         new Thread(c4::foo).run();
33
34         System.out.println("---分割线---");
35         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss:SSS");
36         Thread tJoin = new Thread(c4::foo);
37         tJoin.start();
38         System.out.println(simpleDateFormat.format(new Date()));
39         try {
40             //阻塞代码 等待线程执行完
41             tJoin.join();
42         } catch (InterruptedException e) {
43             e.printStackTrace();
44         }
45         System.out.println(simpleDateFormat.format(new Date()));
46     }
47 }
start()

先看main方法里的前5行,在同一时间两次调用同一个线程的start().

运行结果:

抛出了IllegalThreadStateException非法的线程状态异常.也就是说在同一线程里,不能同时多次执行同一段代码.这很好理解了,同时多次执行同一段代码应该用多个线程.

注释掉tStart相关代码,继续运行

运行结果:

第一部分结果说明了同时多次执行同一段代码应该用多个线程.

start方法开启新线程,见源码(删掉源注释):

 1 public synchronized void start() {
 2         //判断线程状态 抛出IllegalThreadStateException
 3         if (threadStatus != 0)
 4             throw new IllegalThreadStateException();
 5
 6         group.add(this);
 7
 8         boolean started = false;
 9         try {
10             //这里开启
11             start0();
12             started = true;
13         } finally {
14             try {
15                 if (!started) {
16                     group.threadStartFailed(this);
17                 }
18             } catch (Throwable ignore) {
19             }
20         }
21     }

可以看到调用start首先会判断线程状态,不是0的话,抛出非法的线程状态异常.

真正开启线程是在start0()这里.

转到定义可以看到start调用了本地方法,也就是真正开启线程是c语言写的.那么什么参数都没有.底层这么知道开启线程调用什么方法呢.

最上面,Thread类有个静态构造函数是类在实例化的时候,调用registerNatives本地方法,将自己注册进去的.

run()

第二部分是调用的run方法,显示的线程名是主线程.就是说调用run方法并不会开启新线程.

看看到底是怎么回事.

导航到源码可以看到,Thread类是实现了Runnable接口的.重写了run方法.

如果用继承Thread类的方式写线程类,要重写run方法.先不管源码重写的run是什么,那么你的重写会覆盖掉源码重写的run.这种情况下,new出自己的线程类,然后调用run,当然和线程没有任何关系,就是在主线程里调用了另一个类的普通的方法.

如果用实现Runnable接口的方式写线程类,那么会new一个这个接口的实例,传到new出的Thread对象里.继续往下传到init方法,最终赋值到target变量上

结合上面源码重写的run,那么它还是调用了你传过来的Runnable实例的run方法.

join()

join就是阻塞代码,等待线程执行完后再开继续始执行当前代码

可以看到第三部分结果,第一次输出时间和第二次输出时间相差了109毫秒(线程内睡了100毫秒).如果不用join阻塞,时间一般相差的会很小,当然具体是多少也不一定,得看机器当时的运行情况.用join相差多少毫秒也不一定,但至少一定不会小于100毫秒.(可自行多次尝试).

原文地址:https://www.cnblogs.com/tsliwei/p/10021802.html

时间: 2024-10-18 14:22:24

Java并发编程系列(一)-线程的基本使用的相关文章

Java并发编程系列-(2) 线程的并发工具类

2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了分而治之的思想:什么是分而治之?规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解. 具体使用中,需要向ForkJoinPool线程池提交一个ForkJoinTask任务.ForkJoinTask任务有两个重要

【java并发编程实战】-----线程基本概念

学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习Java并发编程,共同进步,互相指导. 在学习Java并发之前我们需要先理解一些基本的概念:共享.可变.线程安全性.线程同步.原子性.可见性.有序性. 共享和可变 要编写线程安全的代码,其核心在于对共享的和可变的状态进行访问. "共享"就意味着变量可以被多个线程同时访问.我们知道系统中的资

java并发编程系列一、多线程

1.什么是线程 线程是CPU独立运行和独立调度的基本单位: 2.什么是进程 进程是资源分配的基本单位: 3.线程的状态 新创建   线程被创建,但是没有调用start方法 可运行(RUNNABLE)  运行状态,由cpu决定是不是正在运行 被阻塞(BLOCKING)  阻塞,线程被阻塞于锁 等待/计时等待(WAITING) 等待某些条件成熟 被终止  线程执行完毕 线程的生命周期及五种基本状态: 4.线程的优先级 成员变量priority控制优先级,范围1-10之间,数字越高优先级越高,缺省为5

Java并发编程系列-(8) JMM和底层实现原理

8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信. 在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify(). 线程之间的同步

Java并发编程(01):线程的创建方式,状态周期管理

本文源码:GitHub·点这里 || GitEE·点这里 一.并发编程简介 1.基础概念 程序 与计算机系统操作有关的计算机程序.规程.规则,以及可能有的文件.文档及数据. 进程 进程是计算机中的程序,关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体:在面向线程设计的计算机结构中,进程是线程的容器.程序是指令.数据及其组织形式的描述,进程是程序的实体. 线程 线程是操作系统能够进行运算调度的最小单

Java并发编程(02):线程核心机制,基础概念扩展

本文源码:GitHub·点这里 || GitEE·点这里 一.线程基本机制 1.概念描述 并发编程的特点是:可以将程序划分为多个分离且独立运行的任务,通过线程来驱动这些独立的任务执行,从而提升整体的效率.下面提供一个基础的演示案例. 2.应用案例 场景:假设有一个容器集合,需要拿出容器中的每个元素,进行加工处理,一般情况下直接遍历就好,如果数据偏大,可以根据线程数量对集合切割,每个线程处理一部分数据,这样处理时间就会减少很多. public class ExtendThread01 { publ

Java并发编程-如何终止线程

我们知道使用stop().suspend()等方法在终止与恢复线程有弊端,会造成线程不安全,那么问题来了,应该如何正确终止与恢复线程呢?这里可以使用两种方法: 1.使用interrupt()中断方法. 2.使用volatile boolean变量进行控制. 在使用interrupt方法之前,有必要介绍一下中断以及与interrupt相关的方法.中断可以理解为线程的一个标志位属性,表示一个运行中的线程是否被其他线程进行了中断操作.这里提到了其他线程,所以可以认为中断是线程之间进行通信的一种方式,简

Java并发编程学习:线程安全与锁优化

本文参考<深入理解java虚拟机第二版> 一.什么是线程安全? 这里我借<Java Concurrency In Practice>里面的话:当多个线程访问一个对象,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的. 我的理解:多线程访问一个对象,任何情况下,都能保持正确行为,就是对象就是安全的. 我们可以将Java语言中各种操作共享的数据分为以下5类:不可变.

JAVA - 并发编程 - 执行器和线程池

思考? 1 为什么要使用执行器和线程池? 2 执行器和线程是什么?怎么使用 执行器 线程执行器分离了任务的创建和执行,提高了线程的性能 线程池 避免了频繁地创建和销毁线程,达到线程对象的重用,可以根据项目灵活地控制并发的数量 ExecutorService (java.util.concurrent) 1 Executors.newCachedThreadPool() 可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程 2 Executors.newFixedT