Java 线程同步及通信
同步
进程
进程与线程基本区别: 进程内的数据不同线程可共享,不同进程数据除非通信不可共享。
共享: 共享非实时的,创建线程时会把所需数据重新拷贝一份使用,计算完成后的数据写回具有随机性,以减少数据不断跨线程读取耗费的时间。 所以当不同线程同时操作同一个数据时应考虑线程同步。
进程与CPU
单核CPU:操作系统划分时间片进行多进程操作
多核CPU:每个核可独立运行一个进程
Callable -> 有返回值的Runnable
1 | Future = ExecutorService.submit(Callable) |
线程池
线程池优点:
- 方便统一管理,如对线程命名
- 线程的创建及销毁需耗费资源,使用线程池可方便重用
线程同步:
- volatile: 强制打开同步,线程读取时总从原数据中读取,写入时即刻更新。
- synchronized: 将代码块封装成原子操作,提供互斥访问,其中的数据无法被其他线程修改
- monitor: 使用同一个 monitor 的不同 synchronized 代码块共享同样的互斥条件。 如 synchronized A() 和 synchronized B() 使用同一个 monitor 时,当 A() 被调用时,B() 也不可访问。
- lock: 将锁分为读锁/写锁,可更精确的对数据读写进行操作,比如可开放多个线程同时读数据的权限。
- Atomic: Java中对部分数据类型封装的类,如
AtomicInt
,将非原子操作的int++
封装成原子操作的AtomicInt.getAndIncrement()
一个较完备的单例:
1 | class SingleMan { |
通信
- thread.stop()
deprecated
立即停止线程, 停止位置不可控,可能造成线程任务处于中间状态导致程序错误 - thread.interrupt()
通知线程中止,非强制性,线程通过isInterrupted()
获得通知以通过可控的方式中止操作。
如果此时线程处于sleep
状态,会抛出InterruptedException
。 - monitor.wait()/monitor.notify()/monitor.notifyAll()
- 等待区/排队区的对象指同一个monitor进行监听的线程
–> monitor.notify() // 在等待区中随机抽取一个线程进入排队区
–> monitor.notifyAll() // 将等待区中所有线程都放入排队区
–> 进入排队区的原等待线程执行优先级与其他排队区线程一致1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20┌─────────────────────┐
│ waiting │
│ ├────────┐
│ ┌───────────────┐ │ │
│ │thread.notify()│ │ │
│ ├───────────────┼ │ │
└──────────┼──────────┘ │
│ │
┌──────────▼──────────┐ │
│ queuing │ │
│ │ │
│ │ │ │
└──────────┼──────────┘ │
│ │
┌──────────▼──────────┐ │
│ running │ │
│ ┌─────────────┐ │ │
│ │thread.wait()├───┼────────┘
│ └─────────────┘ │
└─────────────────────┘ - thread.join()
执行该thread完毕后才执行后续代码,如:1
2
3
4
5
6
7
8
9
10
11
12
13
14thread1 = {
sleep(3000)
print("thread1 ended")
}
thread1.start()
thread2 = {
thread1.join()
print("thread2 ended") // 会在thread1执行完后才调用
}
thread1.start()
==========================
输出结果:
thread1 ended
thread2 ended - thread.yield()
让出时间片给其他相同优先级的线程,但在当前线程执行完后立即获取时间片