进程:当执行一个程序(比如QQ.exe)时,内存为程序分配的资源的基本单位。
线程:调度进程的基本单位。
通俗一点说:当你打开360浏览器,就是进程,当从浏览器打开多个网页,就是多个线程。
线程:
1、线程的并行和并发:
现在有两个线程,起名叫:小明和小芳
并行:小明和小芳肩并肩在操场上跑步,即:两个线程在同一时刻都在同步的做一些事情。
并发:小明和小芳一前一后的在同一条跑道上跑步,即:两个线程在同一时间段以队列的形式去做一些事情。
2、线程生命周期:
新建:
new关键字创建一个线程之后,该线程处于新建状态。JVM为线程分配内存,初始化成员变量值。
就绪:
当线程对象调用了start() 方法之后,该线程处于就绪状态,JVM为线程创建爱你方法栈和程序计数器,等待线程调度器调度。
运行:
就绪状态的线程获得CPU资源,开始运行run() 方法。该线程进入运行状态。
阻塞:
当发生以下情况,线程会进入阻塞状态:
- 线程调用sleep() 主动放弃锁占用的处理器资源
- 线程调用了一个阻塞式 IO 方法,在该方法返回前,线程处于阻塞
- 线程试图获取一个同步锁(同步监视器),但该同步锁正被其它线程所持有。
- 线程在等待某个通知(notify)
- 程序调用了线程的 suspend() 方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。
死亡:
线程会以一下3中方式结束,结束后处于死亡状态:
- run()或call()方法执行完成,线程正常结束。
- 线程抛出未捕获的Exception或Error。
- 调用该线程stop() 方法来结束该线程,但容易死锁,不推荐使用。
3、线程的4中创建:
继承 Thread 父类:
public class ThreadDemo extends Thread { @SneakyThrows @Override public void run() { for (int i = 0; i <10 ; i++) { Thread.sleep(50); //模拟网络延迟 System.out.println("线程::"+Thread.currentThread().getName()); } } public static void main(String[] args) { System.out.println("main start"); ThreadDemo threadDemo = new ThreadDemo(); ThreadDemo threadDemo2 = new ThreadDemo(); threadDemo.start(); threadDemo2.start(); System.out.println("main end"); } }
打印图示:
正常单线程打印结果应该是 “main end”在最后结束,按顺序打印。
但多线程情况就是 “main end” 都打印完毕,其它两个线程还在随机交互打印。
实现 Runnable 接口:
public class RunnableDemo implements Runnable { @SneakyThrows @Override public void run() { for (int i = 0; i <10 ; i++) { Thread.sleep(50); //模拟网络延迟 System.out.println("线程::"+Thread.currentThread().getName()); } } public static void main(String[] args) { System.out.println("main start"); RunnableDemo threadDemo = new RunnableDemo(); //开启两个线程 Thread thread1 = new Thread(threadDemo); Thread thread2 = new Thread(threadDemo); thread1.start(); thread2.start(); System.out.println("main end"); } }
打印图示:
实现 Callable 接口+ FutureTask:
public class CallableDemo implements Callable<String> { @Override public String call() throws Exception { for (int i = 0; i <10 ; i++) { Thread.sleep(50); //模拟网络延迟 System.out.println("线程::"+Thread.currentThread().getName()); } return Thread.currentThread().getName()+":打印结束"; } public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("main start"); CallableDemo threadDemo = new CallableDemo(); //开启两个线程 FutureTask<String> futureTask = new FutureTask<>(threadDemo); new Thread(futureTask).start(); //拿到异步返回结果 // System.out.println(futureTask.get()); System.out.println("main end"); } }
打印图示:
当需要获取异步结果时,则调用 futureTask.get() 方法,因为主main线程需要阻塞等待异步线程的返回结果,所以打印图示:
使用线程池:
public class RunnableDemo implements Runnable { // 创建全局线程池,一般整个系统中只需要创建2、3个 // 创建了一个 存有 10个线程的 线程池 public static ExecutorService executorService = Executors.newFixedThreadPool(10); @SneakyThrows @Override public void run() { for (int i = 0; i <10 ; i++) { Thread.sleep(50); //模拟网络延迟 System.out.println("线程::"+Thread.currentThread().getName()); } } public static void main(String[] args) { System.out.println("main start"); // executorService.submit() 可以获得返回值, // executorService.execute(); 没有返回值 executorService.submit(new RunnableDemo()); executorService.submit(new RunnableDemo()); System.out.println("main end"); } }
如图所示:
四种的区别:
- Thread Runnable 无法得到返回结果,Callable可以得到
- 前三种都无法控制资源,线程池可以。
- 由于线程池可控资源,所以稳定,常在项目中使用。
异步编排:
https://www.cnblogs.com/lwh1019/p/12896990.html
锁机制:
1、为什么要加锁:
锁机制就是保证当出现多线程(或高并发情况)时,内存中的共享数据(变量)安全性。
2、锁的类型:
锁从宏观上分类,分为悲观锁与乐观锁。
乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
悲观锁
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。java中的悲观锁就是Synchronized。
3、java中的重量级锁和轻量级锁:
重量级锁(悲观锁性质):
性质: 在执行代码中进行加锁(synchronized关键词或创建Lock对象),JVM虚拟机将锁的权限移交给操作系统处理。
优点:线程竞争不使用自旋,不会消耗CPU
缺点:多线程或高并发情况时,只允许一个线程去执行被锁代码块,其它线程都处于阻塞状态。
适用场景:追求吞吐量。写操作比读操作频繁。
轻量级锁(乐观锁性质):
性质:原理采用CAS(compare and swap)自旋锁,为要修改的数据加上版本号(version),等将数据修改完赋值时先对比版本号是否一致,如果一致则赋值。
优点:多线程或高并发情况不会阻塞,提高了程序的响应速度。
缺点:如果始终得不到锁竞争的线程,使用自旋会消耗CPU
适用场景: 追求响应时间,读操作比写操作频繁。
4、重量级锁的应用:
1、synchronized 的使用:
本质:synchronized 本质是拥有一把锁,当大量请求去执行被锁代码块时,谁先抢到锁,谁就可以进行操作,并且有且只有一个线程可以访问,其它全部处于阻塞状态,需等锁释放后才可以再次抢锁。
写法:
2、Lock 的使用:
本质:从类关系看出Lock接口是jdk5后新添的来实现锁的功能,其实现类:ReentrantLock、WriteLock、ReadLock。其实还有一个接口ReadWriteLock,读写锁(读读共享、读写独享、写读独享、写写独享)。使用上需要显示的获取锁和释放锁,提高可操作性、可中断的获取获取锁以及可超时的获取锁,默认是非公平的但可以实现公平锁,悲观,独享,互斥,可重入,重量级锁。
写法:
class MMT { String name; Lock lock=null; public MMT(Lock lock) { this.lock=lock; } public void update(String name) throws InterruptedException{ // lock.lock(); // boolean tryLock = lock.tryLock();//尝试获取锁 //中断只是在当前线程获取锁之前,或者当前线程获取锁的时候被阻塞 // lock.lockInterruptibly(); lock.tryLock(3000, TimeUnit.SECONDS); try{ setName(name); System.out.println(Thread.currentThread().getName()+" 变换后的姓名为"+name); }finally{ lock.unlock(); } } public void setName(String name) { this.name = name; } public String getName() { return name; } public static void main(String[] args) { Lock lock = new ReentrantLock(); final MMT m = new MMT(lock); Thread tt = new Thread(new Runnable() { @Override public void run() { System.out.println("线程一 开始执行。。。"); try { m.update("张三"); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"被中断(锁释放)。。。"); } System.out.println("线程一 结束执行。。。"); } },"线程一"); Thread tt2 = new Thread(new Runnable() { @Override public void run() { System.out.println("线程二 开始执行。。。"); try { m.update("李四"); } catch (InterruptedException e) { // TODO Auto-generated catch block System.out.println(Thread.currentThread().getName()+"被中断(锁释放)。。。"); } System.out.println("线程二 结束执行。。。"); } },"线程二"); tt.start(); tt2.start(); //中断线程 tt.interrupt(); try { tt.join(); tt2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
区别:
ReentrantLock:使用上需要显示的获取锁和释放锁,提高可操作性、可中断的获取获取锁以及可超时的获取锁,默认是非公平的但可以实现公平锁,悲观,独享,互斥,可重入,重量级锁。ReentrantReadWriteLock:默认非公平但可实现公平的,悲观,写独享,读共享,读写,可重入,重量级锁。
synchronized:关键字,隐式的获取锁和释放锁,不具备可中断、可超时,非公平、互斥、悲观、独享、可重入的重量级。
应用场景:
在资源竞争不激烈的情况下,synchronized关键字的性能优与ReentrantLock,相反,ReentrantLock的性能保持常态,优于关键字。
5、轻量级锁的应用:
1、也叫自旋锁,采用CAS(compare and swap)(或compare and exchange)(或compare and set)模式,如图:
2、CAS中的常见ABA问题:
描述:所谓ABA问题就是:当我获取值为1并进行++操作的同时,有其它线程对右侧1值进行1--操作和1++操作,虽然最后的值还是1,但终究不安全。
解决:常见的解决方式就是加版本号(version),举例:
比如一个库存表的结构:
当我要对10001商品库存进行修改时,应先查询对应version的值,然后对stock进行修改时对比version是否有变化,没有变化则修改,有变化则不允许修改。
6、公平锁/非公平锁:
公平锁指多个线程按照申请锁的顺序来依次获取锁。非公平锁指多个线程获取锁的顺序并不是按照申请锁的顺序来获取,有可能后申请锁的线程比先申请锁的线程优先获取到锁,此极大的可能会造成线程饥饿现象,迟迟获取不到锁。由于ReentrantLock是通过AQS来实现线程调度,可以实现公平锁,,但是synchroized是非公平的,无法实现公平锁。
https://blog.csdn.net/cuichunchi/article/details/88532582
https://blog.csdn.net/weixin_42245930/article/details/88604036