多线程基础知识归纳总结

        

part A进程、线程和多线程

 

进程:正在运行的程序

线程:进程中独立运行的子任务

运行main函数的线程是主线程,也就是main线程,该线程的名字是main,跟main方法无关。

 

实现多线程的方式

1.继承Thread类(Thread类实现了runnable接口)局限于java单继承,不能继承其他类,所以一般都使用第二种方法。

2.实现runnable接口

 

多线程代码的运行结果与代码执行顺序或者调用顺序无关。线程的调用是随机的。

 

线程安全问题:多个线程对同一个对象中的同一个实例变量进行操作时会出现值被修改,但修改不同步的情况。

currentThread() 方法,返回一个正在被执行的线程

 

this.getName()方法被Thread-0调用

 

线程中的各种方法

getName() 返回正在运行的线程的名字

isAlive() 判断是否处于活动状态,线程从创建到死亡之前都返回true,一旦线程执行完,线程进入死亡状态就是false

sleep()   休眠线程,毫秒内让当前执行的线程休眠,时间过了就会自动,这个方法是放锁的。

getId()   获得执行线程的id

suspend() 暂停线程,可以用resume()回复线程执行.但是这两个方法是会对公共的同步对象独占,其他线程就无法访问这个对象,不同步,最后作废。

yield()   礼让线程,让其他的任务先执行,导致本任务用时变长

setDaemon(boolean on)  守护线程随着主线程停止而停止守护线程

join()           等待该线程终止。加入线程

wait()    线程等待

notify()     线程唤醒

notifyAll() 线程唤醒

线程优先级

线程优先级分为1-10十个等级,默认5,超出范围会抛出一个IllegalArgumentException

getPriority()

setPriority()

优先级是可以继承的;例如线程A在运行中启动线程B,线程B的优先级和A是一样的。

高优先级的线程大部分先执行完,但不代表高优先级就要全先执行完。优先级和代码执行顺序没关系。优先级具有随机性

 

终止线程的三种方法:

1.等run方法完成

2.使用stop强行终止(不被推荐,因为这个方法不是线程安全的)

3.使用interrupt来中断线程(这个方法被执行的瞬间总是再运行多几句,所以可以通过先sleep()然后interrupt();当然还可以通过interrupted(),return来停止线程)

 

part B 多线程同步

 

实现线程的方法中的变量(局部变量)不存在非线程安全问题,是线程安全的。

实例变量和静态变量是非线程安全的。

 

 

一、Synchronize同步方法结论

1.A线程先持有对象的Lock锁,B线程可以以异步的方式调用OIbject对象的非Synchronize类型的方法。

2.A线程先持有对象的Lock锁,B线程调用OIbject对象的Synchronize类型的方法需要等待,就是同步。

3.关键字Synchronize拥有锁重入的功能,就是说当线程得到一个锁对象的时候,再次请求此对象锁的时候可以再次得到对象的锁,

即是说,在Synchronize内部调用本类的其他Synchronize方法时候,是一定能得到锁的。再次获得自己的内部锁。

4.当存在父子继承关系时,子类可以通过“可重入锁”调用父类的同步方法。

5.多个线程执行的代码其中有一个在运行的时候出现异常,其持有的锁会自动释放,其他的线程会运行。

6.同步不可以被继承,父类的方法中有Synchronize,子类没有Synchronize,则会导致调用子类方法的时候会有非同步安全问题

Synchronize方法的弊端:从运行时间上来看,运行时间太长,解决方法:使用同步代码块

Synchronize(object){......}

 

二、Synchronize(对象监视器object{..}同步代码块:

Synchronize同步方法有两个作用:

         1)对其他Synchronize同步方法或者Synchronize(this)同步代码块调用呈阻塞状态,

         2)同一时间只有一个线程可以执行Synchronize同步方法代码。

