Java多线程是Java程序员必须掌握的基本的知识点,这块知识点比较复杂,知识点也比较多,今天我们一一来聊下Java多线程,系统的整理下这部分内容。

一、Java中线程创建的三种方式:

1.通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中

class MyThread extends Thread{

public void run(){

//do run something

}

}

public class ThreadDemo {

public static void main(String[] args) {

MyThread mt1= new MyThread();

MyThread mt2= new MyThread();

MyThread mt3= new MyThread();

mt1.start();

mt2.start();

mt3.start();

}

}

2.通过实现Runnable接口,实例化Thread类

//构造一个实现了Runnable接口的类

class MyThread1 implements Runnable{

public void run(){

//do run something

}

}

public class RunnableDemo {

public static void main(String[] args) {

//创建一个类对象

MyThread1 mt = new MyThread1();

//由Runnable创建Thread对象

Thread t1 = new Thread(mt);

Thread t2 = new Thread(mt);

Thread t3 = new Thread(mt);

//启动线程

t1.start();

t2.start();

t3.start();

}

}

3.实现Callable接口,创建FutureTask包装器,实例化Thread类

FutureTask实现接口类图:

public interface Future {

//取消任务的运行

boolean cancel(boolean mayInterruptIfRunning);

//如果任务在完成前取消了返回true

boolean isCancelled();

//任务结束(正常结束、中途取消、发生异常),返回true

boolean isDone();

//返回最终计算完成的结果

V get() throws InterruptedException, ExecutionException;

//返回在指定时间内计算的结果,如果超过指定时间没有结果则抛出TimeoutException异常

V get(long timeout, TimeUnit unit)

throws InterruptedException, ExecutionException, TimeoutException;

}

public interface Callable{

V call() throws Exception;

}

class MyThread2 implements Callable{

public Integer call(){

//do call something

}

}

public class CallableDemo{

public static void main(String[] args){

MyThread2 mt = new MyThread2();

FutureTask task = new FutureTask<>(mt);

Thread t = new Thread(task);

t.start();

Integer result = task.get();

}

}

注意:

1、不要直接调用Thread类或Runnable接口的run方法,直接调用run方法单纯执行run方法体中的内容,而不会启动新线程,应该调用Thread的start方法,这个方法将创建一个执行run方法的新线程。

2、尽量不要使用第一种方式来创建线程,因为有多个任务,这种方式需要为每个任务创建一个独立的线程(new MyThread()),这个代价太大。

二、未捕获异常处理器

我们知道在run方法中无法抛出任何不可查的异常,但一旦run方法中出现了这类异常则会直接导致线程终止,在这种情况下,线程就GG了。通过分析,我们知道在线程死亡之前,异常会被传递到一个用于未捕获异常的处理器中,所以为了防止这种情况出现,我们可以为线程安装一个未捕获异常处理器。

未捕获异常处理器必须实现Thread.UncaughtExceptionHanlder接口的类(这个接口类在Thread),这个类只有一个方法:

@FunctionalInterface

public interface UncaughtExceptionHandler {

void uncaughtException(Thread t, Throwable e);

}

通过Thread的静态方法setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)为线程安装一个默认的处理器。

当一个线程因为未捕获异常而终止时,通过uncaughtException方法的System.err.print打印异常信息。

public void uncaughtException(Thread t, Throwable e) {

if (parent != null) {

parent.uncaughtException(t, e);

} else {

Thread.UncaughtExceptionHandler ueh =

Thread.getDefaultUncaughtExceptionHandler();

if (ueh != null) {

ueh.uncaughtException(t, e);

} else if (!(e instanceof ThreadDeath)) {

System.err.print("Exception in thread \""

+ t.getName() + "\" ");

e.printStackTrace(System.err);

}

}

}

三、线程状态

线程的五个基本状态:

新建(New)

可运行状态(Runnable)

运行时状态(Running)

阻塞状态(Blocked)

死亡状态(Dead)

线程调用start()方法开始后,就进入到可运行状态,随着CPU的资源调度在运行和可运行之间切换;遇到阻塞则进入阻塞状态。这五种状态的相互之间转换图如下图所示:

线程被阻塞可能是由于下面五方面的原因:(《Thinking in Java》)

