美文网首页
(四):并发编程基础

(四):并发编程基础

作者: JBryan | 来源:发表于2020-03-13 14:55 被阅读0次

1.并行,并发的区别

并发 concurrency:
一台处理器上同时处理任务, 这个同时实际上是交替处理多个任务,程序中可以同时拥有两个或者多
个线程,当有多个线程在操作时,如果系统只有⼀个CPU,则它根本不可能真正同时进行 一个以上的线
程,它只能把CPU运行 时间划分成若干个时间段,再将时间段分配给各个线程执行 。
并行 parallellism:
多个CPU上同时处理多个任务,一个CPU执行 ⼀个进程时,另一个CPU可以执行 另⼀个进程,两个进程互不抢占CPU资源,可以同时进行 。
在垃圾收集器中,用户线程与垃圾收集线程 的并行与并发关系:
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行,用户程序在继续执行,而垃圾收集程序运行于另一个CPU上。

2.实现多线程有哪几种方式

2.1.继承Thread
public class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.setName("zbw");
        thread.start();
    }
}
2.2.实现Runnable接口
public class MyRunnable implements Runnable{

    public void run() {
        System.out.println(Thread.currentThread().getName());
        try {
            System.in.read();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.setName("runnable");
        thread.start();
        //Lambda表达式
        new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        }).start();
        
    }

}
2.3.通过Callable和FutureTask方式
public class MyTask implements Callable<Object> {
    @Override
    public Object call() throws Exception {
        System.out.println("通过Callable实现多线程,名称:"+Thread.currentThread().getName());
        return "我是返回值";
    }

    public static void main(String[] args) {
        MyTask myTask = new MyTask();
        FutureTask<Object> futureTask = new FutureTask<>(myTask);
        Thread thread = new Thread(futureTask,"CallableThread");
        thread.start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

2.4.通过线程池创建线程
public class ThreadPoolDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("通过Callable实现多线程,名称:"+Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i=0;i<10;i++){
            executorService.execute(new ThreadPoolDemo());
        }
        executorService.shutdown();
    }
}

3.java线程常见的基本状态有哪些,这些状态分别是做什么的?

1.新建(NEW),表示线程被创建出来还没真正启动的状态,可以认为它是个 Java 内部状态。
2.就绪(RUNNABLE),表示该线程已经在 JVM 中执行,当然由于执行需要计算资源,它可能是正在运行,也可能还在等待系统分配给它 CPU 片段,在就绪队列里面排队。在其他一些分析中,会额外区分一种状态 RUNNING,但是从 Java API 的角度,并不能表示出来。
3.阻塞(BLOCKED),这个状态和我们前面两讲介绍的同步非常相关,阻塞表示线程在等待 Monitor lock。比如,线程试图通过 synchronized 去获取某个锁,但是其他线程已经独占了,那么当前线程就会处于阻塞状态。
4.等待(WAITING),表示正在等待其他线程采取某些操作。一个常见的场景是类似生产者消费者模式,发现任务条件尚未满足,就让当前消费者线程等待(wait),另外的生产者线程去准备任务数据,然后通过类似 notify 等动作,通知消费线程可以继续工作了。Thread.join() 也会令线程进入等待状态。
5.计时等待(TIMED_WAITING),其进入条件和等待状态类似,但是调用的是存在超时条件的方法,比如 wait 或 join 等方法的指定超时版本。
6.终止(TERMINATED),不管是意外退出还是正常执行结束,线程已经完成使命,终止运行,也有人把这个状态叫作死亡。


线程状态.jpg

4.Java线程的常见方法

sleep()
属于线程Thread的方法,让线程暂缓执行,等待预计时间之后再恢复,交出CPU使用权,不会释放锁,进入阻塞状态TIME_WAITGING,睡眠结束变为就绪Runnable。
yield()
属于线程Thread的方法,暂停当前线程的对象,去执行其他线程,交出CPU使用权,不会释放锁,和sleep类似。
作用:让相同优先级的线程轮流执行,但是不保证⼀定轮流。
注意:不会让线程进入阻塞状态,直接变为就绪Runnable,只需要重新获得CPU使用权。
join()
属于线程Thread的方法
在主线程上运行调用该方法,会让主线程休眠,不会释放已经持有的对象锁,让调用join方法的线程先执行完毕,再执行其他线程。类似让特种车辆优先通过
wait()
属于Object的方法,当前线程调用对象的wait方法,会释放锁,进入线程的等待队列,需要依靠notify或者notifyAll唤醒,或者wait(timeout)时间自动唤醒。
notify()
属于Object的方法,唤醒在对象监视器上等待的单个线程,选择是任意的。
notifyAll()
属于Object的方法,唤醒在对象监视器上等待的全部线程。

5.能举例几个多线程的业务场景吗?

异步任务:用户注册、记录日志
定时任务:定期备份日志、备份数据库
服务器编程:Socket网络编程,⼀个连接⼀个线程

6.了解volatile关键字不?

当一个变量被定义为volatile之后,它将具备两种特性。
第一是保证此变量对所有线程的可见性,这里的可见性是指,当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。volatile变量的运算,在并发下一样是不安全的。

第二个是禁止指令重排序优化,普通的变量仅仅会保证在该方法的执行过程中,所有依赖赋值结果的地方,都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。因为在一个线程的方法执行过程中无法感知这点,这也就是Java内存模型中描述的所谓的“线程内表现为串行的语义”。

假定T表示一个线程,V和M表示两个volatile变量,那么在进行Java内存模型的8种操作时,需要满足如下规则:
1.只有当线程T对变量V执行的前一个动作是load时,T才能对V执行use动作;并且,只有T对V的后一个动作时use时,T才能对V执行load操作。也就是说use和load,read动作必须连续一起出现。(这条规则要求在工作内存中,每次使用V前,都必须先从主内存刷新最新的值,用于保证能看到其他线程对变量V操作的修改后的值)

2.只有T对V执行的前一个动作是assign时,T才能对V执行store;并且,只有T对V的后一个动作是store时,T才能对V执行assign操作。也就是说assign和store,write动作必须连续一起出现。(这条规则要求在工作内存中,每次修改V后都必须立刻同步回主内存中,用于保证其他线程可以看到,自己对变量V的修改)

3.假定A是T对V的use操作,F是与A关联的load操作,P是与F关联的read操作;B是对W的use操作,G是与B关联的load操作,Q是与P关联的read操作。如果A先于B,那么P先于Q。(这条规则要求volatile修饰的变量,不会被指令重排优化,保证代码的执行顺序与程序的顺序相同)


12-规则3.jpg

相关文章

网友评论

      本文标题:(四):并发编程基础

      本文链接:https://www.haomeiwen.com/subject/nrdqshtx.html