文章目录
进程和线程的区别
- 进程:正在运行的程序。计算机在执行硬盘中的程序时会为程序创建相应的进程,会以进程为单位进行资源分配。
线程:每个进程实际是执行一系列的线程 - 进程之间的资源是独立的,线程之间的资源在同一个进程内是共享的
系统为每个进程分配独立的寻址空间,而线程没有独立的寻址空间,多线程共享同一个进程的寻址空间
线程的基本概念
- 线程都有自己的名字,其中执行main方法的线程名称叫:main线程
- 每个线程都有自己的优先级,分为1-10个优先级,所有线程默认优先级为5。优先级数字越大,越优先执行,获取到的CPU资源越多。
线程的四种实现方式
- 实现Runnable接口,覆写run方法
//创建线程
public class Ticket implements Runnable {
private int num = 100;
@Override
public void run() {
System.out.println("实现Runnable接口:"+ --num);
}
}
//启动线程
public class Demo{
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread thread = new Thread(ticket);
thread.start();
}
}
- 继承Thread类,覆写run方法
public class CustomThread extends Thread {
@Override
public void run() {
System.out.println("继承Thread");
}
public static void main(String[] args) {
//启动线程
new CustomThread().start();
}
}
- 实现Callable接口,覆写call方法【这种线程实现方式有返回值,并且可以抛出异常】
//通过泛型来指定该线程执行完毕后的返回值
public class CustomCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 123;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CustomCall call = new CustomCall();
//FutureTask是Future接口的实现类
FutureTask<Integer> task = new FutureTask<Integer>(call);
Thread thread = new Thread(task);
thread.start();
//接收返回结果
System.out.println(task.get());
}
}
-
Thread和Runnable的相同和不同
-
相同
Thread类实现了Runnable接口
二者都覆写了run方法
-
不同
Runnable接口可以使多个线程共享一个资源
Runnable接口的实现类更健壮,避免了java单继承的限制
-
线程安全
线程安全问题:多个线程同时操作共享资源会出现的问题
线程同步:多个线程在执行某部分代码时按照一定顺序执行
线程通信:多线程之间信息传递
线程安全性
-
原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作
Atomic包、CAS算法、synchronized、lock
-
可见性:一个线程对主内存的修改可以及时被其他线程观察到
synchronized、volatile
-
有序性:保证指令的重排序不会影响到多线程并发执行的正确性——遵循happens-before原则
java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
举例:i=i++;-----------temp=i; i=i+1; i=temp;
Volatile—内存可见性
-
内存可见性问题:当多个线程进行操作共享数据时,彼此不可见。【高速缓存和寄存器】与【内存】中的数据一致性问题。
高速缓冲:cpu和内存之间的存储器件 寄存器:cpu的一部分
举例:线程甲从堆内存中获取共享数据,在使用的过程中线程乙把共享数据给修改了,而线程甲却仍然还在使用之前读取的数据
-
Volatile修饰符的作用:当多个线程进行操作共享数据时,保证内存中的数据可见。
使用volatile修饰符修饰的数据,每次都从内存中读取数据,且读取之前先跨越内存栅栏,使所有【写操作线程】将数据同步到内存中,读操作线程才获取数据
CAS算法—原子性
当多个线程同时修改共享数据时,有且仅有一个线程修改成功,其他线程再次读取主存中的共享数据,再次执行。
举例:当线程A读取主存中的共享数据i=0,当线程A要修改时会再次到内存中读取共享数据,将前后2次的数据相同均为0,则修改共享数据,如果不同则什么也不做。
使用原子性类型,如AtomicInteger、AtomicLong
Synchronized
- Synchronized同步锁
- 修饰类
- 修饰普通方法
- 修饰静态方法
- 修饰代码块
- Synchronized的作用:保证共享变量修改能够及时可见
- Synchronized的缺点:加了同步锁,代码运行效率会下降
- Synchronized的原理:每个对象都有一个监视器锁(monitor),没有获取到同步锁的线程无法继续执行下去,就以队列的方式进行等待,获取到同步锁的线程执行完后才会释放锁
- Synchronized 修饰代码块
//对象监视器(同步锁):没有获取到同步锁的线程无法继续执行下去,获取到同步锁的线程执行完后才会释放锁
Synchronized(对象监视器){
//要操作的共享代码
}
- Synchronized 修饰方法:方法的返回值类型前加Synchronized
public synchronized void Sell() {
//要操作的共享代码
}
-
同步锁必须是多线程共享的对象,这样每个线程才都能获取到同一把锁
同步普通方法的同步锁默认是:this
同步静态方法的同步锁是:同步方法所在类的字节码对象【类名.class】
同步普通代码块的同步锁可以是任意对象,需要根据代码逻辑的具体的实现我们自己去指定,锁对象可以通过身加static来实现多线程共享一把锁,一般我们可以使用使用this,但也有可能使用:当前类名.clsss
Lock
Lock是个接口,Lock底层是CAS算法(数据一致性)和Volatile(内存可见性)实现
获取锁的方法lock(),释放锁的方法unlock
ReentrantLock是Lock接口的实现类,使用时需要注意是否加static,实现多线程共享一把锁
public class ThreadDemo extends Thread {
private static Integer num = 100;
private static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (num > 0) {
System.out.println(Thread.currentThread().getName() + ":" + num--);
}
} finally {
lock.unlock();
}
}
}
}
死锁
甲想进入A1屋里的B1屋,乙想进入B2屋里的A2屋,A1和A2屋共用一把锁,B1和B2屋共用一把锁
//锁A的单例
public class LockA {
private static final LockA lockA = new LockA();
private LockA() {
}
public static LockA getLockA() {
return lockA;
}
}
//锁B的单例
public class LockB {
private static final LockB lockB = new LockB();
private LockB() {
}
public static LockB getLockB() {
return lockB;
}
}
public class ThreadTask implements Runnable {
private int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (LockA.getLockA()) {
System.out.println("甲进入房间A");
synchronized (LockB.getLockB()) {
System.out.println("甲进入房间B");
}
}
} else {
synchronized (LockB.getLockB()) {
System.out.println("乙进入房间B");
synchronized (LockA.getLockA()) {
System.out.println("乙进入房间A");
}
}
}
i++;
}
}
}
public class Demo {
public static void main(String[] args) {
new Thread(new ThreadTask()).start();
new Thread(new ThreadTask()).start();
}
}
Synchronized和Lock的区别
- Synchronized是关键字,Lock是接口
- Synchronized加锁和解锁由JVM管理,Lock加锁和解锁由java代码实现
- Synchronized可以锁住类、普通方法、静态方法、代码块,Lock只能锁住代码块
- Synchronized是悲观锁,Lock是乐观锁,Lock底层是CAS算法(数据一致性)和Volatile(内存可见性)实现
悲观锁和乐观锁的区别
悲观锁在获取到数据的时候,别人就不能修改,必须等自己修改完了释放了锁才能修改
乐观锁在获得到数据的时候,别人可以修改,自己修改时只需要判断数据获取数据之后是否被其他线程更新即可,如版本号
线程通信
Synchronize同步时,线程通信使用的Object类的wait、notify、notifyAll方法
Lock同步时,线程通信使用的是与Lock锁绑定的Condition对象的await、signal、signalAll方法
synchronized(同步锁对象){
this.wait();//等待
this.notify();//唤醒任意一个等待的线程
this.notifyAll();//唤醒所有等待的线程
}
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
condition.await();
condition.signal();
condition.signalAll();
lock.unlock;
-
wait、notify、notifyAll是Object类的方法,不是Thread的
-
join、yield、sleep是Thread的方法,sleep是静态方法
-
只有在同步控制方法或同步控制块里才能调用 wait() 、notify() 和 notifyAll()。
-
执行了wait方法的线程,只有执行notify() 和 notifyAll()才能被唤醒
-
notify唤醒同一个监视器对象上的其他处于等待状态的任意一个线程
notify唤醒同一个监视器对象上的其他所有等待状态的线程
程序运行原理
分时调度、抢占式调度
分时调度:每个线程运行时间固定
抢占式调度:优先级高的线程优先运行,优先级相同随机选择一个运行【JVM采用的抢占式调度】
创建线程的目的
为程序创建单独的执行路径,使多个程序同时运行
虚拟机会给每个线程开一个独立的虚拟机栈,栈内存是线程私有的,一个方法一个栈帧,后进来的方法不执行完,先进栈的方法就无法执行
获取当前线程的名称:Thread.currentThread().getName()
单例模式
单例模式分为懒汉模式和饿汉模式
饿汉模式
饿汉模式解决了线程安全问题,但在类加载时就会创建对象时,而不管对象是否要被使用,会占用内存
public class SingleInstance {
private static final SingleInstance instance = new SingleInstance();
private SingleInstance() {
}
public static SingleInstance getInstance() {
return instance;
}
}
懒汉模式
懒汉模式不加synchronized,线程不安全,因此需要加synchronized,保证线程安全
public class SingleInstance {
private static SingleInstance instance;
private SingleInstance() {
}
public static synchronized SingleInstance getInstance() {
if (instance == null) {
instance = new SingleInstance();
}
return instance;
}
}
- 双重验证:加了synchronized修饰符的懒汉模式效率不高,因为只有第一次调用getInstance方法时,才需要创建实例对象,因此当实例对象已经被创建时就需要进同步锁。
public class SingleInstance {
private static SingleInstance instance;
private SingleInstance() {
}
public static SingleInstance getInstance() {
if (instance == null) {
synchronized (SingleInstance.class) {
if (instance == null) {
instance = new SingleInstance();
}
}
}
return instance;
}
}
枚举模式
public enum Singleton {
INSTANCE;
}
同步和异步的区别
同步必须等方法执行完成后才能继续往下执行
异步则不需要等方法执行完成就可以继续往下执行
并发和并行的区别
- 并发:多个任务交替执行 ,同一时间只有一个任务执行 【CPU上下文切换】
- 并行:多个任务同时执行