Synchronize(this)同步代码块有两个作用:

         1)对其他Synchronize同步方法或者Synchronize(this)同步代码块调用呈阻塞状态,

         2)同一时间只有一个线程可以执行Synchronize(this)的代码。

Synchronize(x)

         1)多个线程持有同一个对象监视器object的时候,同一时间只有一个线程可以执行Synchronize(对象监视器)的代码块

         2)持有同一个对象监视器object的时候,同一时间只有一个线程可以执行Synchronize(对象监视器)的代码块

 

Synchronize(x)的优点在于,如果一个类里有很多的Synchronize方法,这时候虽然会同步,但是会受到阻塞,影响效率

但如果是同Synchronize(非thisd的对象),则Synchronize(非thisd的对象)代码块中的程序与同步方法是异步的,其不会与其他锁不一样的抢this锁,可以提高效率

换句话说:如果Synchronize的锁对象不是同一个对象,那么就是异步调用了,不是同步调用,结果当然是异步的。

 

Synchronize(x)

1)当多个线程同时执行Synchronize(x){}的时候同步代码块呈同步效果

2)当其他线程执行x对象中的Synchronize同步方法时候呈同步

3)当其他线程执行x对象中的Synchronize(this)方法时候呈同步

 

三、volatile关键字

3.1 关键字volatile的主要作用就是使变量在多个线程间可见。强制的从公共内存中读取变量的值。

3.2 volatile和synchronize比较?

1)关键字volatile是线程同步的轻量级实现,所以性能肯定比synchronize好,并且volatile只用来修饰变量,而synchronize修饰方法代码块。

2)多线程访问volatile不会发生阻塞,而synchronize会发生阻塞

3)volatile能保证数据的可见性,但不能保证原子性;而synchronize可以保证原子性,也可以间接的保证可见性,因为它会将私有内存和公共内存中的数据做同步,而volatile直接强制的从公共内存中读取变量的值。

4)volatile解决的是变量在多个线程间的可见性,而synchronize关键字解决的是多线程直接访问资源的同步性。

 

3.3  volatile关键字的非原子特性:保证可见性,但不具备同步性,也就是不具备原子性。

线程工作过程;如使用i++操作的时候,应该加上synchronize锁,且锁对象要是类,加static

Volatile 变量在内存中的工作过程:

         Read和load:在主内存中复制变量到线程工作内存。

         User和asign:修改变量

         Store和write: 将线程工作内存同步到主工作内存。

所以,当同时有多个线程访问修改这个变量的时候就会出现非线程安全问题,需要上锁才可以实现线程同步。

 

四、线程间的通讯

4.1等待/唤醒机制wait()/notify()

Wait()是Object类的方法,只能在线程同步的方法或者同步代码块里调用。执行完会释放当前锁。在wait()结束返回后回到就绪状态,去抢锁,当wait()调用时候没有适当的锁,会抛出一个异常IllegalMonitoirStateException(属于运行时期异常的子类)。不需要try-catch

         Notify()也需要在同步方法或者同步代码块中调用,如果有多个线程在等待,那就由线程规划器随机挑选一个对其发出唤醒通知。在notify()执行完后,当前线程不会马上是否该对象锁,wait()状态的线程也不是马上就获取这个锁,要等待执行了notify的线程将程序执行完退出这个代码块,这个线程才会释放锁,wait()状态的线程才可以获取这个锁。

         为了唤醒全部的线程,可以使用notifyAll()

         Wait(long time) 方法是线程等待而在等待超时的时候自动唤醒。

         通知过早会导致逻辑混乱,wait等待条件发生变化也会导致逻辑混乱

4.2生产者和消费者问题:

         消费者类:如果商店没有商品;那就等生产者生产。wait()

                                     如果商店有,那就取。并且通知生产者取生产。notify()

         生产者:如果商店有商品;那就等待消费者消费 。wait()

                            如果商店没有商品,生产,再叫消费者消费。notify()

4.3 通过管道进行线程间的通讯

1)PipeInputStream和PipeOutputStream

