Java多线程和并发到线程池

 

 

一、进程与线程

进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。

线程:是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

虽然系统是把资源分给进程,但是CPU很特殊,是被分配到线程级(指代内核级线程)的,此时进程不作为调度的基本单位,所以线程是CPU分配的基本单位。

二者关系:

在JVM中,一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。

 程序计数器:是一块内存区域,用来记录线程当前要执行的指令地址 。

:用于存储该线程的局部变量,这些局部变量是该线程私有的,除此之外还用来存放线程的调用栈祯。

:是一个进程中最大的一块内存,堆是被进程中的所有线程共享的。

方法区:则用来存放 NM 加载的类、常量及静态变量等信息,也是线程共享的 。

二者区别:

进程:有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。

线程:是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。

二、并发与并行

并发:是指同一个时间段内多个任务同时都在执行,并且都没有执行结束。并发任务强调在一个时间段内同时执行,而一个时间段由多个单位时间累积而成,所以说并发的多个任务在单位时间内不一定同时在执行 。

并行:是说在单位时间内多个任务同时在执行 。

在多线程编程实践中,线程的个数往往多于CPU的个数,所以一般都称多线程并发编程而不是多线程并行编程

并发过程中常见的问题:

1、线程安全问题:

多个线程同时操作共享变量1时,会出现线程1更新共享变量1的值,但是其他线程获取到的是共享变量没有被更新之前的值。就会导致数据不准确问题,读脏数据。

2、共享内存不可见性问题

Java 内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫作工作内存,线程读写变量时操作的是自己工作内存中的变量 。

     ( 实际工作的java内存模型)

上图中所示是一个双核 CPU 系统架构,每个核有自己的控制器和运算器,其中控制器包含一组寄存器和操作控制器,运算器执行算术逻辅运算。CPU的每个核都有自己的一级缓存,在有些架构里面还有一个所有CPU都共享的二级缓存。 那么Java内存模型里面的工作内存,就对应这里的 Ll或者 L2 缓存或者 CPU 的寄存器

 

1、线程A首先获取共享变量X的值,由于两级Cache都没有命中,所以加载主内存中X的值,假如为0。然后把X=0的值缓存到两级缓存,线程A修改X的值为1,然后将其写入两级Cache,并且刷新到主内存。线程A操作完毕后,线程A所在的CPU的两级Cache内和主内存里面的X的值都是l。

2、线程B获取X的值,首先一级缓存没有命中,然后看二级缓存,二级缓存命中了,所以返回X=1;到这里一切都是正常的,因为这时候主内存中也是X=l。然后线程B修改X的值为2,并将其存放到线程2所在的一级Cache和共享二级Cache中,最后更新主内存中X的值为2,到这里一切都是好的。

3、线程A这次又需要修改X的值,获取时一级缓存命中,并且X=l这里问题就出现了,明明线程B已经把X的值修改为2,为何线程A获取的还是l呢?这就是共享变量的内存不可见问题,也就是线程B写入的值对线程A不可见。

 

synchronized 的内存语义:synchronized关键字:能够保证同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

这个内存语义就可以解决共享变量内存可见性问题。进入synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。会造成上下文切换的开销,独占锁,降低并发性

Volatile的理解:

该关键字可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时-,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。volatile的内存语义和synchronized有相似之处,具体来说就是,当线程写入了volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取volatile变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。不能保证原子性。

三、创建线程

1、继承Thread类

重写run方法:使用继承方式的好处是,在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.currentThread()方法;不好的地方是Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码

public class ThreadRuning extends Thread{

    public ThreadRuning(String name){  
//重写构造,可以对线程添加名字
        super(name);
    }
    @Override
    public void run() {
        while(true){
            System.out.println("good time");
//在run方法里,this代表当前线程
            System.out.println(this);
        }
    }
    public static void main(String[] args){
        ThreadRuning threadRuning = new ThreadRuning("1111");
        threadRuning.start();
    }
}

2、实现Runable接口

实现run方法:解决继承Thread的缺点,没有返回值

public class RunableTest implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("good time");
        }
    }
    public static void main(String[] args) {
        RunableTest runableTest1 = new RunableTest();
        RunableTest runableTest2 = new RunableTest();
        new Thread(runableTest1).start();
        new Thread(runableTest1).start();
        new Thread(runableTest2).start();
    }
}

3、实现Callable接口 可以有返回值

public class CallTest implements Callable {
    @Override
    public Object call() throws Exception {
        return "hello world";
    }
 
    public static void main(String[] args){
        FutureTask<String> futureTask = new FutureTask<String>(new CallTest());
        new Thread(futureTask).start();
        try {
            String result = futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

 使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Callable方式可以。

四、Thread类详解

线程特性:

1、线程能被标记为守护线程,也可以是用户线程

2、每个线程均分配一个name,默认为(Thread-自增数字)的组合

3、每个线程都有优先级.高优先级线程优先于低优先级线程执行. 1-10,默认为5

4、main所在的线程组为main,构造线程的时候没有现实的指定线程组,线程组默认和父线程一样

5、当线程中的run()方法代码里面又创建了一个新的线程对象时,新创建的线程优先级和父线程优先级一样.

6、当且仅当父线程为守护线程时,新创建的线程才会是守护线程.

7、当JVM启动时,通常会有唯一的一个非守护线程(这一线程用于调用指定类的main()方法)

JVM会持续执行线程直到下面情况某一个发生为止:

1)类运行时exit()方法被调用 且 安全机制允许此exit()方法的调用.

2)所有非守护类型的线程均已经终止,or run()方法调用返回or在run()方法外部抛出了一些可传播性的异常.

Thrend类里面的Init方法,所以构造函数都是调用这个进行初始化:

/**
 * Initializes a Thread.
 * @param g 线程组
 * @param target 执行对象
 * @param name 线程名
 * @param stackSize 新线程栈大小,为0表示忽略
 * @param acc用于继承的访问控制上下文
 * @param inheritThreadLocals如果值为true,从构造线程继承可继承线程局部变量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    this.name = name;
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
   //如果所属线程组为null
if (g == null) {
        /* Determine if it's an applet or not */
 
        /* If there is a security manager, ask the security manager
   //如果有安全管理,查询安全管理需要做的工作
           what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }
 
        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
//如果安全管理在线程所属父线程组的问题上没有什么强制的要求
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
 
    /* checkAccess regardless of whether or not threadgroup is
       explicitly passed in. */
//无论所属线程组是否显示传入,都要进行检查访问.
    g.checkAccess();
    /*
     * Do we have the required permissions?
     */
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
    g.addUnstarted();
    this.group = g;
    this.daemon = parent.isDaemon();//如果父线程为守护线程,则此线程也被 设置为守护线程.
    this.priority = parent.getPriority();//获取父进程的优先级
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
 
    /* Set thread ID 设置线程id*/
    tid = nextThreadID();
}

构造方法:所有的构造方法都是调用init()方法

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
 
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
 
 
public Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
 
public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}
 
 
public Thread(String name) {
    init(null, null, name, 0);
}
 
 
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}
 
 
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}
 
 
public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    init(group, target, name, stackSize);
}

 

线程状态:

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

NEW:状态是指线程刚创建, 尚未启动

RUNNABLE:状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等。此处的runnable包括了操作系统里面的所有线程生命周期。

BLOCKED:这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区

WAITING:这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束

TIMED_WAITING:这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态

TERMINATED: 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)

(在很多文章中都写了running状态,其实源码里面只有六种的,当自己写一个线程通过while一直保持执行状态,然后使用jconsole工具去查看线程的状态,确实是Runable状态)

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值