线程与进程

线程的5大状态

新建-------就绪--------运行---------阻塞(线程被暂停)-----------死亡
在这里插入图片描述
阻塞状态:

  1. 等待阻塞:执行wait()方法之后,使本线程进入等待阻塞状态,【会释放对象锁】因此被唤醒之后需要重新等待锁资源。
  2. 同步阻塞:线程在获取synchronized同步锁失败,会进入同步阻塞状态
  3. Thread.sleep()【不会释放锁资源】或join()或I/O请求时,线程会进入阻塞状态,在处理结束之后,会重新进入就绪状态。

常用方法:

  1. wait()与notify()属于Object方法,wait()会释放锁资源,并让出cpu;
  2. sleep()会阻塞当前线程,但是不会释放锁资源,只会让出cpu;
  3. yeild():属于一个声明,当本线程重要部分已运行完之后,可以切换给其他线程来运行。这个方法只是对线程调度器的一个建议;
  4. 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

  1. 同步代码块
	class  ThreadRunnable implements Runnable {
		@Override
		public void run() {
			synchronized (this){  //对代码块添加锁,保证线程同步
				age++;
				System.out.println(Thread.currentThread().getName() + "----" + age);
			}
		}
	}

  1. 同步方法:加synchronized修饰
class  ThreadRunnable implements Runnable {
		@Override
		public synchronized void run() {
				age++;
				System.out.println(Thread.currentThread().getName() + "----" + age);
		}
	}

synchronized修饰普通方法与修饰静态方法与修饰类的区别
修饰方法:锁的是当前对象
修饰类:锁的是类,该类的所有对象公用一把锁

Lock 实现类之一--------ReentrantLock
底层是AQS+CAS实现,在并发量小的时候性能不如synchronized,

  1. 可重入锁,
  2. 需手动获取和释放锁lock.lock()或lock.unlock()
  3. 支持获取锁的公平性和非公平性-----所以性能相对差一点
    concurrent包下的一些东西
    volatile
    **作用:**仅能实现对原始变量(int long)操作的原子性,所有对volatile变量的读写都会直接刷到内存。
    **本质:**告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取

synchronized与ReetrantLock的区别

  1. 两者都是可重入锁
  2. synchronized是基于JVM层面的,释放锁的方式:正常释放或者是抛出异常,由jvm释放
  3. synchronized是悲观锁
  4. 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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值