2)PipeReader和PipeWriter

通过下面两个方法产生通讯连接:

PipeInputStream.connect(outputStream) 

PipeOutputStream.connect(inputStream)

 

五、join()的使用

Join()的作用是使得所属的线程对象x正常执行run()方法中的任务,而使得当前 线程z进行无限期的阻塞,等待x线程销毁后再继续执行线程z后面的代码。

         Join()方法剧透使线程排队运行的作用,有些类似同步的运行效果,join和synchronize的区别是:join在内部使用wait()方法进行等待,而synchronize关键字使用的是”对象监视器”原来作为同步。

         Join()和interrupt()方法如果彼此碰到,则会出现异常

         Join(long time)   time是等待的时间。

 

Join(long time)和sleep(long time)的区别?

  1. Join内部使用wait方法实现的,所有它具有释放锁的特点
  2. 而sleep()是不释放锁的。

 

六、ThreadLocal类的使用

ThreadLocal主要是解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局放数据的盒子,盒子中可以存储每个线程的私有数据。

通过get(),set()方法获得/设置变量值,线程各自set值,每个线程还能取出自己的数据。

刚开始没赋值之前get()=null;可以通过创建一个类继承ThreadLocal类,覆盖initialValue()方法,return初始值。

 

七、InheritableThreadLocal类的使用

同样,刚开始没赋值之前get()=null;可以通过创建一个类继承InheritableThreadLocal类,覆盖initialValue()方法,return初始值。要对值进一步处理可以在这个类里面覆盖childValue()方法,直接return “value”;然后输出结果就会在原来的结果后面加上value。

         这个类可以在子线程中取到父线程继承下来的值。可以用来继承thread主线程main的属性值,如果子线程在取值的同时,主线程将InheritableThreadLocal中的值进行更改,name子线程取到的还是旧值,就是说,不同步。

 

八、Lock锁(同步锁,使用上更方便的锁)

8.1  ReentrantLock

         Lock lock = new ReentrantLock();

         lock.lock();

         {……}

         lock.unlock();

8.2 Condition对象

         Lock lock = new ReentrantLock();

         Condition condition = lock.newCondition();

         Condition.await()当前线程进入waiting状态

Condition.await(long time,TimeUnit time )   当前线程进入waiting状态等待时间过了就自动唤醒

Condition.signal() 唤醒一个等待的线程

Condition.signalAll() 唤醒所有等待的线程

         要实现部分唤醒的话可以创建多个condition对象,分别进入等待,分别调用signal.同时说明了使用Condition可以实现顺序执行,只需要使用多个condition对象,逐个进行等待唤醒下一个的操作就可以让业务进行排序规划。

 

注意:在使用condition.await()之前必须要锁上,lock.lock(),否则会报错:IllegalMonitoeStateException监视器违例。

 

8.3 公平锁和非公平锁

公平锁:表示获取锁的顺序安装线程加载的顺序分配,先来先得

非公平锁:这是一种抢占机制,是随机获得锁的,跟公平锁不一样的是先来的不一定能先得到锁,这个方式可能会造成有些线程一直拿不到锁。

获得公平锁:

lock = new ReentrantLock(true);

公平锁的结果是基本呈现有序的状态,一个线程一次,一个接一个,这是公平锁的特点。

获得非公平锁

lock = new ReentrantLock(false);

抢占式获得锁。先启动不一定先获得锁

 

Int getHoldCount()  获取当前锁定lock的线程个数。

Int getQueueLength()  获取等待lock释放的线程个数。

Int getWaitQueueLength(Condition condition)  返回当前condition等待lock释放的线程个数。

boolean hasQueuedThread(Thred thred)  查询指定的线程是否在等待获取此锁

boolean hasQueuedThreads()  查询是否有线程正在等待获取此锁定

boolean hasWaiters(Condition condition) 查询是否有线程正在等待与此锁有关的condition条件

boolean isFair() 是否是公平锁

…….

