Java核心知识---初识线程

Posted 一位懒得写博客的小学生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java核心知识---初识线程相关的知识,希望对你有一定的参考价值。

最早的并发编程是多进程并发编程。
多个进程不能共享资源。

目录

线程(Thread)

  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。一个进程内的线程之间是可以共享资源的;
  • 一个进程内最少包含一个线程,即主线程;
  • 线程必须依附进程才能存活。
  • 进程不可以共享资源,而线程可以。
线程可以共享的资源
打开的文件
内存(对象)
线程不可共享的资源
线程栈信息
优先级

注意:当线程的数量达到某个合适的值是最好的,如果太多的线程,就会出现线程之间的争抢和CPU的过度调度问题了,而CPU调度是需要消耗系统资源的。

合适线程数量

  • 具体的应用场景
  • 密集计算的 CPU 任务、IO(文件读写)型任务。
  • 当使用的场景是计算行任务时,线程的数量等于CPU的数量是最好的。

进程 ->线程(轻量级的进程) -> 协程(轻量级的线程)golang(谷歌)

线程的创建方式

  1. 继承 Tread 类是实现线程的创建线程(2中方法);
  2. 实现 Runnable 接口的方法(4中方法);
  3. 实现 Callable 接口(1种方法)。

创建线程的方式.

线程休眠

//方法一:
Thread.sleep(1000);//休眠一秒

//方法二:
TimeUnit.SECINDS.sleep(1);//休眠一秒

//方法三:
Thread.sleep(TimeUnit.SECINDS.toMillis(1));

使用线程打印"AABBCCDD"

public class ThreadMain 
    public static void main(String[] args) 
        Runnable runnable = new Runnable() 
            @Override
            public void run() 
                String data = "ABCD";
                for (char c : data.toCharArray()) 
                    System.out.print(c);
                    //使用休眠的额方式等待打印
                    try 
                        Thread.sleep(1);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
            
        ;

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
    


//执行结果
AABBCCDD

线程优先级

新创建的线程默认的优先级为5.
优先级的取值是1~10,这个值越大那么它的(执行)权重就越高。
优先级越高执行的优先级也越高,执行权也就越大,但是 CPU 的调度是很复杂的,所以不会严格的按照优先级的排序去执行,但总体来看还是优先级越高执行的权重也越大。
代码

public class ThreadMain2 
    public static void main(String[] args) 
        // 定义方法
        Runnable runnable = new Runnable() 
            @Override
            public void run() 
                System.out.println("线程名:" +
                        Thread.currentThread().getName());
            
        ;
        for (int i = 0; i < 10; i++) 
            Thread t1 = new Thread(runnable, "张三");
            Thread t2 = new Thread(runnable, "李四");
            Thread t3 = new Thread(runnable, "王五");
            t1.setPriority(1); // 优先级最小
//            t2.setPriority(Thread.MIN_PRIORITY); // 优先级最小
            t3.setPriority(Thread.MAX_PRIORITY); // 最高的优先级
            t1.start();
            t2.start();
            t3.start();
        

    


//执行结果
线程名:张三
线程名:李四
线程名:王五
线程名:王五
线程名:王五
线程名:李四
线程名:张三
线程名:王五
线程名:李四
线程名:王五
线程名:王五
线程名:李四
线程名:张三
线程名:李四
线程名:王五
线程名:王五
线程名:张三
线程名:张三
线程名:张三
线程名:李四
线程名:张三
线程名:李四
线程名:李四
线程名:王五
线程名:李四
线程名:李四
线程名:王五
线程名:张三
线程名:张三
线程名:张三

Process finished with exit code 0

线程分组(ThreadGroup)

可以将一类线程归为一组,并且进行信息的打印,查看一组线程的具体行为。

