java并发基础(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36223142/article/details/88131990

现代操作系统在运行一个程序时,会为其创建一个进程。例如,启动一个Java程序,操作系统就会创建一个Java进程。线程是现代操作系统调度的最小单元,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计算器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

1.为什么要用到并发

面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分

  • 充分利用多核CPU的计算能力;
  • 方便进行业务拆分,提升应用性能

缺点:

时间片是CPU分配给各个线程的时间,因为时间非常短,所以CPU不断通过切换线程,让我们觉得多个线程是同时执行的,时间片一般是几十毫秒。而每次切换时,需要保存当前的状态起来,以便能够进行恢复先前状态,而这个切换时非常损耗性能,过于频繁反而无法发挥出多线程编程的优势。通常减少上下文切换可以采用无锁并发编程,CAS算法,使用最少的线程和使用协程。

  • 频繁的上下文切换
  • 线程安全(死锁)

2.常用概念

 2.1 同步vs异步

同步和异步通常用来形容一次方法调用。同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。而异步调用,指的是,调用者不用管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。

 2.2 并发vs并行

并发和并行是十分容易混淆的概念。并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。

 2.3 阻塞vs非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响,比如一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞,而非阻塞就恰好相反,它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试地往前运行。

 2.4 临界区资源

保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。 

 2.5 死锁(Deadlock)、饥饿(Starvation)和活锁(LiveLock)

活锁:指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
死锁:指的是两个或者两个以上的进程相互竞争系统资源,导致进程永久阻塞。
饥饿:指的是等待时间已经影响到进程运行,此时成为饥饿现象。如果等待时间过长,导致进程使命已经没有意义时,称之为“饿死”。

3.线程的状态和基本操作

 3.1 如何新建线程

  • 继承Thread类
 public class MyThread extends Thread {
        @Override
        public void run() {
            //重写run方法
        }
 }
  • 实现Runnable接口
public class MyThread implements Runnable {
        @Override
        public void run() {
            //重写run方法
        }
}
  • 实现Callable接口
public class MyThread implements Callable {
        @Override
        public Object call() throws Exception {
            return null;
        }
}
  • FutureTask 

FutureTask<Integer> futureTask=new FutureTask<Integer>((Callable<Integer>) ()-> {
            int i=0;
            for(;i<1000;i++){
                System.out.println(Thread.currentThread().getName()+"===="+i);
            }
            //call()方法可以有返回值
            return i;
});
Thread thread=new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());

3.2 线程状态的转换

https://blog.csdn.net/pange1991/article/details/53860651

 3.3 线程的基本操作

  • interrupt

线程将会标记成中断状态

isInterrupted(),用来判断当前线程的中断状态

nterrupted()是个Thread的static方法,用来恢复中断状态

  • sleep

Thread.sleep()  、 Object.wait()

sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

wait和notify/notifyAll方法只能在同步代码块里使用

wait()方法强制当前线程释放对象锁。这意味着在调用对象的wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wati()方法.

  • join

Join方法实现是通过wait,当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。

  • yield

告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。不能保证使得当前正在运行的线程迅速转换到可运行的状态。它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

 3.4 守护线程Daemon

用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程,而用户线程都已经退出运行时,VM就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,VM就不会退出。

 thread.setDaemon(true); //必须在启动线程前调用

猜你喜欢

转载自blog.csdn.net/qq_36223142/article/details/88131990