java并发编程(一基本概念、线程基础)
1.简介
为什么需要并行
提高程序性能(利用cpu多核特点【并行】、充分利用CPU的时间片【并发】)
业务上需要(例如服务器需要接收多个请求,需要用不同线程处理请求)
硬件上速度达到瓶颈,目前芯片处理速度4GHZ,已经停留了10年,需要从软件方面提高处理能力。
几个重要的概念
同步(synchronous)和异步(asynchronous)
同步在程序处理上是在一个时间段串行运行。
异步在程序处理上是在一个时间段能同时做多件事。
并发(Concurrency)和并行(Parallelism)
并发不是真正的并行,是程序利用CPU的时间片来执行多个线程。
并行事多核CPU同时执行多个线程。
临界区
表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程 使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。
阻塞(Blocking)和非阻塞(Non-Blocking)
阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其它所有需要 这个资源的线程就必须在这个临界区中进行等待,等待会导致线程挂起。这种情况就是阻塞。此时,如 果占用资源的线程一直不愿意释放资源,那么其它所有阻塞在这个临界区上的线程都不能工作。
非阻塞允许多个线程同时进入临界区死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
饥饿是指某一个或 者多个线程因为种 种原因无法获得所 需要的资源,导致 一直无法执行。
活锁是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
(活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
)并发级别
阻塞: 当一个线程进入临界区后,其他线程必须等待 。无障碍(Obstruction-Free):
- 无障碍是一种最弱的非阻塞调度
- 自由出入临界区
- 无竞争时,有限步内完成操作
- 有竞争时,回滚数据
无锁(Lock-Free):
- 是无障碍的
- 保证有一个线程可以胜出
无等待(Wait-Free):
- 无锁的
- 要求所有的线程都必须在有限步内完成
- 无饥饿的
有关并行的2个重要定律
Amdahl定律(阿姆达尔定律)
增加CPU处理器的数量并不一定能起到有效的作用 提高系统内可并行化的模块比重,合理增加并行处 理器数量,才能以最小的投入,得到最大的加速比 。
Gustafson定律(古斯塔夫森)
只要有足够的并行化,那么加速 比和CPU个数成正比 。
2. 线程基础
什么是线程
线程是进程内的执行单元 。
线程的状态
- new
线程对象已经创建,还没有在其上调用start()方法。 - runnable
线程就绪,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。 - running
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。 - blocked
阻塞,多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区。 - wating
线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 以便该线程可以继续下一步操作。
(区分 blocked和 wating的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束) - timed_waiting
有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态。 - terminated
表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)。
线程的基本操作
- 继承thread类
package basic;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承 Thread方式");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
- 实现runnable接口
package basic;
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable方式");
}
public static void main(String[] args) {
Thread myThread = new Thread(new MyRunnable());
myThread.start();
}
}
- 终止线程
Thread.stop() 已过时,不推荐使用。它会释放所有monitor ,多线程时可能带来数据异常。 - 中断线程
public void Thread.interrupt() // 中断线程 public boolean
Thread.isInterrupted() // 判断是否被中断 public static boolean
Thread.interrupted() // 判断是否被中断,并清除当前中断状态
package basic;
public class Mythread1 extends Thread {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interruted!");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interruted When Sleep");
// 设置中断状态,抛出异常后会清除中断标记位
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
Mythread1 t1 = new Mythread1();
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
- 挂起(suspend)和继续执行(resume)线程
suspend()不会释放锁 。
如果加锁发生在suspend()之前 ,则死锁发生 。
– 等待线程结束(join)和谦让(yeild)
public final void join() throws InterruptedException public final synchronized
void join(long millis) throws InterruptedException
package basic;
public class JoinMain {
public volatile static int i = 0;
public static class AddThread extends Thread {
@Override
public void run() {
for (i = 0; i < 100000; i++)
;
}
}
public static void main(String[] args) throws InterruptedException {
AddThread at = new AddThread();
at.start();
at.join();
System.out.println(i);
}
}
join的本质 while (isAlive()) { wait(0); }
线程执行完毕后, 系统会调用 notifyAll() 。
所以不要在Thread实例上使用 wait()和notify()方法 。
- 守护线程
在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程 。
当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出 。
Thread t = new DaemonT();
t.setDaemon(true);
t.start();
- 线程优先级
高优先级的线程更容易再竞争中获胜
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
Thread high = new HightPriority();
LowPriority low = new LowPriority();
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
low.start();
high.start();
- 线程同步操作
synchronized
– 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
– 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
– 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
指定加锁对象
public void run() {
for (int j = 0; j < 10000000; j++) {
synchronized (instance) {
i++;
}
}
}
用在方法上
public synchronized void increase() {
i++;
}
public static synchronized void increase() {
i++;
}
- Object.wait() Obejct.notify()
这两个方法要在synchronized 同步代码块内使用。
public static class T1 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":T1 start! ");
try {
System.out.println(System.currentTimeMillis() + ":T1 wait for object ");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":T1 end!");
}
}
}
public static class T2 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread");
object.notify();
System.out.println(System.currentTimeMillis() + ":T2 end!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
}
}