多线程基础
一、基本介绍
1.1 进程
当一个程序进入到内存运行,即变成了一个进程,进程是处于运行过程中的程序,并且具有一定的独立功能
- 正在运行中的程序
- 具有一定的独立功能
比如正在运行中的(QQ、迅雷)
1.2 线程
线程是进程中的一个执行单元,负责当前进程中的程序的执行。
- 一个进程至少有一个线程
- 一个进程可以包含多个线程
- 线程开启不一定立即执行
单线程:多个任务只能依次执行。上一个任务结束后,下一个任务开始(qq音乐一首一首播放)
多线程:多个任务同时执行(迅雷同时下载多个文件)
1.3 运行原理
-
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
-
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)

实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。(CPU调度)
二、基本线程类使用
创建基本线程类有四种方式:
-
实现Runnable接口
-
继承Thread类
-
使用JDK 8 的Lambda
-
使用Callable和Future
2.1 Thread类 主要方法
| 方法 | 说明 |
|---|---|
| start() | 启动线程 |
| setName(String name) | 设置线程名称 |
| setPriority(int priority) | 设置线程优先级,默认5,取值1-10 |
| join(long millisec) | 挂起线程xx毫秒,参数可以不传 |
| interrupt() | 终止线程 |
| isAlive() | 测试线程是否处于活动状态 |
Thread静态(static)方法
| 方法 | 说明 |
|---|---|
| yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
| sleep(long millisec)/sleep(long millis, int nanos) | 挂起线程xx秒,参数不可省略 |
| currentThread() | 返回对当前正在执行的线程对象的引用 |
| holdsLock(Object x) | 当前线程是否拥有锁 |
2.2 实现Runnable接口
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
public class MyThread implements Runnable{
@override
public void run(){
System.out.println("正在运行"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread t = new MyThread();
new Thread(t).start();
}
}
2.3 继承Thread类
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("正在运行"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
2.4 Callable接口
- 能接受一个泛型,需要实现返回值
- 重写call方法,可以抛出异常
- get()方法的阻塞性
//callable的接口定义
public interface Callable<V> {
V call() throws Exception;
}
public class MyThread implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
System.out.println("call方法在执行");
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread callable = new MyThread();
//创建1个线程
ExecutorService ser = Executors.newFixedThreadPool(1) ;
//提交执行
Future<Boolean> result = ser.submit(callable) ;
//获取返回值,返回值Future也是一个接口,通过他的get()方法可以获得任务执行的返回值。
boolean b = result.get();
//因为get()方法的阻塞性,获取返回值以后主线程才会继续执行
ser.shutdown();//关闭
}
}
2.5 run()和start()的区别
真正启动线程的是start()方法而不是run(),run()和普通的成员方法一样,可以重复使用,但不能启动一个新线程。
三、线程状态
3.1 线程状态概览
- 初始NEW :新建尚未启动
- 运行RUNNABLE: 可运行
- 阻塞BLOCKED: 阻塞的(被同步锁或者IO锁阻塞)
- 等待WAITING: 永久等待状态
- 超时等待TIMED_WAITING: 等待指定的时间重新被唤醒的状态
- 终止TERMINATED: 执行完成
线程的状态可以使用getState()查看
3.2 线程的优先级
- 优先级高的不一定先执行
- 优先级低只是获得CPU调度的概率低
- 先设置优先级再启动
- 优先级用数字表示,范围从1~10
public class TestPriority {
public static void main(String[] args) {
//默认优先级为5
System.out.println(Thread.currentThread().getName()+"---"+Thread.currentThread().getPriority());
Thread t0 = new Thread(new MyPriority()) ;
Thread t1 = new Thread(new MyPriority()) ;
Thread t2 = new Thread(new MyPriority()) ;
t0.setPriority(Thread.MIN_PRIORITY);//1
t0.start();
t1.setPriority(Thread.NORM_PRIORITY);//5
t1.start();
t2.setPriority(Thread.MAX_PRIORITY);//10
t2.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"----"+ Thread.currentThread().getPriority());
}
}
3.3 守护线程 Daemon
- 线程分为用户线程和守护线程
- 虚拟机必须等待用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 后台记录操作日志,监控内存,垃圾回收等待
setDaemon(boolean on)
isDaemon()
四、线程安全
4.1 线程同步
发生多个线程同时操作同一个资源的情况下,需要线程同步,线程同步其实是一种排队机制
4.1.1 队列和锁
同一进程的多个线程共享同一存储空间,带来方便,也带来了访问冲突,为保证访问的正确性,访问时接入锁机制synchronized 特点
- 一个线程持有锁,导致其他线程必须挂起
- 加锁,释放锁会导致比较多的上下文切换,引起性能问题
- 一个优先级高的线程等待低优先线程释放锁,导致优先级导致
4.1.2 不安全的线程
- 多个线程同一时刻对同一个全局变量(同一份资源)做写操作 如果跟我们预期的结果一样为线程安全,反之线程不安全
不安全 案例1
//抢票案例
public class demo{
public static void main(String[] args) {
BuyTickNum buyTickNum = new BuyTickNum();//需要锁的对象是变化的量
new Thread(buyTickNum,"小明").start();
new Thread(buyTickNum,"黄牛").start();
new Thread(buyTickNum,"小红").start();
}
}
class BuyTickNum implements Runnable{
private int ticketNums = 10 ;
boolean flag = true ;
@Override
public void run() {
//买票
while(flag){
buy();
}
}
//需要加上synchronized关键字 同步方法
private void buy(){
//判断是否有票
if(ticketNums <= 0){
flag = false ;
return ;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//抢票
System.out.println(Thread.currentThread().getName() + ":抢到了"+ticketNums--);
}
}
不安全 案例2
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName()) ;
}).start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());//输出结果不到10000
}
4.1.3 同步方法
-
synchronized 关键字
-
同步块 synchronized(object){ }
- object为需要锁的对象。操作变化的对象
同步方法执行的原理
-
第一个线程访问,锁定同步监视器,执行其中代码。
-
第二个线程访问,发现同步监视器被锁定,无法访问。
-
第一个线程访问完毕,解锁同步监视器
-
第二个线程访问,发现同步监视器没有锁,然后锁定并访问
synchronized关键字 案例
//
public class demo{
public static void main(String[] args) {
BuyTickNum buyTickNum = new BuyTickNum();//需要锁的对象是变化的量
new Thread(buyTickNum,"小明").start();
new Thread(buyTickNum,"黄牛").start();
new Thread(buyTickNum,"小红").start();
}
}
class BuyTickNum implements Runnable{
private int ticketNums = 10 ;
boolean flag = true ;
@Override
public void run() {
//买票
while(flag){
buy();
}
}
//加上synchronized关键字 同步方法
private synchronized void buy(){
//判断是否有票
if(ticketNums <= 0){
flag = false ;
return ;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//抢票
System.out.println(Thread.currentThread().getName() + ":抢到了"+ticketNums--);
}
}
同步块 案例
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized(list){//加了同步块
list.add(Thread.currentThread().getName()) ;
}
}).start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());//输出结果不到10000 ,因为线程还没结束就打印输出了
}
4.1.4 死锁
- 互斥条件:一个资源只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
死锁 案例
/**
* Java线程:并发协作-死锁
*/
public class Test {
public static void main(String[] args) {
DeadlockRisk dead = new DeadlockRisk();
MyThread t1 = new MyThread(dead, 1, 2);
MyThread t2 = new MyThread(dead, 3, 4);
MyThread t3 = new MyThread(dead, 5, 6);
MyThread t4 = new MyThread(dead, 7, 8);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class MyThread extends Thread {
private DeadlockRisk dead;
private int a, b;
MyThread(DeadlockRisk dead, int a, int b) {
this.dead = dead;
this.a = a;
this.b = b;
}
@Override
public void run() {
dead.read();
dead.write(a, b);
}
}
class DeadlockRisk {
private static class Resource {
public int value;
}
private Resource resourceA = new Resource();
private Resource resourceB = new Resource();
public int read() {
synchronized (resourceA) {
System.out.println("read():" + Thread.currentThread().getName()
+ "获取了resourceA的锁!");
synchronized (resourceB) {
System.out.println("read():" + Thread.currentThread().getName()
+ "获取了resourceB的锁!");
return resourceB.value + resourceA.value;
}
}
}
public void write(int a, int b) {
synchronized (resourceB) {
System.out.println("write():" + Thread.currentThread().getName()
+ "获取了resourceA的锁!");
synchronized (resourceA) {
System.out.println("write():"
+ Thread.currentThread().getName() + "获取了resourceB的锁!");
resourceA.value = a;
resourceB.value = b;
}
}
}
}
4.1.5 Lock 锁(可重入锁)
- lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
- lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
- lock释放锁的过程:修改状态值,调整等待链表。
可重入锁使用方法
private final ReentrantLock lock = new ReentrantLock() ;
try{
lock.lock();
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
抢票 案例
public class demo{
public static void main(String[] args) {
BuyTickNum buyTickNum = new BuyTickNum();
new Thread(buyTickNum,"小明").start();
new Thread(buyTickNum,"黄牛").start();
new Thread(buyTickNum,"小红").start();
}
}
class BuyTickNum implements Runnable{
private int ticketNums = 10 ;
//锁
private final ReentrantLock lock = new ReentrantLock() ;
boolean flag = true ;
@Override
public void run() {
//买票
while(flag){
try{
lock.lock();//获取锁
buy();//不安全的方法
}finally {
lock.unlock();//释放锁
}
}
}
private void buy(){
//判断是否有票
if(ticketNums <= 0){
flag = false ;
return ;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//抢票
System.out.println(Thread.currentThread().getName() + ":抢到了"+ticketNums--);
}
}
4.1.6 synchronized与Lock锁对比
- Lock是显式锁(手动开启关闭)synchronized是隐式锁出了作用域自动四方
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并具有更好的扩展性
- 优先使用顺序
- Lock > 同步代码块(已经进入方法体,分配了相应的资源)> 同步方法(在方法体之外)
4.2 线程协作
4.2.1 线程协作的核心方法:wait/notify
- wait 和 notify 操作的是对象内置锁的等待队列
- wait 类方法用于阻塞当前线程,将当前线程挂载进 Wait Set 队列
- notify 类方法用于释放一个或多个处于等待队列中的线程
- 必须在获得对象内置锁的前提下(只能在 synchronized 修饰的代码块内部进行)才能阻塞和释放等待队列上的线程
作用与用法
- 等待线程
synchronized(lock){
while(flag){
lock.wait();
}
doAction();
}
- 唤醒线程:
synchronized(lock){
flag = false;
lock.notifyAll();
}
- 执行流程

等待线程在 `wait();` 阻塞时释放锁,唤醒线程在执行结束时释放锁
等待线程可能不止一条,被唤醒时不一定能获取锁
如果条件被其他获取锁的等待线程修改,那么后续获得锁的等待线程会继续等待
等待线程与唤醒线程必须使用同一个锁
`notify();` 只能随机唤醒一条线程
4.2.2 生产者消费者模式
并发协作
- 生产者仅仅在仓储未满时候生产,仓满则停止生产。
- 消费者仅仅在仓储有产品时候才能消费,仓空则等待。
- 当消费者发现仓储没产品可消费时候会通知生产者生产。
- 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
管程法 案例
//并发协作模型”生产者/消费者模式”->管程法(利用管道容器来处理)
public class TestPc {
public static void main(String[] args) {
SynContiner continer = new SynContiner() ;
new Thread(new Productor(continer) ).start();
new Thread(new Cunsumer(continer)).start();
}
}
//产品
class Chicken{
public int id ;
public Chicken(int id){
this.id = id ;
}
}
//生产者 只管生产
class Productor implements Runnable{
private SynContiner continer ;
public Productor(SynContiner continer ){
this.continer = continer ;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
continer.Push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者 只管消费
class Cunsumer implements Runnable{
private SynContiner continer ;
public Cunsumer(SynContiner continer){
this.continer = continer ;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("消费了"+continer.Pop().id+"只鸡");
}
}
}
//管道容器缓冲区
class SynContiner{
//容器容量
Chicken[] chickens = new Chicken[10] ;
//容器计数器
int count = 0;
//生产者 生产鸡
public synchronized void Push(Chicken chicken){
//如果容器满了,需要等待生产者消费
if(count == chickens.length-1){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果容器没满->丢产品进去
chickens[count] = chicken;
count++ ;
//可以通知消费者消费了
this.notifyAll();
}
//消费者 消费鸡(返回消费了的那只鸡)
public synchronized Chicken Pop(){
if(count == 0){
//没有鸡了,等生产者生产
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count-- ;
Chicken chicken = chickens[count];
//吃完了通知
this.notifyAll();
return chicken;
}
}
信号灯法 案例
/*
*协作模型:生产者消费者实现方式二:信号灯法
* 借助标志位
*/
public class CooperationTest {
public static void main(String[] args) {
Tv tv =new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者 演员
class Player extends Thread{
Tv tv;
public Player(Tv tv) {
this.tv = tv;
}
public void run() {
for(int i=0;i<20;i++) {
if(i%2==0) {
this.tv.play("奇葩说");
}else {
this.tv.play("太污了,喝瓶立白洗洗嘴");
}
}
}
}
//消费者 观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv) {
this.tv = tv;
}
public void run() {
for(int i=0;i<20;i++) {
tv.watch();
}
}
}
//同一个资源 电视
class Tv{
String voice;
//信号灯
//T 表示演员表演 观众等待
//F 表示观众观看 演员等待
boolean flag = true;
//表演
public synchronized void play(String voice) {
//演员等待
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//表演
System.out.println("表演了:"+voice);
this.voice = voice;
//唤醒
this.notifyAll();
//切换标志
this.flag =!this.flag;
}
//观看
public synchronized void watch() {
//观众等待
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观看
System.out.println("听到了:"+voice);
//唤醒
this.notifyAll();
//切换标志
this.flag =!this.flag;
}
}
4.3 线程池
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 方便线程管理(大小,数量,持续时间)
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//ExecutorService:真正的线程池接口
//newFixedThreadPool 参数:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10) ;
//执行:void excute(Runnable command)
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭线程
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--启动");
}
}

浙公网安备 33010602011771号