Java多线程

Java多线程

线程与进程

进程是所有线程的集合,每一个线程是进程中的一条执行路径。

多线程应用场景

多线程提高程序效率。

如迅雷多线程下载、分批发送短信等。

多线程创建方式

  • 继承Thread类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Thread01 extends Thread {

private static final Logger LOGGER = LoggerFactory.getLogger(Thread01.class);

/**
* 功能描述: 线程执行的任务
* @return
* @author zhuweitung
* @date 2020/9/2
*/
@Override
public void run() {
for (int i = 0; i < 20; i++) {
LOGGER.info("{}", i);
}
}
}

使用方式:

1
new Thread01().start();
  • 实现Runable接口
1
2
3
4
5
6
7
8
9
10
public class Thread02 implements Runnable {

private static final Logger LOGGER = LoggerFactory.getLogger(Thread02.class);

public void run() {
for (int i = 0; i < 20; i++) {
LOGGER.info("{}", i);
}
}
}

使用方式:

1
new Thread(new Thread02()).start();
  • 继承Thread类好还是实现Runable接口好?

接口好,接口可以实现多个,而继承只能继承一个

  • 使用匿名内部类方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 功能描述: 匿名内部类方式
* @return
* @author zhuweitung
* @date 2020/9/3
*/
private static void startThread03() {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 20; i++) {
LOGGER.info("{}", i);
}
}
}).start();
}

注:在run方法中不能抛出异常

线程常用API

方法 参数 说明 示例
sleep 毫秒 让当前线程从运行状态变为休眠状态,到时间再变为运行状态。不能释放锁,多线程之间实现同步 Thread.sleep(1000);
getId 获取当前线程id
getName 获取当前线程名称,在实现runable接口的线程类中使用Thread.currentThread()来调用
join 让其他线程等待,只有当前线程执行完毕才会释放资格

线程生命周期

线程从创建、运行到结束总是处于五个状态之中:新建状态、就绪状态、阻塞状态、运行状态、死亡状态

新建状态:线程实例化但没有调用start()状态;

就绪状态:线程调用了start()方法,但没有得到cpu分配的资源;

运行状态:线程在执行run()方法;

死亡状态:run()方法运行完;一个未捕获的异常终止了run方法导致线程猝死;

阻塞状态:调用sleep()方法;线程调用一个在I/O上被阻塞的操作;线程试图得到一个锁,而该锁正被其他线程持有;线程在等待某个触发条件;

线程安全

  • 为什么会有线程安全问题?