调用sleep(毫秒数),使线程进入睡眠状态。在规定时间内,这个线程是不会运行的。

用suspend()暂停了线程的执行。除非收到resume()消息,否则不会返回“可运行”状态。这两个方法已经过时。

用wait()暂停了线程的执行。除非线程收到notify()或notifyAll()消息,否则不会变成“可运行”状态。

线程正在等候一些IO操作完成。

线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。

如果线程中有同步方法,那么线程状态图如下图所示:

当互斥资源被一个线程访问时,互斥资源就上锁了,这时候其他线程访问该互斥资源就会进入了一个锁池(Lock pool);当锁被释放,其他线程获得了锁,就变为可运行状态。

如果线程调用了wait()等方法,那么线程状态图如下图所示:

我们都知道线程调用了wait()(这个方法是Object的方法)方法之后,线程会释放掉锁,这个时候线程进入等待池(Wait pool);等线程收到通知之后等待获取锁,获取锁之后才可以运行。

四、线程同步

在Java中线程同步分五种方式:

synchronized

ReentrantLock与Condition

volatile

ThreadLocal

BlockingQueue

4.1 synchronized、wait与notify

当一个线程访问用synchronized关键字修饰的代码块,如果这个代码块被该线程第一个访问,则这个线程会获取到该Java对象的内部锁,其他线程访问的时候则会因为获取不到内部锁而阻塞。synchronized可以修饰类方法(static修饰的方法)、实例方法和类(Object.class),但是不能修饰抽象类的抽象方法和接口中的接口方法。

线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。

我们知道wait()和notify()方法只能在加锁的代码块中使用,因为调用wait()方法时会释放所持有的对象的lock,同时进入等待状态,notifyAll()方法会唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

4.2 ReentrantLock与Condition

ReentrantLock是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。其lock和unlock必须成对出现,否则可能会出现死锁,通常在finally代码释放锁。ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用。

//fair为true时创建公平锁

public ReentrantLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();

}

Condition代表条件对象,用于线程之间的通信,当一个线程A需要另一个线程B满足一定条件才可以继续操作时,A线程可以调用Condition的await()来阻塞当前线程,且放弃锁,等到B线程执行了某些操作并满足了一些条件后signalAll()唤醒阻塞的线程,当A线程重新强占了锁资源后再变成可运行状态。Condition条件对象对于一个对象来说可以有多个,但Object的wait()和notify()对于一个对象来说只有一个条件对象。

4.3 volatile

对于volatile修饰的变量,jvm虚拟机保证从主内存加载到线程工作内存的值是最新的。volatile可保证变量的可见性,但无法保证原子性。

4.4 ThreadLocal

多线程同步无非是让原本多个线程对某个操作并行变成串行,我们必须小心地对共享资源进行同步,同步不仅会带来一定的效能延迟,而且在处理同步的时候,又要注意对象的锁定与释放,稍有不慎就有可能产生死锁。

既然这么麻烦,ThreadLocal不对共享资源加锁,而是为每个线程创造一个资源的复本。将每一个线程存取数据的行为加以隔离,实现的方法就是给予每个线程一个特定空间来保管该线程所独享的资源。

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。在 ThreadLocal 类中有一个 ThreadLocalMap ,用于存储每一个线程的变量的副本。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。

锁是一种以时间换空间的机制,而ThreadLocal正好是以空间换时间的。

4.5 BlockingQueue

阻塞队列在《浅谈Java集合Collection》有提到过:多线程操作共同的队列时不需要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距。

参考文献:

《Java核心技术》

