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);

    }
}

当前线程: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();
    }
}

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);
    }
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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());
                }
            });
        }
    }
}

二、线程的生命周期

在这里插入图片描述

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);

    }
}

在这里插入图片描述

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());

    }
}

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);
        }

    }
}

在这里插入图片描述

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);

    }
}

三、线程的三大特性

在这里插入图片描述

原子性: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();
    }
}

在这里插入图片描述

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 , 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);
    }
}

用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);
    }
}

用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);
    }
}

在这里插入图片描述

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算法问题;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arya's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值