8.4  ReentrantReadWriteLock类

         ReentrantLock具有完全互斥[1]排他的效果,即同一时间只有一个线程在执行lock()后面的任务,虽然保证了线程安全,但是效率降低了。ReentrantReadWriteLock就是用啦提高这个效率的。

         ReentrantReadWriteLock读写锁类,它有两个锁,一个是读操作相关的锁(共享锁),一个的与写相关的锁(排它锁),也就是多个读锁之间不互斥,读锁与写锁互斥,写锁和写锁互斥,在没有线程Thread进行写入操作的时候,进行读取操作的多个线程都可以获取读锁,而进行写入操作的时候,只能在获取写锁才可以进行写入操作,即多个线程可以同时进行读取操作,但是同一个时刻只允许一个线程进入写入。

获取读锁(两个线程调用读锁时候,异步,可以同时执行lock中的代码)

ReentrantReadWriteLock lock = new ReentrantReadWriteLock()

Lock.readLock().lock()

{……}

Lock.readLock().unaLock()

获取写锁

ReentrantReadWriteLock lock = new ReentrantReadWriteLock()

Lock.writeLock().lock()

{……}

Lock.readLock().unaLock()

 

九、定时器Timer

9.1  schedule(TimerTask task,Date time)  在指定的日期执行一次某任务

         Time如果是未来时间,任务会等待到未来执行

         Time如果是过去时间,任务会马上执行

         在多个任务执行的时候,由于TimerTask执行的时候是以队列的方式一个个的被顺序执行,所以,后面的任务执行时间有可能比预期延迟一点。

9.2  schedule(TimerTask task,Date firstTime,long time)  在指定的日期执行一次某任务,然后每隔time周期性无限期执行这个任务

         firstTime如果是未来时间,任务会等待到未来执行

         firstTime如果是过去时间,任务会马上执行

9.3  TimerTask类中的cancel()方法  将自身从任务队列中清除,其他业务不受影响

9.4  Timer类中的cancel()方法  将任务队列中的全部任务清除,进程销毁

         值得注意的是,有时候并不一定会停止执行计划任务,而是正常执行,这是由于Timer类中的cancel()方法没有抢到queue锁,所有正常执行

9.5  schedule(TimerTask task,long delay)  延迟毫秒数后执行一次TimerTask任务

9.6  schedule(TimerTask task,long delay,long period) 延迟毫秒数后执行一次TimerTask任务,再隔一段时间执行任务。

9.7  scheduleAtFixedRate(TimerTask task,Date first,long period) 

         1.这个方法和schedule都会按照顺序执行,所以不用考虑非线程安全问题

         2.它们的区别在于不延时的情况:

                   Schedule方法如果没有延时,那么下次任务的执行时间参考上一次任务开始的时间

                   scheduleAtFixedRate方法如果没有延时,那么下次任务的执行时间参考上一次任务结束的时间

         3. 延时的情况则没有区别,下次任务的执行时间参考上一次任务结束的时间

scheduleAtFixedRate具有追赶性,过去的时间内的任务会追加运行。

 

十、单例模式与多线程(如何使得单例模式遇到多线程是正确的,安全的)

单例模式:

饿汉式/立即加载:1.直接new实例化

2.不给new,则用静态方法取//这种方法拿到的都是同一个对象

Public class MyTest{

         private MyTest myTest = new MyTest();

         private MyTest(){}//不允许new

         public statis MyTest getInstance(){

                   return myTest;

}

}

懒汉式/延迟加载(在调用get()方法才创建实例对象)

Public class MyTest{

         private static MyTest myTest;

         private MyTest(){}//不允许new

         public statis MyTest getInstance(){

                   if(myTest!=null){

         return myTest;

}else{

         myTest=new MyTest();

}

}

}

饿汉式/立即加载在多线程调用的时候,取到的都是同一个对象。

懒汉式/延迟加载在多线程调用的时候,就会取出多个实例的情况,跟单例模式相违背。

 

