Java多线程与并发复习笔记

java线程Thread的使用方法

  • 一、线程的创建?
  • 二、线程常用方法
    • 1.Thread.currentThread()
    • 2.Thread.sleep(毫秒数)
    • 3.interupt()
    • 4.setDaemon(boolean)
  • 三、线程生命周期
  • 四、线程运行时问题和风险
    • 1.线程安全
    • 2.线程活性问题
    • 3.上下文切换
    • 4.可靠性
  • 五、线程的安全问题
    • 1.线程的原子性
    • 2.线程的可见性
    • 3.线程的有序性
      • (1)重排序

一、线程的创建?

有两种方法,一种是继承Thread,一种是实现Runnable接口

package com.day02;

/**
 * @author: Xu TaoSong
 * @date: 2021/2/2 20:30
 * @description: TODO
 * @modifiedBy:
 */
public class CreateThread extends Thread{
     
    public static void main(String[] args) {
     
/*        CreateThread c = new CreateThread();
        c.start();*/
        Thread c1 = new Thread(new interfaceThread());
        c1.start();
        for (int i = 0;i<99;i++){
     
            System.out.println("main:   "+i);
        }
    }

    @Override
    public void run() {
     
        for(int i = 0;i<99;i++){
     
            System.out.println("c   "+i);
        }
    }
}
class interfaceThread implements Runnable{
     
    @Override
    public void run() {
     
       for(int i = 0;i<99;i++){
     
           System.out.println("c1   "+i);
       }
    }
}

二、线程常用方法

1.Thread.currentThread()

可以获取当前线程

2.Thread.sleep(毫秒数)

让当前线程休眠指定的毫秒数

3.interupt()

中断线程
在当前线程打印停止标志,并不是真正的中断
可以通过(isInterupt方法)来判断是否有中断标志

4.setDaemon(boolean)

设置守护线程

三、线程生命周期

  • NEW:新建状态,调用strat()方法之前
  • Runnable: 符合状态,包含Ready和Running两个状态
    • Ready : 该线程可以被线程调度器调度,使其进行running状态
    • Running:该线程正在执行
  • Blocked:线程阻塞状态,当线程获得了它所需要的资源,它就会从阻塞状态变成running状态
  • Waiting:等待状态,线程执行了wait()方法,或者join()方法,会将线程转换为WAITING状态。
  • TIMED_WATING:与waiting状态类似,区别在,该状态的线程不会无限等待,
  • Terminate:线程结束

四、线程运行时问题和风险

1.线程安全

可能产生数据的一致性问题

2.线程活性问题

  • 死锁(DEADLOCK)
  • 锁死(LOCKOUT)
  • 活锁(LIVELOCK)
  • 饥饿(STRAVATION)

3.上下文切换

切换过程中会耗费资源

4.可靠性

可能由于一个线程导致程序奔溃,那么其他线程也不能执行了。

五、线程的安全问题

1.线程的原子性

原子(Atomic)是不可分割的意思

  • 访问共享变量操作,从其他线程来看,操作要么执行完毕,要么还未执行
  • 访问同组共享变量的原子操作不能并发(不能交错)

java中具有两种操作实现原子性

  • 一种是使用锁,锁具有排他性,保证共享变量一个时间只能被一个变量访问
  • 另一种是利用处理器的CAS(Compare and Swap),直接在硬件层次上实现
package com.day03;

/**
 * @author: Xu TaoSong
 * @date: 2021/2/3 16:30
 * @description: TODO
 * @modifiedBy:
 */
public class TestThreadSafe {
     
    public static void main(String[] args) {
     
        c1 c = new c1();
        for(int i = 0;i<2;i++){
     
            new Thread(new Runnable() {
     
                @Override
                public void run() {
     
                    while (true){
     
                        System.out.println(Thread.currentThread().getName()+"->"+c.getNum());
                        try {
     
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
     
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
    static class c1{
     
        int num;
        public int getNum(){
     
            return num++;
        }
    }
}

在这个代码中,num的数据没有被保证原子性,所以会造成数据一致性问题

在java中有一个AtomicInteger类来保证了数据的原子性,将num换成AtomicInteger后,可以保证了上面程序的原子性。

2.线程的可见性

在多线程中,一个线程对某个共享资源进行修改,后续的线程可能无法立即读取到这个修改后的结果。可见性就是:一个线程对其他共享变量进行更新后,后续线程可以访问到这个修改后的结果,称这个线程对共享变量的更新对其他线程可见。
反之则是不可见。

3.线程的有序性

在多核处理器的环境下,我们编写的顺序结构,这种操作执行的顺序可能是没有保障的:

  • 编译器可能会改变两个操作的先后顺序
  • 处理器也可能不会按照目标代码执行

(1)重排序

这种一个处理器上执行的多个操作,在其他处理器上来看,他的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序。
重排序:是内存访问有序操作的一种优化,可以再不影响单线程程序的正确的情况下提升程序的性能,但是,可能对多线程的正确性产生影响,既可能导致线程的安全问题。


内存操作顺序有关的几个概念:

  • 源代码顺序,就是源码中指定的内存访问顺序
  • 程序顺序,处理器上运行的目标代码所指定的内存访问顺序
  • 执行顺序,内存访问操作在处理器上的实际执行顺序
  • 感知顺序,给定处理器所感知的到的该处理器及其他处理器的内存访问操作的顺序。

可以把重排序分为指令重排序存储子系统重排序两种。

  • 指令重排序主要是由JIT编译器,处理器引起,指程序顺序与执行顺序不一致。
  • 存储子系统重排序是由于高速缓存,写缓冲器引起的,感知顺序与执行顺序不一致。

你可能感兴趣的:(java多线程与并发,java,多线程,并发编程)