public class ThreadMain3 
    public static void main(String[] args) 
        // 定义任务
        Runnable runnable = new Runnable() 
            @Override
            public void run() 
                System.out.println("开始起跑:" +
                        Thread.currentThread().getName());
                try 
                    int num = 1 + new Random().nextInt(5);
                    Thread.sleep(num * 1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println("到终点了:" +
                        Thread.currentThread().getName());
            
        ;
        // 定义分组
        ThreadGroup group = new ThreadGroup("百米赛跑一组");
        // 创建运动员
        Thread t1 = new Thread(group, runnable, "张三");
        Thread t2 = new Thread(group, runnable, "李四");
        t1.start();
        t2.start();
        // 打印线程分组的详情
        group.list();
        // 等待所有选手到达终点
        while (group.activeCount() != 0) 
        
        // 宣布成绩
        System.out.println("宣布成绩");

    



//执行结果
java.lang.ThreadGroup[name=百米赛跑一组,maxpri=10]
    开始起跑:张三
Thread[张三,5,百米赛跑一组]
    开始起跑:李四
Thread[李四,5,百米赛跑一组]
到终点了:张三
到终点了:李四
宣布成绩

Process finished with exit code 0

线程分类

  1. 后台线程(守护线程);
  2. 用户线程(默认线程);

守护线程是用来服务用户线程的,用户线程就是上帝,守护线程就是服务员。
进程退出:没有用户线程运行的时候,进程就会结束。

守护线程使用场景:Java 垃圾回收器。

public class ThreadMain4 
    public static void main(String[] args) 
        Thread t1 = new Thread(() -> 
            // 在守护线程的内部创建的线程
            Thread t2 = new Thread(() -> 
            );
            System.out.println("t2 是:" +
                    (t2.isDaemon() == true ? "守护线程" : "用户线程"));
        );
        // 设置为守护线程
        t1.setDaemon(false);
        t1.start();
        System.out.println("t1 是否为守护线程:" + t1.isDaemon());

    


//执行结果
t1 是否为守护线程:false
t2 是:用户线程
注意事项
守护线程设置必须调用 start();
如果设置守护线程在开始线程之后,那么程序就会报错,并且设置的守护线程值就不能生效。
在守护线程里面创建的线程,默认情况下全部都是守护线程。

Thread 几个常见属性

属性获取方法
IDgetId()
名称:getName()
状态:getState()
优先级:getPriority()
是否后台线程:isDaemon()
是否存活:isAlive()
是否被中断:isInterrupted()

代码

public class ThreadMain1 
    public static void main(String[] args) 
        Thread t1 = new Thread(() -> 
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        , "张三");
        System.out.println("线程状态前:" + t1.getState());
        t1.start();
        System.out.println("线程状态后:" + t1.getState());
        System.out.println("线程ID:" + t1.getId());
        System.out.println("线程的名称:" + t1.getName());
        System.out.println("线程优先级:" + t1.getPriority());
        System.out.println("线程是否为后台线程:" + t1.isDaemon());
        System.out.println("线程是否存活" + t1.isAlive());
        System.out.println("线程是否被中断" + t1.isInterrupted());

    


//执行结果
线程状态前:NEW
线程状态后:RUNNABLE
线程ID12
线程的名称:张三
线程优先级:5
线程是否为后台线程:false
线程是否存活true
线程是否被中断false

线程中断

  1. 使用全局自定义的变量来终止线程;
  2. 使用线程提供的方法 interrupt 来终止线程;
  3. 使用线程提供的方法 stop 来终止线程。

注意:使用全局自定义标识终止线程执行的时候比较温柔,当他收到终止指令后,会把当前手头的任务执行完再终止;使用 interrupt 在收到终止指令之后会立马结束执行。

两种方法

public class Thread1 
    public static void main(String[] args) 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                for (int i = 0; i < 10; i++) 
                    System.out.println(Thread.interrupted());
                
                    
        t1.start();
        // 终止线程
        t1.interrupt();
    


//执行结果
true
false
false
false
false
false
false
false
false
false

Process finished with exit code 0

public class Thread1 
    public static void main(String[] args) 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                for (int i = 0; i < 10; i++) 
                    System.out.println(Thread.currentThread().isInterrupted());
                
            
        );
        t1.start();
        // 终止线程
        t1.interrupt();
    


//执行结果
true
true
true
true
true
true
true
true
true
true

Process finished with exit code 0

interrupted()与 isInterrupted()
interrupted 是全局的方法,判断完之后会重置线程的状态。
isInterrupted() 将线程中的终止状态从默认的 false 改为 true ;

等待线程 join()

方法说明
public void join():等待线程结束
public void join(long millis):等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos):同理,但可以更高精度

线程状态

public class Thread1 
    public static void main(String[] args) 
        for (Thread.State state : Thread.State.values()) 
            System.out.println(state);
        
    


//执行结果
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
  • NEW 新建状态,没有调用start()之前的状态
  • RUNNABLE 运行状态(Running 执行中);
  • BLOCKED 阻塞状态;
  • WAITING 等待,没有明确的等待时间
  • TIMED_WAITING 超时等待时间,有明确的等待时间;
  • TERMINATED 终止状态

线程非安全

多线程执行中,程序的执行结果和预期不相符就叫做线程不安全。

  1. CPU 抢占执行(源头);
  2. 非原子性;
  3. 编译器优化(指令重排序)(代码优化);
  4. 内存不可见;
  5. 多个线程同时修改了同一个变量。

