(一)多线程API
1. 进程和线程
进程:操作系统管理的基本运行单位。也是资源分配的最小单位。进程内的线程共享这部分资源。
线程:进程中独立运行的子任务。
2. 常用的方法
(1) CurrentThread()
返回正在调用代码的那个线程。(需要注意:自定义线程中,只有run方法中执行的时候,currentThread 返回的才是自定义线程)
(2) isLive()方法
判断当前线程是否处在活动状态。
(3) Sleep()方法
正在执行的线程暂时休眠(暂停执行)
(4) GetID()方法
获取线程的唯一标识
(5) Interrupted()
测试当前线程是否已经是中断状态。执行后将状态清除,重新置为false。
(6) isInterrupted()
测试当前线程是否已经是终端状态。执行后,不清楚状态标志。
(7) Suspend ()暂停、挂起
方法的功能是暂停线程。后面可以和resume()配合使用重新启动。但是不建议使用。主要的弊端如下:
资源独占。正在使用资源的线程被挂起,它加锁使用的资源正在独自占用。不会释放。容易造成资源独占。
不同步:正在使用的对象变量可能被修改到一半。造成数据不同步。
(8) Resume()恢复
被暂停的线程恢复正常使用。
(9) Yeid
放弃当前CPU资源,让给其它线程使用。
3. 线程的创建,启动方式
线程创建有两种方式。
(1) 继承Thread类。
ThreadA Extends Thread类,复写Run 方法。
启动:ThreadA.start() 直接使用。这种是每个ThreadA都是单独的一个线程。其中的变量都各自不同。
启动:Thread(ThreadA,”name”).start()。这样的方式,ThreadA 被公用,里面的变量也是公用的。存在共享对象变量的情况。
(2) 实现Runnable 接口。
继承MyRunnAble Imlpements Runnable 接口,实现run方法
启动:Thread(MyRunnable).start()
问题点:
为什么也要类,也要接口。因为JAVA是单继承。如果一个类已经有一个父类,就不能继承Thread来创建线程。所以,设计了Runable接口。
4. 停止线程
线程停止有三种方式:
- 线程执行完毕,正常退出。
- 使用stop方法强行退出。不建议使用(强行退出会导致依赖对象数据不一致-中途停止,可能修改到一半)。
(1) Stop 暴力停止:
会导致数据不一致。本质上也是抛出异常。ThreadDeath异常。
- 使用interrupt()方法退出【建议使用】调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。
(1) 异常法:
外部通过调用thread.interrupt
Run 方法中使用 interrupted 或者 isInterrupted 进行判断。抛出异常。可以中断线程。
(2) 沉睡中停止:
线程在sleep过程中,如果被调用interrupt(),会自动跑异常。然后停止线程。
(3) Return 方法:使用interrupt和return配合使用。
外部调用Thread.interrupt
在Run方法中判断interrupted或者isInterrupted 进行判断。然后return。即可退出线程。
5. 暂停线程
Suspend ()暂停、挂起
方法的功能是暂停线程。后面可以和resume()配合使用重新启动。但是不建议使用。主要的弊端如下:
资源独占。正在使用资源的线程被挂起,它加锁使用的资源正在独自占用。不会释放。容易造成资源独占。
不同步:正在使用的对象变量可能被修改到一半。造成数据不同步。
Resume()恢复
被暂停的线程恢复正常运行。
6. 优先级
优先级越高,得到的CPU资源就越多。CPU优先执行优先级高的线程对象的任务。
线程的优先级继承性:线程的优先级具备继承性。没有特殊的指定,线程的优先级和创建它的线程的优先级一致。
7. 守护线程
和主线程共死,finally不能保证一定执行
在start之前setDaemon(true);设置守护线程,主线程运行完,子线程也结束。常见的守护线程为:垃圾回收器。
(二)对象及变量并发访问
1. 相关定义
(1) 线程安全同步
进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域。也称之为主工作内存。
线程安全:堆内存中的数据,由于可以被任意线程访问到,在没有限制的情况下会存在意外被修改的风险。即队内存的数据在没有被保护机制的情况下,对多线程而言是不安全的。
(2) 死锁
不同的线程分别占用对方需要的同步资源不释放,都等对方放弃自己需要的资源。
(3) 线程同步
多个线程访问同一资源。后一个线程需要等待前一个访问结束才能访问。同一个时刻只有一个线程访问。必须执行到底之后才能执行其它线程。
(4) 互斥锁
限定同一时刻, 只有一个线程能对共享数据进行操作, 其他线程需要等到该线程处理完之后再进行操作。
互斥锁有以下特征:
- 线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁。
- 在同一时刻, 只有一个线程能持有这个锁。当线程 A 尝试获取一个由线程 B 持有的锁时, 线程 A 必须等待或阻塞, 直到线程 B 释放了这个锁。 如果 B 不释放这个锁, 则 A 就需要一直等待。
- 可重入性: 指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
(5) 后续
2. 关键字
(1) synchronized互斥锁
JAVA提供的内置互斥锁。synchronized的使用有三种方式:
① 普通同步方法,锁是当前实例对象(this)。多个线程,一旦有一个线程正在执行,其它线程就进不去【是对象锁,锁的是当前对象】。
② 同步代码块,锁是括号里面的对象。【也是对象锁,可以是任意对象】
③ 静态同步方法,锁是当前类的 Class 对象。【调用类的静态方法,通过对象锁是锁不住的。这个时候需要通过锁类】
其中 1、2是对象锁。理解为对象锁。就是一旦有线程得到这个对象。就不会有其它的线程访问这个对象。
(2) Volatile
JAVA提供的关键字,确保线程之间可见。线程通过排它锁确保只有一个线程获取Volatile 修饰的常量。
- 可见性:确保读取得时候是最后一次写的数据。
- 原子性:对volatile修饰对常量的读写是具备原子性的。
- 有序性:
3. 方法内变量和实例变量
方法内的变量线程安全。方法内的变量是私有的。
实例变量是非线程安全的。
(三)线程间通信
线程之间的通信,协作实现更复杂的工作。
1. 使用wait/notify实现线程间通信
Wait、notify函数都必须在同步代码块中被调用。否则会报错。
Wait()方法调用后,当前线程会释放已经占有的资源。并在线程阻塞队列中,等待被通知。
Notify()方法会唤醒阻塞队列中的一个线程。不过是随机的。Notify方法执行后并不立即释放锁。会执行当前线程内容后释放锁。
NotifyAll()方法会唤醒阻塞队列中的所有线程进行抢夺锁。
除wait/notify方式外,还有其它的线程之间的通信。
- 管道进行通信:字节流
- 管道进行通信:字符流
2. 生产者、消费者模式
假死:所有线程都进入等待的状态。程序不再执行任何业务。产生假死的原因是:唤醒的是同类。
当前模式常被称为 生产者、消费者、仓库模式。
生产者:一批线程负责生产数据,放入仓库。
消费者:一批线程负责消费数据。从仓库中取出。
仓库:生产者和消费者所共享的对象常量。
两类线程交叉操作是 生产者、消费者、仓库的一种简化。仓库为一个状态。0/1。根据不同的状态,确保哪类线程开始工作。其中,状态需要使用volatile进行修饰。
3. join方法
主线程和创建的子线程之间本来是两个线程。有各自的执行时间。不会相互等待。而在主线程中执行 子线程.join() ,之后的方法需要等子线程完成之后再执行。
Join和sleep之间的区别:join的本质是调用wait方法。所以,主线程当前锁被释放了的。而sleep是休眠。不会释放锁。
4. ThreadLocal类的使用
主要的目的是做数据的线程隔离。
变量的共享值 需要使用public static修饰。主要解决的是每个线程都可以保存自己的变量。不同线程都可以调用,存放自己的内容。
方法:对应的两个方法:Get() 和 set(“”)
不同线程之间有隔离:多个线程同时向threadLocal类中写数据。各自读取的都是自己线程写入的数据。
InheritableThreadLocal 的使用。特点:子线程可以继承主线程的值。
(四)Lock的使用
1. ReentrantLock
(1) 同步实现
reentrantlock实现同步,是通过lock() 方法获取锁 和 UnLock()方法释放锁 的方式来实现的。
Lock和unlock之间的内容。相当于synchronized的花括号之间的内容。
(2) 等待、通知实现
通过lock.newCondition() 的condition的await() 等待方法和 signal()唤醒 方法。来实现等待、通知机制的。
同样有signalAll方法。唤醒condition所有的等待线程。
(3) 调用代码必须有同步监视器
和synchronized的wait(), notify()必须在同步锁内一样。Condition的await和signal 也必须在lock和unlock之间一样。
(4) 多个condition实现部分通知
通过不同的condition 可以实现部分通知。
(5) 公平锁和非公平锁
公平锁:线程获取锁的顺序是按照加锁的顺序。FIFO 队列。先进先出的顺序。
2. reentrantReadWriteLock
读写锁有两个锁
读操作的锁:也称之为共享锁。读锁和读锁的线程是可以共享的,也是异步执行。
Lock.readLock
写操作的锁:也称之为互斥锁。
Lock.writeLock
其中读读共享、读写互斥、写读互斥、写写互斥。
(五)线程池
1. 定义/作用
线程池:管理线程的池子。
作用:
(1) 帮助我们管理线程,避免增加创建线程和撤销线程的资源消耗。
(2) 提高响应效率。任务到来直接拿线程开始使用。
(3) 重复使用。
2. 建线程池的核心参数和作用
l corePoolSize:线程池 核心线程数的最大值。
l maximumPoolSize:线程池的 最大线程数。