复习资料:《同步与异步:并发/并行/进程/线程/多cpu/多核/超线程/管程 》
基本线程类
基本线程类
基本线程类指的是Thread类,Runnable接口,Callable接口
继承Thread创建线程
继承java.lang.Thread类创建线程是最简单的一种方法,也最直接。
public class MyThread1 extends Thread {}
种创建方式,把线程执行的逻辑代码直接写在了Thread的子类中,这样根据线程的概念模型,虚拟CPU和代码混合在一起了。并且java是单继承机制,线程体继承Thread类后,就不能继承其他类了,线程的扩展受影响。
实现Runnable接口创建线程
为了构建结构清晰线程程序,可以把代码独立出来形成线程目标对象,然后传给Thread对象。通常,实现Runnable接口的类创建的对象,称作线程的目标对象。
public class MyThreadTarget implements Runnable{ }
出线程目标对象和Thread分开了,并传递给了Thread。如果有比较复杂的数据要处理,可以在线程目标对象中引入数据。使用这种方式获得线程的名字就稍微复杂一些,需要使用到Thread中的静态方法,获得当前线程对象,然后再调用getName()方法。这种方式在较复杂的程序中用得比较普遍。
Callable
future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态
ExecutorService e = Executors.newFixedThreadPool(3); //submit方法有多重参数版本,及支持callable也能够支持runnable接口类型. Future future = e.submit(new myCallable()); future.isDone() //return true,false 无阻塞 future.get() // return 返回值,阻塞直到该线程运行结束
以上是基本方法,总结一下
多线程的基本操作
创建线程的方式
-
将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法
-
声明实现 Runnable 接口的类。该类然后实现 run 方法
推荐方式二,因为接口方式比继承方式更灵活,也减少程序间的耦合。
线程API一览
-
start() 使线程处于就绪状态,Java虚拟机会调用该线程的run方法;
-
stop() 停止线程,已过时,存在不安全性:
-
一是可能请理性的工作得不得完成;
-
二是可能对锁定的对象进行“解锁”,导致数据不同步不一致的情况。
推荐 使用 interrupt() +抛异常 中断线程。
-
suspend() 暂停线程,已过时。
-
resume() 恢复线程,已过时。
suspend 与resume 不建议使用,存在缺陷:
-
一是可能独占同步对象;
-
二是导致数据不一致。
-
yield() 放弃当前线程的CPU资源。放弃时间不确认,也有可能刚刚放弃又获得CPU资源。
-
t.join() 等待该线程t 销毁终止。
获取当前线程信息
Thread.currentThread()
线程的分类
线程分为守护线程、用户线程。线程初始化默认为用户线程。
setDaemon(true) 将该线程标记为守护线程或用户线程。
特性:设置守护线程,会作为进程的守护者,如果进程内没有其他非守护线程,那么守护线程也会被销毁,即使可能线程内没有运行结束。
某线程a 中启动另外一个线程t,那么我们称线程t是线程a的一个子线程,而 线程a是线程t的父线程。
最典型的就是我们在main方法中 启动一个线程去执行。其中main方法隐含的main线程为父线程。
线程安全
线程安全就是每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的。
线程安全就是说多线程访问同一代码,不会产生不确定的结果。
反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:
线程安全问题多是由全局变量和静态变量引起的
-
当多个线程对共享数据只执行读操作,不执行写操作时,一般是线程安全的;
-
当多个线程都执行写操作时,需要考虑线程同步来解决线程安全问题。
线程同步
同步是保证多个线程之间共享同一个全局变量数据安全问题,保证数据原子性。
多个线程操作一个资源的情况下,导致资源数据前后不一致。这样就需要协调线程的调度,即线程同步。 解决多个线程使用共通资源的方法是:线程操作资源时独占资源,其他线程不能访问资源。使用锁可以保证在某一代码段上只有一条线程访问共用资源。
有两种方式实现线程同步:
-
synchronized,使用同步代码块解决线程安全问题
-
同步锁(Lock)
Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
线程状态
synchronized关键字用法
当它用来修饰一个方法或者一个代码块的时候(同步代码块:将可能会发生线程安全问题的代码给包裹起来),能够保证在同一时刻最多只有一个线程执行该段代码。
-
原子性(互斥性):实现多线程的同步机制,使得锁内代码的运行必需先获得对应的锁,运行完后自动释放对应的锁。
-
内存可见性:在同一锁情况下,synchronized锁内代码保证变量的可见性。
-
可重入性:当一个线程获取一个对象的锁,再次请求该对象的锁时是可以再次获取该对象的锁的。
如果在synchronized锁内发生异常,锁会被释放。
-
synchronized方法 与 synchronized(this) 代码块 锁定的都是当前对象,不同的只是同步代码的范围
-
synchronized (非this对象x) 将对象x本身作为“对象监视器”:
-
多个线程同时执行 synchronized(x) 代码块,呈现同步效果。
-
当其他线程同时执行对象x里面的 synchronized方法时,呈现同步效果。
-
当其他线程同时执行对象x里面的 synchronized(this)方法时,呈现同步效果。
-
静态synchronized方法 与 synchronized(calss)代码块 锁定的都是Class锁。Class 锁与 对象锁 不是同一个锁,两者同时使用情况可能呈异步效果。
-
尽量不使用 synchronized(string),是因为string的实际锁为string的常量池对象,多个值相同的string对象可能持有同一个锁。
synchronized原理:
-
首先有一个线程已经拿到了锁,其他线程已经有cup执行权,一直排队,等待释放锁。
-
锁是在什么时候释放?代码执行完毕或者程序抛出异常都会被释放掉。
-
锁已经被释放掉的话,其他线程开始进行抢锁(资源竞争),谁抢到谁进入同步中去,其他线程继续等待。
效率非常低,多个线程需要判断锁,比较消耗资源,抢锁的资源。——synchronized 只能有一个线程进行执行(就像厕所只有一个坑,好多人等着上厕所,谁进入厕所谁上锁,其他人在外等待),这个线程不释放锁的话,其他线程就一直等,就会产生死锁问题。
volatile关键字用法
多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。
Volatile 关键字的作用是变量在多个线程之间可见,但不保证原子性。
-
内存可见性:保证变量的可见性,线程在每次使用变量的时候,都会读取变量修改后的最的值。
-
不保证原子性。
针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。
线程间的通信方式
线程间通信的方式主要为共享内存、线程同步。
线程同步除了synchronized互斥同步外,也可以使用wait/notify实现等待、通知的机制。
-
wait/notify属于Object类的方法,但wait和notify方法调用,必须获取对象的对象级别锁,即synchronized同步方法或同步块中使用。
-
wait()方法:在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,或者其他某个线程中断当前线程,导致当前线程一直阻塞等待。等同wait方法。
-
wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。 单位为毫秒。
-
void wait(long timeout, int nanos) 与 wait(long timeout) 不同的是增加了额外的纳秒级别,更精细的等待时间控制。
-
notfiy方法:唤醒在此对象监视器上等待的单个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
-
notifyAll方法:唤醒在此对象监视器上等待的所有线程。
需要:wait被执行后,会自动释放锁,而notify被执行后,锁没有立刻释放,由synchronized同步块结束时释放。
应用场景:简单的生产、消费问题。
synchronized (lock) {//获取到对象锁lock try { lock.wait();//等待通信信号, 释放对象锁lock } catch (InterruptedException e) { e.printStackTrace(); } }//接到通信信号 synchronized (lock) { //获取到对象锁lock lock.notify();//通知并唤醒某个正等待的线程 //其他操作 }//释放对象锁lock
synchronized, wait, notify 是任何对象都具有的同步工具。
他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。
wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。
当某代码并不持有监视器的使用权时(如上图的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。
参考文章:
Java多线程并发编程一览笔录 https://www.cnblogs.com/yw0219/p/10597041.html
Java 中的多线程你只要看这一篇就够了 https://juejin.im/entry/57339fe82e958a0066bf284f
阿里大牛详细讲解java多线程并发,PDT文档实战篇https://zhuanlan.zhihu.com/p/92958977
JAVA多线程之间实现同步+多线程并发同步解决方案 https://blog.csdn.net/yz2015/article/details/79436123
转载本站文章《java并发编程(1):Java多线程-基本线程类-基础知识复习笔记》,
请注明出处:https://www.zhoulujun.cn/html/java/KeyConcepts/8471.html