进程与线程
- 进程
- 程序的实例(一般一个程序只有一个实例)
- 线程
- 最小的可执行单元
- 一个进程可以有多个线程(进程必须要有一个线程)
线程的创建方式
- 继承 Thread 类
- 如果直接调用 run() , 那么和调用普通的方法一样,会挂起当前方法等待 run() 执行完成
class ThreadDemo extends Thread { // 无参构造器 ThreadDemo(){ } public void run(){ // 自定义的代码 } } public statin void main(String[] args){ // 调用无参构造器来创建线程对象 ThreadDemo td = new ThreadDemo(); /* 开辟新线程执行和调用 run() 方法有区别 run() 方法会将当前线程压栈,等待 run() 方法执行完成后,再回到当前方法 */ td.start(); } - 实现 Runnable 接口
- 三种方式,本质上都是 Runnable 的实现
class RunDemo implements Runnable {
RunDemo(){}
// 实现接口的 run() 方法
public void run(){
// 自定义的代码
}
}
public static void main(String[] args){
Runnable runDemo = new RunDemo();
// 调用 Thread 类的有参构造器并传入线程接口的一个实现来创建线程,并启动线程
new Thread(runDemo).start();
}
- 实现 Callable 接口
-
Callable 需要被包装成 Future
-
Future 需要被包装成 FutureTask
-
FutureTask 事实上实现了 RunnableFuture 接口
-
RunnableFuture 接口是 Runnable 和 Future 的组合接口
-
所以,Callable 本质上还是 Runnable
-
可返回值的线程创建方式 3.1 Callable + Future
public class CallAndFuture implements Callable{ private String name; public CallAndFuture(String name ){ this.name = name; } public String call(){ return "11111133333" + this.name; } public static void main (String[] args) throws ExecutionException, InterruptedException { /* code */ CallAndFuture callOne = new CallAndFuture("结果one"); CallAndFuture callTwo = new CallAndFuture("结果two"); CallAndFuture callThree = new CallAndFuture("结果three"); ExecutorService es = Executors.newFixedThreadPool(3); Future futureOne = es.submit(callOne); System.out.println(futureOne.get()); Future futureTwo = es.submit(callTwo); System.out.println(futureTwo.get()); Future futureThree = es.submit(callThree); System.out.println(futureThree.get()); es.shutdown(); }
3.2 Callable + FutureTask -
-
get()方法会阻塞主线程,一般使用带有超时时间的get(long timeout, TimeUnit unit) -
一般在其它任务完成后,再去获取线程执行结果; 如果后续结果依赖线程执行的结果话,可以阻塞等待(但为什么要这么做呢?)
线程的状态
- 新建(NEW)
- 运行(RUNNABLE)
- 就绪
- 运行中
- 等待(WAITING)
- 等待其它线程的动作(通知或中断)
- 阻塞(BLOCKED)
- 因锁阻塞
- 超时等待(TIMED_WAITING)
- 有结束时间的等待
- 过了指定时间后自行回到就绪状态排队
- sleep() 方法会引发这个状态
- 死亡(TERMINATED)
- 执行完毕
线程的最终执行方法
native void start0()start0()方法由静态方法static native void registerNatives()准备初始化工作- 所以,本质上 java 是无法开启线程的,而是交由底层 C++ 实现的方法 start0 开启
线程的常用方法
- wait
- 调用本方法让线程进入等待状态,会释放锁
- join
- 线程强制执行,俗称插队。谁调用了,谁就会被阻塞。
- notify
- 唤醒等待队列中的一个线程,来竞争 cpu
- notifyAll
- 唤醒等待队列中的所有线程,来竞争 cpu
- stop(不建议使用)
- destroy(不建议使用)
- yield
- 线程礼让。先退出 cpu ,重新和其它线程竞争。
- 可能再次竞争到 cpu , 完全是看 cpu 的心情。
- sleep
- 调用本方法阻塞线程,不会释放锁
线程的同步机制
synchronized- 可以锁住方法
public synchronized void test(){}
- 可以锁住代码块
synchronized(obj){}
- 可以锁住方法
Lock接口- 可以锁住代码块
- ReentrantLock 锁
ReentrantLock lock = new ReentrantLock(); try{ // 加锁 lock.lock(); }finally{ // 解锁 lock.unlock(); }- LockSupport
class FIFOMutex { private final AtomicBoolean locked = new AtomicBoolean(false); private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>(); public void lock() { boolean wasInterrupted = false; Thread current = Thread.currentThread(); waiters.add(current); // Block while not first in queue or cannot acquire lock while (waiters.peek() != current || !locked.compareAndSet(false, true)) { LockSupport.park(this); if (Thread.interrupted()) // ignore interrupts while waiting wasInterrupted = true; } waiters.remove(); if (wasInterrupted) // reassert interrupt status on exit current.interrupt(); } public void unlock() { locked.set(false); LockSupport.unpark(waiters.peek()); } }}- ReentrantReadWriteLock 可重入的读写锁
public class ReadWriteLockDemo { public static void main(String[] args) { Note note = new Note(); for (int i = 0; i < 100; i++) { if( i%10 == 0){ new Thread(new TakeNote(note)).start(); } else { new Thread(new ViewNote(note)).start(); } } } } class TakeNote implements Runnable{ private Note note = null; public TakeNote(Note n){ this.note = n; } @Override public void run() { try { this.note.add("====" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } class ViewNote implements Runnable { private Note note = null; public ViewNote(Note note){ this.note = note; } @Override public void run() { try { this.note.get(); } catch (InterruptedException e) { e.printStackTrace(); } } } class Note{ private ArrayList<String> al = new ArrayList<String>(); private ReentrantReadWriteLock rtRw = new ReentrantReadWriteLock(); private Lock readLock = rtRw.readLock(); private Lock writeLock = rtRw.writeLock(); public void add(String note) throws InterruptedException { writeLock.lock(); System.out.println("===****正在做笔记笔记===" + Thread.currentThread().getName()); al.add(note); Thread.sleep(5000); System.out.println("===****做完笔记===" + Thread.currentThread().getName()); writeLock.unlock(); } public String get() throws InterruptedException { // 读锁可以多次进入 readLock.lock(); System.out.println("===正在查看笔记===" + Thread.currentThread().getName()); if(al.size() == 0) {readLock.unlock(); return "";} String re = al.get(al.size()-1); Thread.sleep(2000); System.out.println("===查完笔记===" + Thread.currentThread().getName()); readLock.unlock(); return re; } }StampedLock印章锁
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
double distanceFromOrigin() { // A read-only method
// 获取乐观的读锁
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
// 尝试转换为写锁
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
}
else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
}}
线程的优先级
- 优先级只是在概率上有更大可能先被 cpu 执行,不是优先级高就必然比优先级低先执行
- 优先级的数值范围是正整数
[1-10] - setPriority() 设置优先级
- 优先级的一些常量
Thread.MIN_PRIORITY(1)Thread.NORM_PRIORITY(5)Thread.MAX_PRIORITY(10)
守护线程(daemon)
- 一般线程都是用户线程
setDaemon(true)开启守护线程- 虚拟机必须等待所有用户线程执行完,但不用等待守护线程执行完
- 常见的守护线程
- GC 垃圾回收线程
线程通信
生产者消费者模式
锁本质
- 锁究竟锁的是什么?(只有两个)
- 实例对象(可以有多个)
- 类对象 Class(一个类只存在一个 Class 对象)
线程并发的一些问题
虚假唤醒
- 把多个相关线程唤醒,但事实上不满足执行条件
- wait 和 notifyAll 的使用
- wait 要放在 while 循环中,避免虚假唤醒的问题
死锁
- 死锁的四个必要条件
- 互斥
- 持有并等待
- 不可强行剥夺资源
- 循环等待(需要的资源在别人手里)