• Java进阶(再论线程)——线程的4种创建方式 & 线程的生命周期 & 线程的3大特性 & 集合中的线程安全问题


    在这里插入图片描述

    前言

    多线程作为编程语言中的难点,虽然初级程序员可能很少遇到线程相关的开发任务,但是作为程序员,持续学习和保持对编程的热爱,要求我们对于线程也需要有一定的认识。

    本篇博客介绍Java中创建线程的4种方式,并进行了简单的对比;介绍了线程的生命周期,几个关键方法的作用;然后阐述了线程的三大特性,最后结合Java集合框架分析了线程安全的问题。

    其他关于Java线程的文章如下:

    引出


    1.线程创建的方式,继承Thread类,实现Runable接口,实现Callable接口,采用线程池;
    2.线程生命周期: join():运行结束再下一个, yield():暂时让出cpu的使用权,deamon():守护线程,最后结束,sleep():如果有锁,不会让出;
    3.线程3大特性,原子性,可见性,有序性;
    4.list集合中线程安全问题,hash算法问题;

    一、创建多线程的方式

    1、继承Thread类

    在这里插入图片描述

    在这里插入图片描述

    调用的流程:

    • 调用start()
    • 线程处于准备状态,一旦cup有闲的时间片,
    • 让线程调用run()方法

    在这里插入图片描述

    package com.tianju.threadLearn;
    
    public class ThreadA extends Thread{
    
        @Override
        public void run(){
            System.out.println("我是线程A:"+this.getName());
        }
    
        public static void main(String[] args) {
            ThreadA threadA = new ThreadA();
            threadA.setName("线程A");
    
            threadA.start(); // 我准备好了
    
            String name = Thread.currentThread().getName();
            System.out.println(name);
    
            // 获得当前线程的路径
            String path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
            System.out.println(path);
    
        }
    }
    
    
    • 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

    当前线程:Thread.currentThread()

    在这里插入图片描述

    2、实现Runable接口

    和继承Thread相比,这个用的更多,因为Java是单继承的,只能继承一个,而可以实现多个接口,所以更加灵活

    在这里插入图片描述

    package com.tianju.threadLearn;
    
    /**
     * 这个常用,因为java是单继承,如果继承了extends Thread;
     * 就不能继续继承了;
     */
    public class ThreadB implements Runnable{
    
        private String name;
        public ThreadB(String name){
            this.name = name;
        }
    
        @Override
        public void run() {
            for(int i =0;i<10;i++){
                System.out.println("当前线程:"+this.name);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        public static void main(String[] args) {
            new Thread(new ThreadB("B")).start();
        }
    }
    
    
    • 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

    3、实现Callable接口

    在这里插入图片描述

    package com.tianju.threadLearn;
    
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * Callable,可以有返回值
     */
    public class ThreadC implements Callable<Long> {
    
    
    
        @Override
        public Long call() throws Exception {
            long sum = 0;
            for (int i = 0; i < 500000; i++) {
                sum+=i;
            }
            return sum;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask<Long> threadC = new FutureTask<Long>(new ThreadC());
            Thread thread = new Thread(threadC);
            thread.start();
            Long aLong = threadC.get();
            System.out.println(aLong);
        }
    }
    
    • 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

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    4、线程池

    池化技术pool【常量池、数据连接池、线程池】

    在这里插入图片描述

    package com.tianju.threadLearn;
    
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    public class PoolDemo1 {
        public static void main(String[] args) {
            System.out.println("===============缓存线程池================");
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i=0;i<10;i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread());
                    }
                });
            }
            System.out.println("===============固定线程池================");
            ExecutorService executorService2 = Executors.newFixedThreadPool(1);
            for (int i=0;i<10;i++) {
                executorService2.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread());
                    }
                });
            }
        }
    }
    
    • 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

    二、线程的生命周期

    在这里插入图片描述

    join():运行结束再下一个

    在这里插入图片描述

    package com.tianju.threadLearn;
    
    public class ThreadA1 extends Thread{
    
        static int c = 0;
    
        @Override
        public void run(){
            for (int i = 0; i < 20; i++) {
    
                try {
                    System.out.println(this.getName()+": "+i);
                    sleep(300);
                    c++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadA1 a = new ThreadA1();
            a.setName("A");
    
            ThreadA1 b = new ThreadA1();
            b.setName("B");
            a.start();
            a.join(); // A运行结束,B才能开始运行
    
            b.start();
            b.join(); // 控制执行顺序
    
            System.out.println(Thread.currentThread().getName());
            System.out.println("c的结果为: "+c);
    
        }
    }
    
    • 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

    在这里插入图片描述

    yield():暂时让出cpu的使用权

    在这里插入图片描述

    package com.tianju.threadLearn;
    
    /**
     * yield,让出cpu的使用权
     */
    public class ThreadA2 extends Thread{
    
        @Override
        public void run(){
            for (int i = 0; i < 20; i++) {
    
                try {
                    if (i%2==0){
                        // 让出cpu的使用权,避免一个线程一直占有cpu,防止独占cpu
                        // 重新竞争
                        yield();
                    }
                    System.out.println(this.getName()+": "+i);
                    sleep(300);
    
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadA2 a = new ThreadA2();
            a.setName("A");
    
            ThreadA2 b = new ThreadA2();
            b.setName("B");
            a.start();
    
            b.start();
    
            System.out.println(Thread.currentThread().getName());
    
        }
    }
    
    • 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

    deamon():守护线程,最后结束

    在这里插入图片描述

    在这里插入图片描述

    package com.tianju.threadLearn;
    
    /**
     * 守护线程
     * 用户线程
     *
     */
    public class ThreadA3 extends Thread{
    
        @Override
        public void run(){
    
            while (true){
                System.out.println("我是守护线程.......");
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
    
            ThreadA3 a = new ThreadA3();
            a.setName("A");
            a.setDaemon(true); // 守护线程,用户线程main结束后,他就结束了
            a.start();
    
            for (int i =0;i<10;i++){
                System.out.println(Thread.currentThread()+":"+i);
                Thread.sleep(200);
            }
    
        }
    }
    
    • 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

    在这里插入图片描述

    sleep():如果有锁,不会让出

    在这里插入图片描述

    package com.tianju.threadLearn;
    
    public class ThreadSleep extends Thread{
    
    
    
        @Override
        public void run(){
    
            for (int i=0;i<10;i++){
                System.out.println("我是线程A:"+this.getName());
    
                try {
                    sleep(200); // 不会让出锁
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
    
        }
    
        public static void main(String[] args) {
            ThreadSleep threadA = new ThreadSleep();
            threadA.setName("线程A");
    
            threadA.start(); // 我准备好了
    
            String name = Thread.currentThread().getName();
            System.out.println(name);
    
        }
    }
    
    • 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

    三、线程的三大特性

    在这里插入图片描述

    原子性:AtomicInteger

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    package com.tianju.tx;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 解决原子性:AtomicInteger
     */
    public class AtomicDemo1 {
        static AtomicInteger x= new AtomicInteger();
        static class A extends Thread{
            @Override
            public void run() {
    
                for (int i = 0; i < 20; i++) {
                    x.incrementAndGet();
                    System.out.println(getName()+ "--x:"+x);
                    try {
                        sleep(200);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
    
                }
            }
        }
    
        public static void main(String[] args) {
            new A().start();
            new A().start();
            new A().start();
        }
    }
    
    
    • 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

    在这里插入图片描述

    CAS

    CAS(Compare And Swap)

    • 从主存拷贝到工作内存(线程)
    • 修改,和主存比较,如果如果和取时结果一致,刷新到主存中。

    ABA问题:

    如果红色线程拿到了主存中的3,进行加1,;

    蓝色线程也拿到了主存中的3,进行了加1,然后又减1;

    红色线程一直问主存里面是不是3,然后发现还是3,就把4写到主存里面;

    但是此时的3已经被蓝色线程动过了;

    解决方案是,加一个版本号,每次被操作,就让版本号加1

    在这里插入图片描述

    可见性:加volatile关键字

    在这里插入图片描述

    package com.tianju.view;
    
    /**
     * 线程之间不可见
     * volatile 用于解决可见性
     */
    public class VisibleDemo2 {
        volatile static boolean f = true; // 处理可见性,解决不了原子性
        static class A extends Thread{
    
            @Override
            public void run() {
                while (f){
    
                }
                System.out.println("A的f为:"+f);
            }
        }
        static class B extends Thread{
            @Override
            public void run() {
                f=false;
                System.out.println("B设置f为:"+f);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            new A().start();
            Thread.sleep(300);
            new B().start();
        }
    }
    
    
    • 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

    在这里插入图片描述

    有序性:引用有了,对象还没

    在这里插入图片描述

    假设: 编译器修改顺序 1 , 3, 2

    线程A执行到对象初始化阶段,还没有初始化;

    线程B先获得的对象的引用,然后调用对象,

    但是对象还没有初始化,因此会报错,对象初始化错误

    在这里插入图片描述

    四、集合中的线程安全问题

    1、List集合的问题

    在这里插入图片描述

    在这里插入图片描述

    package com.tianju.collection;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class ListDemo {
        static List<Integer> list = new ArrayList<>();
    
        // 有序性+原子性保证
        volatile static AtomicInteger index =new AtomicInteger(0);
        static class A extends Thread{
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    list.add(index.incrementAndGet());
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            A a1 = new A();
            A a2 = new A();
            A a3 = new A();
            a1.start();
            Thread.sleep(500);
            a2.start();
            a3.start();
            System.out.println(list);
        }
    }
    
    
    • 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

    用vector解决

    在这里插入图片描述

    package com.tianju.collection;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Vector;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class VectorDemo {
        static Vector<Integer> list = new Vector<>();
    
        // 有序性+原子性保证
        volatile static AtomicInteger index =new AtomicInteger(0);
        static class A extends Thread{
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    list.add(index.incrementAndGet());
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            A a1 = new A();
            A a2 = new A();
            A a3 = new A();
            a1.start();
            a1.join(); // A运行结束,B才能开始运行
            a2.start();
            a2.join();
            a3.start();
            a3.join();
            System.out.println(list);
        }
    }
    
    
    • 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

    用Collections.synchronizedList

    package com.tianju.collection;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Vector;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class CollectionDemo {
        static List<Integer> ls = new ArrayList<>();
        static List<Integer> list = Collections.synchronizedList(ls);
    
        // 有序性+原子性保证
        volatile static AtomicInteger index =new AtomicInteger(0);
        static class A extends Thread{
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    list.add(index.incrementAndGet());
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            A a1 = new A();
            A a2 = new A();
            A a3 = new A();
            a1.start();
            a1.join(); // A运行结束,B才能开始运行
            a2.start();
            a2.join();
            a3.start();
            a3.join();
            System.out.println(list);
        }
    }
    
    
    • 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

    在这里插入图片描述

    2、表格总结

    序号线程不安全线程安全
    1ArrayListVector/ Collections.synchronizedList(list)
    2HashMapConcurentHashMap/HashTable
    3HashSetCollections.synchronizedSet()

    Hashset的底层是HashMap

    在这里插入图片描述

    3、关于hash算法

    比如一开始有蓝色点,经过hash算法之后,得到结果3,然后放到相应的位置;

    然后又来了一个红色的点,经过hash算法之后,也是3,此时就顺着蓝色的位置去找;

    之前是采用头插法,就是新来的放到蓝色的之前,在1.8之后改成尾插法,放到蓝色的后面;

    如果又有新来的,就继续往后面加,此时出现了不利的情况,就是越来越多成了一个很长的链表;

    所以就采用了树的结果,这样提高的查找的效率;在1.8中采用的红黑树;

    在这里插入图片描述


    总结

    1.线程创建的方式,继承Thread类,实现Runable接口,实现Callable接口,采用线程池;
    2.线程生命周期: join():运行结束再下一个, yield():暂时让出cpu的使用权,deamon():守护线程,最后结束,sleep():如果有锁,不会让出;
    3.线程3大特性,原子性,可见性,有序性;
    4.list集合中线程安全问题,hash算法问题;

  • 相关阅读:
    docker原理和基本概念
    正则表达式语法解析
    《相似度对比模型训练及在AidLux上部署应用》--实现印章相似度比较
    数据库工程师的工作职责(合集)
    【Java-----IO流(三)之缓冲流详解】
    C# 支付宝小程序 ---小程序支付
    为什么选择推荐计划?不能错过的这八大好处
    字符串拼接你真的啥都知道了吗
    log4j设置日志的时区
    【随想】每日两题Day.2
  • 原文地址:https://blog.csdn.net/Pireley/article/details/134268456