线程的5大状态
新建-------就绪--------运行---------阻塞(线程被暂停)-----------死亡
阻塞状态:
- 等待阻塞:执行wait()方法之后,使本线程进入等待阻塞状态,【会释放对象锁】因此被唤醒之后需要重新等待锁资源。
- 同步阻塞:线程在获取synchronized同步锁失败,会进入同步阻塞状态
- Thread.sleep()【不会释放锁资源】或join()或I/O请求时,线程会进入阻塞状态,在处理结束之后,会重新进入就绪状态。
常用方法:
- wait()与notify()属于Object方法,wait()会释放锁资源,并让出cpu;
- sleep()会阻塞当前线程,但是不会释放锁资源,只会让出cpu;
- yeild():属于一个声明,当本线程重要部分已运行完之后,可以切换给其他线程来运行。这个方法只是对线程调度器的一个建议;
- join():当父线程想等待子线程Thread0先运行完之后在运行,可以使用Thread0.join(),此时Thread0所在的主线程会被阻塞,Thread0会正常运行。
如何开启多线程—都是采用start()启动多线程
1. 继承Thread类
public class ThreadTest {
private int age = 10;
public void threadTest(){
for (int i = 0; i < 100; i++) {
Thread thread = new NewThread();
thread.start();
}
}
class NewThread extends Thread{
@Override
public void run() {
age++;
System.out.println(age);
}
}
}
采用匿名方式,实现Runable接口,并作为参数传递给Thread类
/**
* 避免这种方式
/
new Thread(new Runnable() {
public void run() {
//执行体
}
}).start();
实现Runable接口,并作为参数传递给Thread类
public class ThreadTest {
private int age = 10;
public void threadTest(){
//实际的线程依然需要Thread实例对象,Thread才真正创建线程对象
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(new ThreadRunnable());
thread.start();
}
}
class ThreadRunnable implements Runnable {
@Override
public void run() {
age++;
System.out.println(Thread.currentThread().getName() +"----"+ age); }
}
}
通过Callable和Future接口创建线程
1)实现Callable接口,重写call()有返回值
2)new一个FutureTask对象,将Callable对象作为初始化参数
3)使用FutureTask对象作为Thread对象的target创建并启动新线程
4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class MyCallable implements Callable<Integer> {
private int i = 0;
@Override
public Integer call() throws Exception {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建MyCallable对象
Callable<Integer> myCallable = new MyCallable();
//使用FutureTask来包装MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable);
for (int i = 0;i<50;i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
if (i == 30) {
Thread thread = new Thread(ft);
thread.start();
}
}
System.out.println("主线程for循环执行完毕..");
Integer integer = ft.get();
System.out.println("sum = "+ integer);
}
}
线程池
** 常见线程池**
1)newSingleThreadExecutor 单个线程的线程池,即线程池中每次只有一个线程工作
2)newFixedThreadExecutor(n)固定数量的线程池
3)newCacheThreadExecutor(推荐)当线程池大小超过了处理任务所需的线程,就会回收一步分空闲线程,又有任务来时,就会生成新的线程来执行。
4)newScheduleThreadExecutor,大小无限制的线程池,支持定时和周期性的执行线程
推荐使用
ExecutorService executor=new ThreadPoolExecutor(corePoolSize,MaximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler)
多线程安全问题以及如何保证
多个线程同时运行这段代码,不会产生不确定的结果
如何保证线程安全的:执行控制和内存可见
执行控制:控制代码执行顺序以及是否可以并发执行
内存可见:线程执行结果对内存中其他线程的可见性。当一个线程在具体执行时,会先copy主存数据到线程本地,操作完成之后在刷为主存。
运行结果和单个线程依次运行结果相同,就是线程安全===》同步问题
线程安全问题
synchronized
- 同步代码块
class ThreadRunnable implements Runnable {
@Override
public void run() {
synchronized (this){ //对代码块添加锁,保证线程同步
age++;
System.out.println(Thread.currentThread().getName() + "----" + age);
}
}
}
- 同步方法:加synchronized修饰
class ThreadRunnable implements Runnable {
@Override
public synchronized void run() {
age++;
System.out.println(Thread.currentThread().getName() + "----" + age);
}
}
synchronized修饰普通方法与修饰静态方法与修饰类的区别
修饰方法:锁的是当前对象
修饰类:锁的是类,该类的所有对象公用一把锁
Lock 实现类之一--------ReentrantLock
底层是AQS+CAS实现,在并发量小的时候性能不如synchronized,
- 可重入锁,
- 需手动获取和释放锁lock.lock()或lock.unlock()
- 支持获取锁的公平性和非公平性-----所以性能相对差一点
concurrent包下的一些东西
volatile
**作用:**仅能实现对原始变量(int long)操作的原子性,所有对volatile变量的读写都会直接刷到内存。
**本质:**告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取
synchronized与ReetrantLock的区别
- 两者都是可重入锁
- synchronized是基于JVM层面的,释放锁的方式:正常释放或者是抛出异常,由jvm释放
- synchronized是悲观锁
- ReetrantLock乐观锁 CAS和volatile
volatile
**作用:**仅能实现对原始变量(int long)操作的原子性,所有对volatile变量的读写都会直接刷到内存。
内存可见:每次写都直接刷到内存,读的时候也从内存读
禁止指令重排序:就是说volatile修饰的变量 必须前边全部执行完,且后边的仍然在后边执行
**本质:**告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取
锁的状态
偏向锁
在轻量级锁的基础上进一步减轻性能消耗,只需要一次CAS操作,轻量级锁是依赖于多次的CAS操作。
在一个线程获得锁之后,这个锁就进入锁偏向状态,当这个线程再次请求锁时,无需在做任何同步操作;
轻量级锁
使用CAS操作来避免重量级锁使用互斥量的开销。
重量级锁
锁优化的方式:
自旋锁
因为同步互斥会频繁的挂起和唤醒线程,就需要从用户态切换到内核态,
考虑到有些数据的锁定时间较短,就让等待锁的那个线程执行一个忙循环,此时是仍占用在处理器上。
对于单核处理器,自旋多久都是浪费,因为占着cpu,旧owner无法释放,
锁粗化
就是将连续的多个加锁解锁操作合并为同一个,比如StringBuffer,在第一个append操作时加锁,最后一个append操作时解锁。
public class StringBufferTest {
StringBuffer stringBuffer = new StringBuffer();
public void append(){
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
}
}
锁消除
是在编译器级别的事情,对运行上下文进行扫描,去除不可能存在共享资源竞争的锁【涉及到“逃逸分析”】;
https://blog.csdn.net/u012834750/article/details/69398216
|轻量级锁| 自旋锁|自旋会消耗cpu,但是线程不会阻塞
|-重量级锁-|阻塞锁|线程会被阻塞,但是不会占用cpu
| 偏向锁 |只适用于一个线程访问同步代码块的场景 |
阻塞锁
就是如果锁被占用,该线程就会被阻塞,优点是不会cpu占用率过高,但是进入时间以及恢复时间都会比自旋锁慢;
可重入锁
可以避免死锁,会统计进入的次数。
读写锁
ReentrantReadWriteLock 使用两把锁解决问题,
同步锁有什么问题啊,jdk对此做了啥改进?
问题:需要操作系统线程之间的切换,从用户态切换到内核态,所以效率比较低。
1)锁自旋
2)锁偏向
3)锁粗化
4)锁消除
ThreadLocal
实现线程本地存储功能:每个Thread都有一个ThreadLocal.ThreadLoacalMap对象。
java内存模型
规定所有的变量都放在主存中,每个线程都有自己的工作内存,工作内存在高速缓存或者寄存器中。线程读变量的所有操作都必须在自己的工作内存中完成,而不能直接堆主存进行操作,而且每个线程不能访问其余线程的工作内存
java内存模型的特点:
1)原子性
2)可见性:一个线程修改了内存的某个遍历,其他线程会力机得到这个修改。主要是通过在修改后立即将新值同步回主内存,其余遍历在读取前会从主内存刷新变量值来实现可见性。
实现可见性的三种方式:
a) volitile
b)synchronized
c)final
3)有序性:volitile关键字通过添加内存屏障的方式来禁止指令重排序。
Java.util.concurrent并发包 包含了什么
1. 提供线程池的创建类:ThreadPoolExecutor, Excutors等
2. 提供了各种锁,Lock
3. 提供了各种线程安全的数据结构,ConcurrentHashMap、LinkedBlockingQueue、DelayQueue 等
**4.提供了更加高级的线程同步结构,如 CountDownLatch、CyclicBarrier、Semaphore 等 **
https://blog.csdn.net/weixin_41818794/article/details/104210071
AQS是核心
**
他是为实现各种阻塞锁和同步器而提供的一种基础框架
内部组成:
1)一个int类型的state变量,被volatile修饰
2)维护一个Node内部类,实现同步队列和等待队列
**
CountDownLatch使用—一个只有减法的计数器,可以让多个线程等待
countDown()计数器减一;
await():当计数器不为0时,阻塞所有线程,为0时,唤醒所有线程;
// 医院闭锁
CountDownLatch hospitalLatch = new CountDownLatch(1);
// 患者闭锁
CountDownLatch patientLatch = new CountDownLatch(5);
System.out.println("患者排队");
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
final int j = i;
executorService.execute(() -> {
try {
hospitalLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("体检:" + j);
patientLatch.countDown();
});
}
System.out.println("医生上班");
hospitalLatch.countDown();
patientLatch.await();
System.out.println("医生下班");
executorService.shutdown();
输出结果:
患者排队
医生上班
体检:4
体检:0
体检:1
体检:3
体检:2
医生下班
CyclicBarrier 可以控制多个线程等待,也是通过计数器进行控制,
它的构造方法为 CyclicBarrier(int parties,Runnable barrierAction) 其中,parties 表示有几个线程来参与等待,barrierAction 表示满足条件之后触发的方法。CyclicBarrier 使用 await() 方法来标识当前线程已到达屏障点,然后被阻塞。
Semaphore类似于信号量
CAS compare and swap—原子操作
https://blog.csdn.net/wenwen360360/article/details/77715421
https://blog.csdn.net/llllllkkkkkooooo/article/details/116705320?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-0.no_search_link&spm=1001.2101.3001.4242
Unsafe类+自旋实现,Unsafe类是一个提供硬件级别的原子操作,自旋避免了线程切换以及用户态内核态切换所造成的损失
是乐观锁的一种实现机制,
悲观锁就是担心别人修改,每次使用都会将资源锁起来,导致其余挂起。
CAS是一种无锁原子算法,它的操作包括三个操作数:需要读写的内存位置(V)、预期原值(A)、新值(B)。仅当 V值等于A值时,才会将V的值设为B,如果V值和A值不同,则说明已经有其他线程做了更新,则当前线程则什么都不做。最后,CAS 返回当前V的真实值。
【也就是说每次都会比较线程内存V与主内存A中的值是否相同,如果相同,则将最终的结果B写到主内存中,如果不相同,则一直自旋等待】
**缺点:**多个线程会出现自旋等待,消耗一定的cpu资源。
使用场景:AtomicInteger;所有Atomic开头的原子类,内部都用到了CAS;