多线程的基本认知
1.什么是多线程?
说多线程得从进程聊起,所谓的进程就是一个可以独立运行的程序单位,一个进程由多个线程构成.
比如打开电脑,打开QQ,这是开启一个进程
打开网页,这类似于打开一个线程,可以同时打开多个网页,类似开启多个线程
多线程的应用可以大幅度提高程序的效率,但是开多线程的同时会消耗更多的系统资源,更多的线程需要更多的内存,使用不当的同时也会引发更多的问题
多线程并不是几个线程同时运行的,实际上是系统不断地在各个线程之间来回的切换,因为切换的速度非常快,所以给人一种线程同时在运行的错觉
线程的三种实现方式
package Sync;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadNew {
public static void main(String[] args) {
new MyThread1().start();
new Thread(new MyThread2()).start();
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
new Thread(futureTask).start();
try {
Integer integer = futureTask.get();
//System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//方式一:继承Thread
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("mythread1");
}
}
//方式二:实现Runnable接口
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("mythread2");
}
}
//方式三:实现callable接口
class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("mythread3");
return 1;
}
}
程序运行结果如下:
线程的三种实现方式底层都是实现了Runnable接口,重写了Runnable接口中的Run()方法.
其中继承Thread类和实现Runnable接口都没有返回值,只有通过Callable接口实现的call()方法有返回值
第四种实现方式:线程池
package Thread1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//线程池
public class TestPool {
public static void main(String[] args) {
//创建线程池
//newFixedThreadPool参数:表示线程池的数量
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
//关闭链接
executorService.shutdownNow();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
执行结果如图
2.线程的生命周期和五种基本状态
1.新建状态(new): 当线程对象被创建后,就进入了新建状态,如下
Thread thread = new Thread();
2.就绪状态(Runnable): 当调用线程的start()方法后,线程进入就绪状态,此时线程随时可以被cpu调动,并不是说调用start()方法线程会立即执行.
3.运行状态(Running):cpu开始调度处于就绪状态的线程时,线程才开始真正被执行,即运行状态.
4.阻塞状态(Blocked):处于运行状态的线程由于某些原因放弃对cpu的使用权,停止运行,进入阻塞状态.直到再次进入就绪状态,才能再次被cpu调用.
5.死亡状态(Dead):线程执行完或者因为异常退出执行run()方法.该线程生命周期结束.
3.多线程与高并发
高并发是系统运行过程中在短时间内遇到大量的请求操作,当请求的操作过多时,可能导致系统宕机,请求响应时间过长等问题.
多线程在高并发中使计算机的资源能最大程度上利用起来,不至于浪费系统资源
4.Synchronized关键字和Lock锁
Synchronized是Java中的关键字,当用Synchronized修饰方法或代码块时,每次只允许一个线程进入由它修饰的方法或代码块,从而防止多个线程同时操作同一个资源进入死锁状态,Synchronized就像一把锁,线程想进入Synchronized修饰的代码只能先获取到这把锁,其他线程想进去只能等待前一个线程使用完把锁归还后再进入,也就是先释放锁,其他线程才能使用.
同步代码块示例:
@Override
public void run() {
//同步代码块
synchronized (account){
if (account.money - drawingmoney < 0) {
System.out.println("你的余额不足");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingmoney;
nowmoney = nowmoney + drawingmoney;
System.out.println(account.name + "的余额为:" + account.money);
System.out.println(this.getName() + "手里的钱:" + nowmoney);
}
}
同步方法,只需要加一个synchronized关键字
private synchronized void buy() throws InterruptedException {
if (TicketNum<=0){
flag=false;
return;
}
Thread.sleep(200);
System.out.println(Thread.currentThread().getName()+"抢到了第"+TicketNum--+"张票");
}
Lock是Java开发中的一个接口,Lock需要手动加锁和释放锁,通过luck()加锁,通过unlock()方法释放锁.ReetrantLock 实现了Lock接口,它是一个可重入锁,内部定义了公平锁和非公平锁,如果使用了luck锁,则必须释放锁,一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
Synchronzied 和 Lock 的主要区别
- 存在层面:Syncronized 是Java 中的一个关键字,存在于 JVM 层面,Lock 是 Java 中的一个接口
- 锁的释放条件:1. 获取锁的线程执行完同步代码后,自动释放;2. 线程发生异常时,JVM会让线程释放锁;Lock 必须在 finally 关键字中释放锁,不然容易造成线程死锁
- 锁的获取: 在 Syncronized 中,假设线程 A 获得锁,B 线程等待。如果 A 发生阻塞,那么 B 会一直等待。在 Lock 中,会分情况而定,Lock 中有尝试获取锁的方法,如果尝试获取到锁,则不用一直等待
- 锁的状态:Synchronized 无法判断锁的状态,Lock 则可以判断
- 锁的类型:Synchronized 是可重入,不可中断,非公平锁;Lock 锁则是 可重入,可判断,可公平锁
- 锁的性能:Synchronized 适用于少量同步的情况下,性能开销比较大。Lock 锁适用于大量同步阶段:
- Lock 锁可以提高多个线程进行读的效率(使用 readWriteLock)
- 在竞争不是很激烈的情况下,Synchronized的性能要优于RentrantLock ,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是RentrantLock 的性能能维持常态;
- RentrantLock 提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。
死锁的成立条件
- 互斥条件:任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
- 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
- 循环等待条件:就是前一个进程占有后一个进程所需要的资源,导致线程之间的资源无法获取到,形成闭环,等待资源的释放。
预防死锁只需要破坏四个必要条件的其中的任何一个即可