并发随笔(1)

来源:互联网 发布:镜片种类知乎 编辑:程序博客网 时间:2024/05/19 20:43

并发的相关概念

  • 进程:
    操作系统必须全方位的管理计算机系统中运行的程序,因此操作系统为正在运行的程序建立了一个管理实体——进程,进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,进程是操作系统进行资源分配和调度的一个独立单位。
  • 线程
    线程是轻量级的进程,是程序执行的最小单位。使用多线程而不使用多进程,是应为线程之间的切换和调度成本远小于进程。一个进程可以包含多个线程
  • 并行和并发
    并行是真正意义上的多个任务同时执行,并发实际上任务仍然是串行的,只不过是多个任务交替执行
  • 临界区
    共享资源或者说是共享数据
  • 原子性,可见性,有序性
    原子性指一个操作是不能被中断的,即使是在多个线程一起执行的时候,一个操作一旦开始就不会被其他线程干扰。
    可见性是指一个线程修改了一个共享的变量之后,其他线程是否能够马上知道这个数据被修改了。
    有序性指在并发的操作中很有可能会发生乱序的情况即指令重排。

线程的状态

关于线程的状态有很多说法,大家可以参考操作系统中进程的状态,不过在java.lang.Thread中给出了线程的状态只有六种:

public enum State {         NEW,//表示刚刚创建,但是还没有开始执行,等待start()方法调用        RUNNALE,//当线程执行的时候,,表示线程一切的资源都已经准备好了,就绪        BLOCKED,//当线程在执行的时候遇到了synchronized同步代码块,进入到blocked(阻塞)状态直到获得请求的锁        WAITING,//无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。        TIME_WAITING,//等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。         TERMINATED//当线程执行完毕之后        }

线程中断

线程中断并不会使线程立即退出,而是给线程发送一个通知。告知目标线程需要退出,而目标线程接到通知后会如何处理,完全由目标线程决定。

  • 关于线程中断的三个方法
public void interrupt() //中断当前线程(中断标记)public boolean isInterrupted()// 判断是否被中断public static boolean interrupted()// 判断是否被中断 ,并清除中断状态

示例

public static void main(String[] ars) throws InterruptedException {        Thread t1 = new Thread() {            @Override            public void run() {                while (true) {                    if(Thread.currentThread().isInterrupted())                        break;                    System.out.println("222");                }            }        };        t1.start();        Thread.sleep(400);        t1.interrupt();    }}

注意的是:虽然中断了线程但是如果没有做中断处理逻辑,即使被置上中断状态,这个中断也不会发生作用,只是做上一个标记。

Thread.sleep()

Thread.sleep()方法会让当前线程休眠一段时间,并抛出一个InterruptedException.这个异常不是运行时异常,必须捕获并处理它。如果当前线程正在休眠时被中断就会抛出这个异常。并且会清除中断标记。所以在必要的时候我们必须在异常的处理中重新设置中断标记。

public class Demo3 {    public static void main(String[] ars) throws InterruptedException {        Thread t1 = new Thread() {            @Override            public void run() {                while (true) {                    if (Thread.currentThread().isInterrupted()) {                        System.out.println("中断");                        break;                    }                    System.out.println("222");                    try {                        Thread.sleep(2000);                    } catch (InterruptedException e) {                        System.out.println("正在休眠");                        //重新设置中断标记                        //Thread.currentThread().interrupt();                    }                }            }        };        t1.start();        Thread.sleep(2000);        t1.interrupt();    }}

我们发现如果不在异常捕获中重新设置中断标记,在t1休眠的时候会清除中断标记,导致下一次循环无法在捕获这个中断位

等待(wait)通知(notify)

public final void notify()public final void notifyAll()public final void wait(long timeout)throws  InterruptedException//这三个方法都是object中的方法

如果一个线程调用了obj.wait() ,就会进入object对象等待队列,在这个队列中可能会有多个线程。因为系统中可能运行多个线程等待一个对象,当obj.notify()被调用时,他会从这个队列中随机选择一个线程,将其唤醒。这个选择完全是随机的。但是在调用wait()方法的时候必须要先获得object对象的监视器,执行之后会释放这个监视器。这样做的目的是使其他在等待object对象上的线程不至于因为T1线程的休眠而全部无法正常执行。而T2在执行notify()之前也必须获得object的监视器。接着唤醒一个等待的线程,T1被唤醒之后第一件事是重新获得object的监视器。只有顺利获得这个监视器才能顺利执行下面的代码。在消费者和生产者模型会具体说明这两个方法。
面试相关:解释Object.wait()和Thread.sleep()。
上面的区别很详细了,但是最主要的一个区别就是wait()会释放目标对象的锁,而sleep()不会释放任何资源。

挂起(suspend)和继续执行(resume)

挂起和执行时一对相反的操作,挂起起后导致线程暂停,只有resume后才会继续执行线程但是他们已经被废弃了,因为挂起不会释放任何的锁资源,只有当resume,挂起的线程才能被继续执行,由于锁不会被释放会导致整个系统异常,而且从线程的状态上看竟然还是Runnable,会严重影响我们对系统当前状态的判断。

等待线程结束(join)和谦让(yield)

public final void join() throws InterruptedException 无限阻塞当前线程直到目标线程执行完毕public final void join(long millis,int nanos)throws InterruptedException 给出等待的最大时间

示例

public class Demo4 {    public volatile static int i = 0;    public static class AddThread extends Thread {        @Override        public void run() {            for (i = 0; i < 1000; i++) {            }        }    }    // 在主函数中如果没有at.join(),那么得到的i可能是一个非常小的数或者是0    // 因为AddThread还没有开始执行主线程就已经执行完毕了。    // 但是在加入at.join()表示主线程愿意等待AddThread执行完毕之后,再执行主线程。    public static void main(String[] args) throws InterruptedException {        AddThread at = new AddThread();        at.start();        at.join();        System.out.println(i);    }}
public  static  void  yield()   //他会使当前线程放弃CPU的执行权,但是不表示当前线程不执行了。仍然会进行CPU的资源争夺。但是是随机的

volatile关键字

当一个变量可能会不断被修改的时候使用volatile修饰。使所有的线程都能“看到”这个改动。可以简单的解决原子性和可见性的问题。

守护线程

守护线程是一种特殊的线程,它在后台默默的执行一些系统性的服务,例如垃圾回收线程,JIT线程,与之对应的用户线程。当用户线程全部结束意味着这个程序的业务操作已经全部完成,守护线程要守护的对象已经不存在了。所以当一个java应用只有守护线程的时候,java虚拟机会自然退出。设置守护线程:Thread.setDeamon(true),但是必须要在线程start()之前设置

内部锁(sychronized)

三种方式:
指定对象加锁对象,必须是同一个对象
直接作用于实例方法(使用当前类的对象this同步)
直接作用于静态方法(使用Class字节码文件对象同步)

后面会具体示范内部锁的使用

该随笔主要阅读《Java高并发程序设计》总结

0 0
原创粉丝点击