java多线程基本知识总结

时间:2023-02-26 18:36:17

1.synchronized:它是一个互斥锁(独占锁),是对某个对象加锁,而不是对某段代码块加锁,当一个线程获的。

这个对象锁后,其它线程只能进入等待,直到获取锁的线程执行完代码释放锁后,其它线程才能再次获取这把锁。

2.synchronized锁定的对象:

     (1)可以是我们自己创建的对象:

      例如:下面代码我们锁定的对象就是obj

      private Object obj=new Object();
       public void control(){
    synchronized(obj){
    方法代码
    }

      }

    (2)对象自身(this):当我们new一个对象时,指向自身,把自身对象锁定,每次执行对象的方法时锁定自己  

      public void control(){
    synchronized(this){
    方法代码
    }

      }

    这种写法类似于在方法上加synchronized

    public synchronized void control(){

        方法代码

      }

3.synchronized用在static方法上面时,锁定的是这个类的Class对象
  例如:
  public synchronized static void control(){
方法代码
  }
  相当于
  synchronized (类.class) {
方法代码

  }  

4.不加锁的方法可能会发生线程重入,即线程执行到一半被另一个线程给打断,从而出现数据一致性问题,

解决办法就是加锁,使的线程无法被打断。

5.在一个类中同步方法和非同步方法是可以同时运行的,原因为同步方法需要获取锁,非同步方法不用获取锁,

不会形成等待锁的关系。

6.一个同步方法可以调用另一个同步方法,原因是因为synchronized锁是可重入锁,当一个线程拥有某个

对象的锁时,我们再次去申请同一把锁的时候是可以获得该锁的,只是我们在锁上会再次加上一个一。

7.脏读:出现的原因是写上加了锁,读上没有加锁,解决办法为读上也加上锁。

8.死锁:我们定义了俩个对象,在有俩个方法中分别按照不同的顺序锁定这俩个对象,这样就会形成死锁。

9.子类可以调用父类的同步方法。

10.如果我们的程序发生了异常,默认情况下ynchronized是会释放锁的,在并发时候当多个线程共享一个

资源时,如果发生异常,锁被释放,这样多个线程就会同时进入同步方法,发生数据的不一致问题,所以我们要

小心的处理异常,如果不希望在发生异常释放锁,我们可以在try catch中处理。

11.volatile:是一个变量在多个线程之间可见

    (1)不使用volatile会出现的情况:假如有俩个线程A,B,他们两共享一个变量叫做share,这个变量位于

    一个对象的堆内存中(主内存中),当线程A运行时,会把share的值从主内存中读到A的工作区(缓存区),

    在运行中就会直接使用这个copy的值,就不会再去读取主内存的值了,线程B也会将share的值从主内存中

    读到自己的工作去(缓存区),当B修改了share的值后,它发现值已经修改了就会把值写回到主内存中,

    由于线程A不再去主内存中读取share的值,所以它不能发现修改后的值。

    (2)当我们使用volatile后:一旦值发生变化就会提醒其它线程你们缓存区的值已经过期了,请你们从

    主内存中再读取一次(不是每次都去主内存中读取),即写完进行缓存过期通知。

12. volatile只保证可见性,synchronized既保证可见性和原子性,如果只是保证可见性我们要用volatile,因为

它的效率要高于synchronized。

13.我们如果只是进行简单的数字运算,我们可以使用AtomicXXX类,因为它本身的方法都是原子性的,但不能保证

多个方法连续调用是原子的。

例如:AtomicInteger count=new AtomicInteger(0);
       for(int i=0;i<1000;i++){
    //if(count.get()<1000)
    //它俩中间的代码别的线程是可以打断执行的
    count.incrementAndGet();

}

14.使用锁的注意事项:

(1)同步块的代码越少越好

(2)锁定某个对象o,如果o对象的属性发生变化,不影响锁的使用,但如果o变成另一个对象,则锁对象发生改变

所以应该避免将锁定对象的引用变成另一个对象

(3)不要把字符串常量作为锁定对象,假如当我们用到一个类库时,该类库代码锁定了字符串,但是因为

        读不到源码,而我们在自己的代码中也锁定了这个字符串,这个时候可能会发生死锁阻塞

15.线程间通信:

(1)wait和notify:使用这个他们时必须进行锁定,如果没有锁定就不能调用这个对象wait和notify方法,

        当我们调用这个锁定对象的wait方法,前线程进入等待状态,同时释放锁,别的线程可以执行,只有在

        调用这个对象的notify方法,会启动在这个对象上等待的某一个线程,或则notifyAll启动所有在这个

        对象上等待的线程,因为notify不会释放锁,而我们项要让另一个线程wait后面的代码继续,我们必须调用

        wait方法释放锁,使自身进入等待状态,释放锁,同样我们想让刚才notify后的代码继续执行,我们需要在一个

        wait线程中调用notify方法唤醒线程

        (2)使用latch(门栓)代替wait,notify,来进行通知,我们使用countdownlatch的await和countdown方法替代

        wait和notify方法,好处是它不涉及锁定,当count的值为0时当前线程继续执行,当不涉及同步,只是涉及线程

        之间通讯时,应该考虑使用CountDownLatch,CyclicBarrier,Semaphore,因为synchronized+wait/notify太重。

16.reentrantlock:

(1)可重入锁,当程序发生异常时,JVM会自动释放synchronized的锁,但reentrantlock必须手动释放锁,

        所以我们一般将释放锁的代码放到finally块中。

(2)reentrantlock:它的tryLock方法,可以进行尝试锁定,如果获得了锁返回结果为true,没有获取锁结果为false,

        所以我们可以根据这个返回结果写我们的处理逻辑,而synchronized没有获取锁,他会一直等待下去,

        同时我们还可以通过tryLock来指定我们等待的时间。

(3)reentrantlock的lockInterruptibly方法,,可以对线程的interrupt方法做出响应,即interrupt标志为true,

        则立刻抛出InterruptedException异常,因此必须捕捉该异常

(4)Reentrantlock:是公平锁,就是哪个线程等待这把锁的时间越长,谁先获取锁,synchronized为非公平锁,

        哪个线程获取锁是根据根据线程调度器决定的。

ReentrantLock lock=new ReentrantLock(true)这样就声明公平锁,公平锁效率要第一下。

        参考自:马士兵高并发编程