java基础之一多线程

一、进程和线程

进程:操作系统同时执行多个程序或任务,单个程序或者任务即为一个进程;
线程:一个进程中有多个不同的执行顺序流(程序流),即每个不同的执行程序流即为一个线程;

二、多线程的优点

  • 相比多进程而言,系统会给每个进程分配一个独立的内存空间,但是线程是进程的一个执行路径,即多个线程可以共享一个进程中的内存空间;
  • 由于多进程之间内存是相互独立的,所以多进程不能共享内存。

三、线程创建和启动

  1. 继承Thread类,重写run()方法,创建子类实例调用start()方法,启动线程并执行run()方法的线程执行体。
  2. 实现Runnable接口,实现run()方法(无返回值),创建实现类的实例,创建线程对象,将实现类的实例作为线程对象的target,调用线程对象的start()方法并执行run()方法;
  3. 实现Callable接口,实现call方法(有返回值),步骤同实现Runable接口;
两者区别:
  • 由于java不支持继承多个直接父类,所以实现Runable接口或者Callable接口有更好的扩展性;
  • 实现Runable接口,多线程之间可以共享接口实现类的实例变量(共享资源);

四、线程状态


五、线程方法

  1. join()方法:线程A在执行时,线程B调用join方法,等线程B执行完,线程A继续执行;
  2. sleep()方法:Thread.sleep(),让当前线程进入阻塞状态,等待时间到进入就绪状态等待获取cpu执行权,不会释放当前的锁对象,必须捕获异常,可以抛出IIlegalThreadStateException异常;
  3. yield()方法:线程让步,正在运行的线程A调用yield方法,会暂时让出执行权,给和线程A自己优先级一样或更高的线程,这时线程A会立即进入就绪状态,等待cpu重新分配资源,无声明的异常;

六、线程同步

1.同步代码块:建议将需要多个线程同时访问的共享资源 作为锁的对象,其中obj为共享资源对象,语法如下:

synchronizied(obj){
//线程执行体
}

2.同步方法:锁住当前对象,this

3.使用Lock或ReadWriteLock接口的实现类ReentrantLock和ReentrantReadWriteLock;

同步代码块和Lock接口区别:
  • 同步代码块和同步方法只是隐式的对当前对象进行同步,要求加锁和释放锁都必须在同一个代码块中或者方法中,没有明显的释放锁方法
  • Lock接口是控制多个线程同时访问共享资源的工具。可以显式的对共享资源进行加锁释放锁,还提供了比同步代码块和方法更多的方法,例如非块代码块tryLock等。
  • Lock提供一个可以多线程并发读,只能一个线程写的ReentrantReadWriteLock类,即最大的特点:为读和写提供了锁的功能
同步变量:volatile,对指定变量进行线程同步,是对线程内存和“主”内存进行同步,当某个线程修改volatile修饰的变量时,正在共享的该变量的线程变量也会随着改变; 但是synchronizied关键词是锁定某个监视器来同步变量的,但是这个相对于锁定一个变量消耗资源更多。

线程安全:多个线程执行完得到正确的结果和线程对象正常化

1、使用线程组可以保证线程安全,原理是: java默认创建的线程都是属于系统线程组,而同一个线程组的线程是可以相互修改对方的数据的。但如果在不同的线程组中,那么就不能“跨线程组”修改数据,可以从一定程度上保证数据安全。

锁的释放:
  1. 正常执行结束;
  2. 遇到break,return方法;
  3. Error或Exception;
  4. 程序执行锁对象的wait方法,当前线程暂时释放锁的资源,进入阻塞状态;
死锁:
当两个以上的线程互相等待对方释放锁的资源,会发生死锁,进入阻塞状态,程序正常执行,但是无异常出现。

七、线程通信

  1. 传统方式
调用锁对象wait(),notify()和notifyAll(),这些事object类的方法
  • wait()方法,暂停当前线程执行,进入阻塞状态,并释放当前的锁,不需要捕获异常,sleep不会释放,必须捕获异常
  • notify()方法:唤醒当前正在等待通知的线程之一,重新获得cpu分配资源的机会;
  • notifyAll()方法:唤醒当前正在等待通知的所有线程,重新获得cpu分配资源的机会
  1. 使用condition类
  2. 使用阻塞序列

八、线程池

作用:
1.减少创建和销毁线程上所花的时间和消耗的内存空间,这样可以解决资源不足问题,如果不使用线程池,大量的线程创建会消耗内存。
2.减少多个线程之间导致的cpu线程过度切换。


实现步骤:
  1. 系统启动,并创建指定数量的线程数池;
  2. 程序传入实现runable或callable接口实现类的实例对象,会启动一个空闲线程执行run或call方法;
  3. 线程结束后,进入线程池等待下次被调用,并不会死亡。

九、ThreadLocal类

1.作用:ThreadLocal类将需要并发访问的资源复制给多份,每个线程都会有自己的资源,主要用于隔离多个线程之间的资源共享

2.与Lock区别:
Synchronize和Lock主要从资源安全机制解决多线程并发访问共享资源,即当一个线程在操作某个共享资源时,先暂时锁住该对象,等待该线程执行完后或者释放被锁的对象时,然后下一个线程则可共享该资源;
ThreadLock类则是隔离了多线程并发访问共享资源,这样就避免了对共享资源的竞争访问,也就不需要对多个线程进行同步了

3.使用场景:
当需要多个线程通过共享资源达到线程通信时则使用同步代码块或者Lock;
当需要隔离各个线程之间的共享冲突,则使用ThreadLocal类。

十、同步和互斥


同步:直接制约关系,指多个线程为了合作完成任务,必须严格按照规定的先后顺序来运行。
互斥:间接制约关系,指多个线程执行时,当第一个线程拥有该资源时,其他线程必须等待该线程执行完再获取执行权

总结:

  1. 互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
  2. 同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
  3. 同步其实已经实现了互斥,所以同步是一种更为复杂的互斥。
  4. 互斥是一种特殊的同步。
高并发注意的问题:
1.同步调用时应该考虑锁的性能损耗,能用无所数据结构,就不要用锁,能锁区块就不要锁住整个方法体,能锁住对象锁,就不要用类锁。
2.加锁时,对个多个资源,数据库表,对象加锁,尽量保持加锁顺序,否则容易出现死锁。
3.并发修改一条数据库记录时,如何保证数据丢失问题?1.应用层进行加锁,2,缓存加锁,3,数据库使用乐观锁(冲突概率不能超过20%)通过加version进行更新,4使用悲观锁
4.volatile解决多线程内存不可见问题,对于一写多读,可以解决变量同步问题,如果多写也无法解决线程安全问题。如果想取回count++数据,要使用AtomicInteger count=new AtomicInteger();count.addAndGet(1);这里如果使用jdk8,推荐使用LongAdder对象,可以减少乐观锁的重试次数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值