Java并发编程

Java编发编程

Thread

Thread的几种状态

  • NEW:线程刚创建还没start
  • RUNNABLE: 线程正在JVM中执行
  • BLOCKED: 当前线程正在等待monitor lock Synchronized代码块会让没获得锁的线程进入Blocked状态,Object.wait也会进入BLOCKED状态。
  • WAITING: 等待其他线程唤醒。Locksupport.park会让线程进入waitting状态,Object.wait() Thread.join()
  • TIMED_WAITING: sleep(1000)、wait(1000)指定时间的阻塞线程都是进入这个状态
  • TERMINATED: 线程结束

WAITTING和BLOCKED的区别

BLOCKED是指线程正在等待获取锁;WAITING是指线程正在等待其他线程发来的通知(notify)。

个人理解不知道对不对:Blocked线程在自旋尝试获取锁,Waittint状态在等着其他地方唤醒。???

Thread阻塞/中断 方法

  • sleep: Thread.sleep(4000);线程进入TIMED_WAITTING状态,sleep不会释放锁
  • wait: 只能在同步代码块中使用,调用obj.wait之后进入了waitting状态。wait通过notify/notify唤醒。线程没有引用object调用object.notify/wait等方法会抛出IlleagalMoniterException
  • yield: 让当前线程让掉占用CPU的时间段,重新进入就绪状态和其他线程重新竞争CPU,yield可以暂停当前线程,如果当前线程已经持有锁和sleep一样不会释放锁。
  • join: 可以让线程进入waitting状态
  • await:Lock中Condition中使用,用于阻塞进程和singal配合使用。

Thread唤醒方法

  • notify/notifyAll: object中方法只能在同步代码块种使用,用于唤醒wait
  • interrupt: thread.interrupt会让线程在阻塞的地方抛出interruptException变相的唤醒线程,通知线程做释放资源的工作。
  • singal: Lock中Condition中使用,用户唤醒阻塞进程,和await配合使用。

怎样正确停止线程

  • 线程中run方法执行完之后线程就会销毁,对于一直在循环执行的线程可以通过设置变量来停止线程。
  • 调用thread.interrupt方法停止线程。调用interrupt方法后不会立刻停止线程,需要判断isInterrupt是true/false来决定下一步工作。
  @Override
    public void run() {
        for(int i=0;i<500000;i++) {//只有循环足够多,才能看到线程停止
            if(this.interrupted()) {//用Thread.interrupted有什么不同?
                System.out.println("已经是停止状态了!我要退出");
                //break;  for循环后面如果有语句会继续执行
                try {
                    throw new InterruptedException();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println("i="+(i+1));
        }
    }


wait 和sleep的区别

wait是Object中的方法,sleep是Thread中的方法
wait方法会让出锁,调用notify/notifyAll方法才会重新去竞争锁,sleep则不会让出锁
wait只能在同步代码块中使用,sleep可以在任何地方使用

死锁

线程一拿到A对象的锁,线程二拿到B对象的锁,同步代码执行过程中需要依赖对象B的锁,而依赖A对象的锁,此时就产生了死锁。

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized (A) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B) {
                System.out.println("thread1...");
            }
        }
    }
});

Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized (B) {
            synchronized (A) {
                System.out.println("thread2...");
            }
        }
    }
});

t1.start();
t2.start();

怎么避免死锁:尽量不要在一个锁内尝试去拿另一个对象的锁。

线程安全 && 线程不安全

线程安全:多线程访问一段代码不会产生不确定的结果,称为线程非安全。线程安全都是由全局变量和静态变量引起的。

全局变量:存在于堆内存中,和对象同时创建,堆内存被所有线程共享。

静态变量:被static关键字修饰的变量,只要类存在变量就可以访问到。无论类创建了多少个对象,类变量只有一个,被static修饰的变量存放在方法区中。方法区中的变量所有的线程都可以访问

局部变量:每个线程中都包含一个栈内存区域,局部变量存储在此区域,栈内存区域每个线程是私有的,其他线程无法访问到,当方法执行完成后会释放掉内存,所以局部变量是线程安全的。

通过atomic包保证线程安全

atomic包下常用的有AtomicInteger,AtomicBoolean,AtomicRefrence几种,通过调用类中提供的api来修改对象(变量)可以保证修改操作的原子性,从而保证线程安全,是实现线程安全最轻量级的方法。

atomic内部是通过CAS+validate来保证原子性。

   //AtomicInteger AtomicBoolean
    private volatile int value;
    
    
   //AtomincRefrence
    private volatile V value;
    
    //通过调用Native方法保证修改值的原子性 获取失败时会自旋
      public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

validate