懒汉式/延迟加载在多线程中使用的解决方案:

10.1  synchronize方法

(1)声明synchronize关键字,对getInstance()方法声明就行。这种方法效率比较低

(2)synchronize同步代码块,效率还时低

(3)针对最重要的代码单独进行同步,将加到同步代码块里,无法解决     

synchronize{

                           myTest=new MyTest();

}

4DCL双检查锁机制,完美解决

         synchronize{

                  if(myTest==null){

                            myTest=new MyTest();

}

}

 

10.2  静态内部类实现单例模式

Public class MyTest{

         private MyTest(){}

         private static class MyTestObject{

                   private static MyTest myTest = new MyTest();

}

         public statis MyTest getInstance(){

                   return MyTestObject .myTest;

}

}

静态内部类实现单例模式遇到序列化对象的时候,使用默认的方式运行结果还是多例。

解决方法就是要在反序列化中使用readResolve()方法

 

10.3用静态代码块实现单例模式

Public class MyTest{

         private static MyTest mytest;

private MyTest(){}

static{

         mytest = new Mytest();

}

pblic statis MyTest getInstance(){

                   return myTest;

}

}

 

十一、其他

11.1线程的状态

        

 

11.2  线程组 ThreadGroud  批量操作线程

         把线程归属到某一个线程组,线程组中可以有线程对象,也可以有线程组,组中还可以有线程。

         其作用就是可以批量的取管理线程或者线程组对象,可以有效的对线程或者线程组对象进行组织。

         ThreadA threadA = new ThreadA();

ThreadB threadB = new ThreadB();

         ThreadGroud threadGroud = new ThreadGroud(“我的线程组名字”); //新建线程组对象

         Thread a = new Thread(threadGroud,threadA);//创建线程的时候分配到线程组

Thread b = new Thread(threadGroud,threadB);

        

         批量停止:group.interrupt()

 

11.3 SimpleDateFormat  非线程安全

         可以用ThreadLocal类来绑定指定的对象,使用这个类来解决多线程环境下SimpleDateFormat  处理错误

11.4  线程中出现异常的处理:

1.对指定线程设置默认的异常处理器setUncaughtExceptionHandler

2.对所有的线程对象设置异常处理器setDefaultUncaughtExceptionHandler

11.5  线程池 Executors

常见的四种线程池:

1)fixThreadPool  正规线程

         这是一个有指定的线程数的线程池,可以控制线程量最大并发数,有核心的线程,里面有固定的线程数量,响应的速度快,超出的线程会在队列中等待。

            Executors.newFixThreadPool() 

2)cacheThreadPool  可缓存线程池

         只有非核心线程,最大线程数很大(Int.Max(values)),它会为每一个任务添加一个新的线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收,如果没有可回收线程,则创建一个新的线程。缺点就是没有考虑到系统的实际内存大小。

            Executors.newCachedThreadPool()

3)singleThreadPool   单线程线程池

  看这个名字就知道这个家伙是只有一个核心线程,就是一个孤家寡人,通过指定的顺序将任务一个个丢到线程,都乖乖的排队等待执行,不处理并发的操作,不会被回收。确定就是一个人干活效率慢。用唯一的工作线程执行任务,保证按顺序执行。

4)ScheduledThreadPool   

         这个线程池就厉害了,是唯一一个有延迟执行和周期重复执行的线程池。它的核心线程池固定,非核心线程的数量没有限制,但是闲置时会立即会被回收。

 

 

这些总结主要是来自我看的一本书《java多线程编程核心技术》.pdf,这本书对初学的来说挺好的,有点基础都能看懂,书中有很多案例,所以看起来会很好理解。

附:《java多线程编程核心技术》.pdf

链接:https://pan.baidu.com/s/1YEuYKsMeoVsfpp1hbgfLWA 密码:2icm

 

 

猜你喜欢

转载自blog.csdn.net/weixin_41987553/article/details/82715861