当多个线程同时写一个共享全局变量时,可能会发生数据冲突问题,也就是线程安全问题。

  • 线程安全问题怎么解决?

    • synchronized

      • 同步代码块:将可能发生线程安全问题的代码,给包括起来(有问题的写操作)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        /**
        * @description 同步代码块
        * @param
        * @return void
        * @author zhuweitung
        * @date 2020/9/5
        */
        private void synchronizedCodeBlock() {
        while (count > 0) {
        try {
        Thread.sleep(20);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        //synchronized包裹的代码一次只能有一个线程执行
        synchronized (lock) {
        if (count > 0) {
        log.info("threadName={},count={}", Thread.currentThread().getName(), count);
        count--;
        }
        }
        }
        }
      • 同步函数:synchronized 关键字修饰在方法上,相当于使用this锁的同步代码块

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        /**
        * @description 同步函数
        * synchronized 关键字修饰在方法上
        * @param
        * @return void
        * @author zhuweitung
        * @date 2020/9/5
        */
        private synchronized void synchronizedFunction() {
        while (count > 0) {
        try {
        Thread.sleep(20);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        log.info("threadName={},count={}", Thread.currentThread().getName(), count);
        count--;
        }
        }
        /**
        * @description 同步函数相当于使用this锁的同步代码块
        * @param
        * @return void
        * @author zhuweitung
        * @date 2020/9/5
        */
        private void synchronizedFunction2() {
        while (count > 0) {
        try {
        Thread.sleep(20);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        //synchronized包裹的代码一次只能有一个线程执行
        synchronized (this) {
        if (count > 0) {
        log.info("threadName={},count={}", Thread.currentThread().getName(), count);
        count--;
        }
        }
        }
        }
      • 静态同步函数:static、synchronized关键字修饰在方法上,相当于使用当前线程类.class锁的同步代码块

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        /**
        * @description 静态同步函数
        * static、synchronized关键字修饰在方法上
        * @param
        * @return void
        * @author zhuweitung
        * @date 2020/9/5
        */
        private static synchronized void staticSynchronizedFunction() {
        while (staticCount > 0) {
        try {
        Thread.sleep(20);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        log.info("threadName={},count={}", Thread.currentThread().getName(), staticCount);
        staticCount--;
        }
        }
        /**
        * @description 静态同步函数相当于使用当前线程类.class锁的同步代码块
        * @param
        * @return void
        * @author zhuweitung
        * @date 2020/9/5
        */
        private static void staticSynchronizedFunction2() {
        while (staticCount > 0) {
        try {
        Thread.sleep(20);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        //synchronized包裹的代码一次只能有一个线程执行
        synchronized (Thread04.class) {
        if (staticCount > 0) {
        log.info("threadName={},count={}", Thread.currentThread().getName(), staticCount);
        staticCount--;
        }
        }
        }
        }
      • 适合场景:单个jvm环境下,集群中不可行

    • lock

  • 多线程死锁:同步中嵌套同步,导致锁无法释放

线程间的通讯

  • wait:可以让当前线程由运行状态变为阻塞状态,可以释放锁,与synchronized一起使用,且使用同一个锁
  • notify:唤醒另一个线程(由阻塞状态变为运行状态 ),一般和wait一起使用
  • Lock接口:配合Condition接口实现等待/通知模式,常用lock()、unlock()方法,常用实现类ReentrantLock
  • Condition接口:依赖于Lock对象,常用await()、signal()方法

线程的停止方法:

  • 使用退出标志,是线程正常退出,也就是当run方法执行完成后线程终止
  • 使用stop()方法强行终止线程(不建议使用stop()方法,因为没有异常处理,可能会出现不可预料的结果)
  • 使用interrupt()方法中断线程

守护线程

线程分为两类:用户线程(前台线程)和守护线程(后台线程);

守护线程(deamon)是为我们创建的用户线程提供服务的线程,如jvm的gc等;

  • 当用户线程运行结束的时候,daemon线程将会自动退出

线程的三大特性

原子性

即一个或多个操作,要么全部执行并执行的过程不会被任何元素打断,要么就都不执行;

使用同步和lock来确保这个特性。

原子性其实就是保证数据一致,线程安全一部分。

可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性

程序执行的顺序按照代码的先后顺序执行。

注:线程安全包括两个方面:可见性,原子性

Java内存模型

共享内存模型就是Java内存模型(简称JMM,JMM决定一个线程对共享变量的写入时,能对另一个线程可见)。线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

注:与Java内存结构(方法区,堆,栈等)要区分概念

Volatile

作用:变量在多个线程之间可见,强制线程每次读取volatile关键字修饰的变量时都去主内存中取值。

注:不能解决原子性问题,在高并发下i++无法保证原子性,往往会出现问题,所以引入了AtomicInteger类。synchronized不仅保证可见性,而且保证原子性

ThreadLocal

ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,线程之间互不影响。

ThreadLocal底层用Map实现,key:当前线程,value:值。

强引用、软引用、弱引用、虚引用

Java中4种引用的级别由高到低依次为:

强引用 > 软引用 > 弱引用 > 虚引用

他们之间在垃圾回收时的区别:

各引用间的区别:

线程池

为什么使用线程池?

因为启动或停止一个线程非常耗资源,将线程交给线程池来管理可以节约内存。

Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:

  • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收线程,则新建线程
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
  • newScheduledThreadPool 创建一个定长的线程池。支持定时及周期性任务执行
  • newSingleThreadExecutor 创建一个单线程化线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行

Java多线程
https://blog.kedr.cc/posts/2730732825/
作者
zhuweitung
发布于
2020年9月2日
许可协议