java 多线程同步_浅谈Java多线程(状态、同步等)相关推荐

  1. java对象头_浅谈java对象结构 对象头 Markword

    概述 对象实例由对象头.实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ----- ...

  2. java bitset用途_浅谈Java BitSet使用场景和代码示例

    搜索热词 @H_502_0@一.什么是BitSet? @H_502_0@ 注:以下内容来自JDK API: @H_502_0@ BitSet类实现了一个按需增长的位向量.位Set的每一个组件都有一个b ...

  3. java手动回收_浅谈java是如何做资源回收补救的

    学习java的过程,我们经常谈论一个对象的回收,尤其是资源类型,如果没有显示的关闭,对象就被回收了,说明出现了资源泄漏.java本身为了防止这种情况,做了一些担保的方式,确保可以让未关闭的资源合理回收 ...

  4. java同名函数_浅谈Java 继承接口同名函数问题

    在Java中如果一个类同时继承接口A与B,并且这两个接口中具有同名方法,会怎么样? 动手做实验: interface A{ void fun(); } interface B{ void fun(); ...

  5. java list翻转_浅谈Java数据结构中的常见问题

    1.常用数据结构 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素间的关系组成.常用的数据有:数组.栈.队列.链表.树.图.堆.散列表. 1)数组:在内存中连续存储多个元素的 ...

  6. java 变量共享_浅谈Java共享变量

    Java并发一直都是开发中比较难也比较有挑战性的技术,对于很多新手来说是很容易掉进这个并发陷阱的,其中尤以共享变量最具代表性,其实关于讲这个知识点网上也不少,但大象想讲讲自己对这个概念的理解.共享变量 ...

  7. java并发计数器_浅谈java并发之计数器CountDownLatch

    CountDownLatch简介 CountDownLatch顾名思义,count + down + latch = 计数 + 减 + 门闩(这么拆分也是便于记忆=_=) 可以理解这个东西就是个计数器 ...

  8. java反射 用处_浅谈Java反射

    一.何为反射 反射就是对于任何一个类都能知道这个类的的所有属性和方法,并且对于任何一个对象都能调用他的属性和方法,而且能修改其属性. 二.反射的作用 就我的理解来看,通常我们在写代码的时会非常强调代码 ...

  9. 00005在java结果输出_浅谈Java反序列化漏洞原理(案例未完善后续补充)

    摘要: 0005,这个16进制流基本上也意味者java反序列化的开始:(2)HTTP:必有rO0AB,其实这就是aced0005的base64编码的结果:以上意味着存在Java反序列化,可尝试构造pa ...

最新文章

  1. sql查询父节点所有子节点id_5招搞定SQL棘手问题,同事看到直呼“内行”
  2. 作为一名程序员为什么要用vim作为自己的IDE编辑器
  3. python开发需要掌握哪些知识-Python的8个基础知识点,新手必须背下来!
  4. 【MarkDown】:MarkDown编辑器
  5. LaTeX文档插入图片的几种常用方法
  6. PTA 7-3 地铁一日游 (30 分)
  7. linkedin爬虫_您应该在LinkedIn上关注的8个人
  8. VM Ware 虚拟机centos 时间与本地时间不一致
  9. 基于jQuery鼠标点击弹出登陆框效果
  10. Java-字符与字符串的转化
  11. python做系统查人的往来的信息_L01-04:python查询员工信息表练习
  12. node ajax配置文件,如何存储Node.js部署设置/配置文件?
  13. pygame模块_pygame模块方法和事件
  14. Dijkstra与Floyd算法
  15. 微软官方地址下载sql 2000简体中文企业版(含SP3 SP4 下载地址)
  16. 龙讯3号 龙芯电脑即将店面销售
  17. 学日语就是一种煎熬!
  18. NULL, '\0',0 '0'的区别
  19. what is denoise
  20. 计算机视觉论文速递(七)FAN:提升ViT和CNN的鲁棒性和准确性

热门文章

  1. shell的执行流控制
  2. 1746: 多项式系数(杨辉三角的应用)
  3. mysql数据漂移_第28问:SIP 漂移时,会影响正在使用的数据库连接么?
  4. 前沿分享|阿里云数据库高级技术专家 宋利兵:阿里云企业级自治数据库RDS详解
  5. 回顾 | Apache Flink 1.13 新版本 x 互娱实践分享 Meetup · 北京站精彩回顾 (附 PPT 下载)
  6. 业内首发,ACK@Edge支持高质量加密网络与精细化管理
  7. android 恢复出厂设置 时间,Android 恢复出厂设置后,时间不能恢复为:2013年1月1日...
  8. PHP面试题:对于用户输入一串字符串$string,要求$string中只能包含大于0的数字和英文逗号,请用正则 表达式验证,对于不符合要求的$string返回出错信息
  9. InstantClient安装使用
  10. 设置Linux虚拟机和主机在同一网段