validate只能保证内存可见性,但是不能保证原子性。在一定情况下使用volidate可以提高性能,因为使用validate之后多线程访问变量不需要切换线程。

CAS

CompareAndSwap(比较并交换)通过比较某个内存地址中的值是否等于预期的值,如果相等则将新的值付给该内存,不相等则不赋值。当多个线程对同一个内存地址进行CAS操作时只有一个线程能成功,其余的会失败,失败的线程会重新尝试,通过硬件支持实现。

Synchronized

Synchronized每次进入临界区时都会考虑拿不到锁的情况,拿不到锁会让线程进入阻塞状态,在多线程竞争下频繁的加锁和释放锁操作会引起性能问题,Synchronized在思想上属于悲观锁。

CAS实现属于乐观锁的思想,每次都假设没有冲突,如果有冲突了就重试直到成功为止。

Synchronized分为对象锁和类锁两种。

对象锁

每个对象都只有一把锁,当线程A执行某个对象的同步方法时,此时该对象唯一锁被线程A占用,线程B如想要进入该对象的同步方法执行则必须等线程A释放锁,但是线程B可以访问该对象的非同步方法。使用Synchronized关键字修饰加锁和解锁的过程都是由JVM控制,不需要上层关心。

//对象锁两种实现方式
 public synchronized void objectLock() {

  }

 public void objectLock() {
     synchronized (this) {
            
      }
  }
类锁

类可以创建多个实例,当类第一次被加载时会创建class对象,这个class对象时唯一的,该类的所有实例共用同一个class对象,类锁使用的是class对象的锁,所以类锁只有一把。

	//类锁的两种实现方式
  public static synchronized  void staticLock() {

    }

    public void staticLock1() {
        synchronized (MyClass.class) {

        }
    }
Synchronized底层实现和优化

Synchronized是Java的关键字,通过在编译时期在class文件中插入moniterEnter/moniterExit来控制了访问逻辑。

Synchronized属于可重入锁(同一线程多次进入同步代码块不需再次获取锁)。同时刻一个对象的监视器只能被一个线程获得,获得对象监视器的线程可以进入到同步代码块种执行。没有获取对象监视器的线程会进入Blocked状态,当对象的监视器被释放时,其他线程就有机会获取到该监视器。

jdk1.6在每个Java对象头中markword字段中记录了当前锁的状态,通过控制锁的状态来提升Synchronized性能。

锁的几种状态:无锁,偏向锁,轻量级,重量级。这几种状态会随着线程竞争的激励程度来膨胀(JVM控制),但是不能降。

  1. 无锁: 默认是无锁状态。

  2. 偏向锁:大多数情况下锁不存在竞争,而是总是有同一线程获得,为了让获得锁的代价更低引入了偏向锁。当线程A进入同步代码块时会将该线程A的ID写入改对象头部信息中。每个线程在进入代码块前都会先检测当前对象锁是否被占用,如果被占用则比较对象中存储的线程的ID,如果Id和之前存储在头部中的Id相同,则证明是同一线程进入不再进行加锁操作,直接进入代码中执行。如果不是同线程,则此时证明发生了线程间竞争,偏向锁会膨胀成为轻量级锁,让线程之间进行竞争。偏向锁加锁和解锁只有一次,第一个线程进入时加锁,但是退出同步代码块时并不会解锁,只有当竞争线程出现时才会解锁。 偏向锁的释放的开销相对来说挺大,所以偏向锁适用于大部分都是同一个线程执行的情况。

  3. 轻量级锁:有竞争线程出现时偏向锁膨胀为轻量级锁。此时有多个线程在竞争同一把锁,轻量级锁每次进入和退出代码块都需要加锁和解锁操作。竞争线程发现锁已经被其他线程占用后,会自旋(一直尝试获取锁)长时间的自旋会消耗CPU资源,所以经过一段时间自旋后如果还拿不到锁,会继续膨胀成重量级锁。

  4. 重量级锁:当自旋一定时间后,会膨胀为重量级锁。会把一直未取得锁的线程阻塞,直到线程释放锁后才会被唤醒继续执行,因为线程处于阻塞状态当其他线程释放锁时并不会立刻响应。

总结:
jdk1.6版本Synchronized关键字加入锁的状态判断后性能有很大提升。
偏向锁引入提高了于大部分只有一个线程访问临界区时的性能。1.6之前未拿到锁的线程处于Blocked状态,锁放释放后需要唤醒Bloked的线程,轻量级锁在一直自旋,锁被释放后会其他线程会立刻得到锁。重量级锁则解决了当同步代码块中执行耗时工作其他线程迟迟拿不到锁一直自旋引起CPU的消耗的问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值