编译器优化在单线程下没问题,可以提升程序的执行效率,但是在多线程的情况下就会出现混乱,导致线程不安全情况。

start() VS run()

  • Start():用来启动线程,真正实现了多线程;
  • Run():当作普通方法来调用。

代码对比

Start():

public class ThreadDemo5 
    public static final boolean flag=false;
    public static void main(String[] args) 
        Thread t1=new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println(Thread.currentThread().getName());
            
        );
        t1.start();
    



//执行结果
Thread-0

Run()

public class ThreadDemo5 
    public static final boolean flag=false;
    public static void main(String[] args) 
        Thread t1=new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println(Thread.currentThread().getName());
            
        );
        t1.run();
    



//执行结果
main

线程的工作方式:

  1. 先在自己的工作内存中找变量;
  2. 去主内存里面找变量

Volatile 轻量级解决“线程安全” 的方案;

作用:
进制指令重排序;
解决线程可见性问题(实际原理:当操作完变量之后,强制删除掉线程工作内存中的此变量);
(注意)不能解决原子性问题。

线程安全:

  1. CPU 抢占调度(不能);
  2. 每个线程操作自己的变量(可能性),不通用,修改难度大;
  3. 在关键代码让所有的CPU排队执行,加锁。

锁操作的关键步骤:

  • 尝试获取锁(如果成功拿到加锁,拿不到排队);
  • 释放锁。

JAVA 中解决线程安全的问题的方案:

  • synchronized 加锁和释放锁【JVM层面的解决方案,自动加锁和释放锁】;
  • Lock 手动锁(程序员自己加锁和释放锁)

Synchronized

  • 在进行加锁操作时,同一组业务必须是同一把锁对象。
  • 锁机制是非公平锁。(公平锁可以按顺序进行执行,非公平锁执行的效率更高)。

在 JAVA 中所有锁默认的策略都是非公平锁

使用场景:

  • 修饰代码块(加锁对象可以自定义);
  • 修饰静态方法(加锁对象是当前类对象);
  • 可以用来修饰普通方法(加锁对象是当前类的实例)。

原理:

  • 操作:互斥锁 mutex;
  • JVM :帮忙实现的监视器锁的加锁和释放锁的操作;
  • JAVA :(锁存在的地方:变量的对象头)。

JDK 6 对 synchronized 优化

lock.lock();

  1. 如果将lock()方法放在try里面,那么当try里面的代码出现异常之后,那么就会执行finally里面释放锁的代码,但这个时候加锁还没成功,就去释放锁。
  2. 执行finall里面释放锁的代码时就会报错(线程状态异常),释放锁的异常会覆盖掉业务代码的异常报错,从而增加了排除错误成本。

Lock 默认的锁策略是非公平锁,但是Lock可以显示的声明为公平锁。

Volatile 和synchronized 有什么区别

  1. volatile 可以解决内存可见性问题和禁止指令重排序,但volatile 不能解决原子性问题;synchronized 是用来保证线程安全,也就是synchronized 可以解决任何关于线程安全的问题(关键代码排队执行,始终只有一个线程会执行加锁操作;原子性问题);

synchronized 和Lock 之间的区别

  1. synchronized 既可以修饰代码快,又可以修饰静态方法与普通方法;而Lock智能修饰代码块;
  2. synchronized只有非公平的锁策略,而Lock既可以公平锁也可以非公平锁(ReentranLock默认是fgp锁,也可以通过构造函数设置 true 声明它为公平锁);
  3. ReentrantLock 更加的灵活(比如 tryLock);
  4. synchronized 是自动加锁和释放锁的,而 ReentrantLock 需要自己手动加锁和释放锁。

死锁

定义:在多线程中(两个或两个以上的线程),因为资源抢占而造成线程无限等待 的问题。

代码示例

    public static void main(String[] args) 
        Object lockA = new Object();
        Object lockB = new Object();

        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized (lockA) 
                    System.out.println("线程A");
                    try 
                        Thread.sleep(1000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    System.out.println("等待B");

                    synchronized (lockB) 
                        System.out.println("线程1得到了锁B");
                    
                
            
        ,"t1");

        t1.start();

        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized (lockB以上是关于Java核心知识---初识线程的主要内容,如果未能解决你的问题,请参考以下文章

初识JVM--java虚拟机的基本知识

java 多线程核心知识

java核心知识点 --- 线程池ThreadPool

深入理解 Java 多线程核心知识:跳槽面试必备

深入理解 Java 多线程核心知识:跳槽面试必备

java核心-多线程-线程类基础知识