基础概念
并发与并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行, 即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。
线程与进程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
图中每一行代表一个进程

图中每一行代表一个线程

线程的创建
线程的创建共有两种方式,一种是继承 Thread 类,另一种就是实现 Runnable 接口
实现 Runnable 接口:
public class MyThread1 implements Runnable {
private final String name;
public MyThread1(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程:" + this.name + " "+ i + "-----");
}
}
}
执行:
public class Main {
public static void main(String[] args) {
MyThread1 m1 = new MyThread1("线程1");
MyThread1 m2 = new MyThread1("线程2");
Thread t1 = new Thread(m1);
Thread t2 = new Thread(m2);
t1.start();
t2.start();
}
}
输出:
线程:线程1 0-----
线程:线程2 0-----
线程:线程1 1-----
线程:线程2 1-----
线程:线程1 2-----
线程:线程2 2-----
线程:线程1 3-----
线程:线程2 3-----
线程:线程1 4-----
线程:线程2 4-----
线程:线程1 5-----
线程:线程2 5-----
线程:线程1 6-----
线程:线程2 6-----
线程:线程1 7-----
线程:线程2 7-----
线程:线程1 8-----
线程:线程2 8-----
线程:线程1 9-----
线程:线程2 9-----
继承 Thread 类:
public class MyThread2 extends Thread {
private String name;
public MyThread2(String name){
this.name=name;
}
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("线程:" + this.name + " "+ i + "-----");
}
}
}
执行:
public class ThreadDemo2 {
public static void main(String[] args) {
MyThread2 m1=new MyThread2("线程1");
MyThread2 m2=new MyThread2("线程2");
m1.start();
m2.start();
}
}
输出:
线程:线程1 0-----
线程:线程2 0-----
线程:线程1 1-----
线程:线程2 1-----
线程:线程2 2-----
线程:线程1 2-----
线程:线程2 3-----
线程:线程1 3-----
线程:线程2 4-----
线程:线程1 4-----
线程:线程2 5-----
线程:线程1 5-----
线程:线程2 6-----
线程:线程1 6-----
线程:线程2 7-----
线程:线程1 7-----
线程:线程2 8-----
线程:线程1 8-----
线程:线程2 9-----
线程:线程1 9-----
实现Runnable接口比继承Thread类所具有的优势:
适合多个相同的程序代码的线程去共享同一个资源。
可以避免java中的单继承的局限性。
增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
线程安全问题
三个售票口,同时售卖100张票,每张票售卖0.1秒,多线程解决:
public class Shopping implements Runnable {
private int ticket;
public Shopping(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket--);
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
Shopping s = new Shopping(100);
Thread t1 = new Thread(s,"售票口1");
Thread t2 = new Thread(s,"售票口2");
Thread t3 = new Thread(s,"售票口3");
t1.start();
t2.start();
t3.start();
}
}
输出问题:
票重复售卖

卖出负数票

卖出重复票的原因:线程二在执行完售出后,执行 ticket--前(此时票已售出,但票数没有减少),线程一开始执行,并且顺利售出了一张票,导致同时售出了第100张票。
卖出负数票的原因:在线程1执行if时,票数处于大于零状态,但在票数售出前其他线程执行了ticket- -,之后再执行此线程时便会出错。
解决方案:
同步方法:在方法前加入synchronized
关键字,保证线程执行该方法的时候,其他线程只能在方法外等着(对性能影响较大)
public synchronized void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket--);
}
}
}
同步代码块:将可能出现线程安全的问题放入代码块中,并在代码块前加入synchronized关键字,并且双重检查,防止出错
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Shopping.class) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()+"正在卖"+ ticket);
}
ticket--;
}
}
}
}
加锁:
java.util.concurrent.locks.Lock
机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
:加同步锁。public void unlock()
:释放同步锁。
public class Shopping implements Runnable {
private int ticket;
Lock lock = new ReentrantLock();
public Shopping(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖" +ticket--);
}
lock.unlock();
}
}
}
线程的状态
在API中java.lang.Thread.State
这个枚举中给出了六种线程状态:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
| 线程状态 | 导致状态发生条件 |
|---|---|
| NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
| Runnable(可 运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器。 |
| Blocked(锁阻 塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程持有锁时,该线程将变成Runnable状态。 |
| Waiting(无限 等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
| TimedWaiting(计时 等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、 Object.wait。 |
| Teminated(被 终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |

线程优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY
) ~ 10 (Thread.MAX_PRIORITY
)。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)
。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
取得和设置线程的名称
Thread.currentThread().getName()//返回当前线程的名字:Thread-0等待唤醒机制
什么是等待唤醒机制
这是多个线程间的一种协作机制。
就是在一个线程进行了规定操作后,就进入等待状态wait()
, 等待其他线程执行完他们的指定代码过后 再将其唤醒notify()
;在有多个线程进行等待时, 如果需要,可以使用 notifyAll()
来唤醒所有的等待线程。
等待唤醒中的方法
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是通知(notify)在这个对象等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
notifyAll:则释放所通知对象的 wait set 上的全部线程。
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
调用wait和notify方法需要注意的细节
wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
包子问题:
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
BaoZi
public class BaoZi {
String pier ;
String xianer ;
boolean flag = false ;//包子资源 是否存在 包子资源状态
}
BaoZiPu
public class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0;
//造包子
while (true) {
//同步
synchronized (bz) {
if (bz.flag) {//包子资源 存在
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有包子 造包子
System.out.println("包子铺开始做包子");
if (count % 2 == 0) {
// 冰皮 五仁
bz.pier = "冰皮";
bz.xianer = "五仁";
} else {
// 薄皮 牛肉大葱
bz.pier = "薄皮";
bz.xianer = "牛肉大葱";
}
count++;
bz.flag = true;
System.out.println("包子造好了:" + bz.pier + bz.xianer);
System.out.println("吃货来吃吧");
//唤醒等待线程 (吃货)
bz.notify();
}
}
}
}
ChiHuo
public class ChiHuo extends Thread {
private BaoZi bz;
public ChiHuo(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
while (true) {
synchronized (bz) {
if (!bz.flag) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包子");
bz.flag = false;
bz.notify();
}
}
}
}
Demo
public class Demo {
public static void main(String[] args) {
//等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货",bz);
BaoZiPu bzp = new BaoZiPu("包子铺",bz);
ch.start();
bzp.start();
}
}
线程池
概念
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。
在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService
对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
使用线程池中线程对象的步骤:
创建线程池对象。
创建Runnable接口子类对象。(task)
提交Runnable接口子类对象。(take task)
关闭线程池(一般不做)。
线程池的使用
MyRunnable:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("开始,接下来等待两秒");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("两秒结束:" + Thread.currentThread().getName());
System.out.println("本线程完成工作");
}
}
ThreadPoolDemo:
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
MyRunnable r = new MyRunnable();
service.submit(r);
service.submit(r);
service.submit(r);
}
}




