JUC(并发编程)

实现多线程的几种方式

  • 继承Thread类重写run方法
class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println("线程"+Thread.currentThread().getName()+"正在执行");
        }
    }
}
  • 实现Runnable接口实现run方法
public class MyThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getName() + "正在执行");
    }
}

class Test {
    public static void main(String[] args) {
        MyThread2 thread = new MyThread2();
        Thread t = new Thread(thread);//用实现了runnable类的实例对象作为参数创建Thread类
        t.start();
    }
}
//用lambda表达式
class Test2{
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println("线程" + Thread.currentThread().getName() + "正在执行");
        },"大哥牛逼").start();
        /**为什么用start()而不用run?
         * start另外开辟线程执行 而run是普通地顺序执行
         */
    }
}

Runnable是Java语言实现线程的接口。从本质上说,实现线程的类必须实现该接口。其实Thread就是直接继承Object类并且实现Runnable接口。

  • 线程池创建
  • 实现Callable接口

多线程中的方法

多线程中的方法作用
Thread.yield()当前获取到cpu资源的线程进入就绪状态 重新分配cpu资源
Thread.sleep()暂停当前线程,把cpu片段让出给其他线程,减缓当前线程的执行。如果线程是通过实现Runnable接口来实现的,则不是Thread类,不能直接使用Thread.sleep(),必须使用Thread.currentThread()来得到当前线程的引用才可以调用sleep(),所以要用Thread.currentThread().sleep()来睡眠。在多线程情况下区别特别明显。
t.join()阻塞当前线程,让调用者先执行,直到调用者销毁 不能阻塞同级线程(加上参数指的是调用该方法的线程所在的线程最多被阻塞的时长 0代表无限)
t.setPriority()优先级可以用0到10的整数表示,0为最低优先级别、10为最高优先级别。

当我们的线程 共享一个资源时,就会发生一种情况,线程需要获取到了共享的一个int类型的数据0,需要将它+1,在修改之前其他线程抢到了cpu资源,
将0改成了1,这时原先的线程再抢到了cpu资源就会认为共享的资源还是0,继而数据的结果还是1.这时就需要一个的概念

class MyThread extends Thread {
  public static Integer num = 0;

  public void add() {
    synchronized (this) {
      MyThread.num++;
    }
  }
  /**相当于
   * synchronized(this){MyThread.num++;}
   */
  public synchronized void add2(){
    MyThread.num++;
  }
  /**相当于
   * synchronized(MyThread.class){MyThread.num++;}
   */
  public static synchronized void add3(){
    MyThread.num++;
  }

  @Override
  public void run() {
    add();
  }
}

public class Test {
  public static void main(String[] args) {
    MyThread t = new MyThread();
    MyThread t2 = new MyThread();
    MyThread t3 = new MyThread();
    t.start();
    t2.start();
    t3.start();
  }
}

死锁

死锁是什么?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去;此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。(互持对方所需的资源不放手)
如何避免死锁
1、 注意加锁顺序
2、 减少锁的嵌套

可重入锁

广义上的可重⼊锁指的是可重复可递归调⽤的锁,在外层使⽤锁之后,在内层仍然可以使⽤,并且不发⽣死锁(前提得是同⼀个对象或者
class),这样的锁就叫做可重⼊锁。简单的说,就是某个线程获得某个锁,之后可以不⽤等待⽽再次获取锁且不会出现死锁。

常见的可重入锁

SynchronizedReentrantLock都是可重⼊锁。

可重入锁的释放

同⼀个线程获取同⼀个锁,状态值state会累加,假设state累加到了2,每释放⼀次锁会减1,只有当状态值state减到0了,其他线程才有机
会获取锁。也就是说,state归零才是已释放锁的标致。

锁升级的过程(锁膨胀)

偏向锁

当锁偏向某个线程时,该线程再次获取锁时⽆需CAS,只需要⼀个简单的⽐较就可以获取锁,这个过程效率很⾼。

轻量级锁/自旋锁(一种CAS锁)

这个过程并没有把线程阻塞挂起,而是让线程空循环等待,串行执行。轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。

重量级锁

线程A获取到了锁,线程B因为没有获取到锁然后挂起⾃⼰,等待线程A释放锁后唤醒⾃⼰。线程的挂起/唤醒需要CPU切换上下⽂,此过程代
价⽐较⼤,因此称此种锁为重量级锁

锁膨胀

偏向锁运行在一个线程进入同步块的情况下,如果这时第二个线程加入了锁竞争当中,那么就会发生释放锁,偏向锁变为无锁的状态,进而升级为自旋锁/轻量级锁,这个过程并没有将线程阻塞起来,而是没有获取到锁资源的线程在旋转等待锁释放,如果等待时间过长,轻量级锁会升级成为重量级锁,没有竞争到锁资源的线程将自己挂起,等待锁被释放将自己唤醒,此过程代价较大。

CAS机制(Compare-And-Swap)

直译就是对比交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。   简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。(内存值V、预期值A与修改值B。只有当预期值A与内存值V相同时,才会将内存值替换成B。否则,会进入下一轮循环中。)
cas操作需要三个参数(内存中的值,预期的值,修改的值)

乐观锁

乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。(cas方式为乐观锁)

悲观锁

悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。

CAS的使用

JAVA中为我们提供了AtomicInteger原子类(底层基于CAS进行更新数据),不需要加锁就能在多线程下实现数据一致性。

public static AtomicInteger count = new AtomicInteger(0);
    public static int count2 = 0;
    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < 1000; i++) {
            threads[i] = new Thread(()->{
                for (int j = 0; j < 10000; j++) {
                    count.incrementAndGet();
                    //count2++;
                    System.out.println(Thread.currentThread().getName()+" the number is:"+count);
                }
            });
        }
        for (Thread thread:threads)thread.start();
        for (Thread thread:threads)thread.join();
        System.out.println(count);

CAS存在的问题

  • ABA问题:就是在比较的时候,预期的值与现在的值是相同的,然后进行值的修改,其实这样的是有问题的,就比如首先a=0,经过某些操作a=0=>1=>0,a变成了1然后再变回了0,但是比较时认为它从来没有变过。就比如说你女朋友和你分手后,又谈了几个男朋友,再回来找你,女朋友还是你女朋友,但其实这个女朋友是发生改变过的。
    解决方法:同时设置一个版本号,改变过就给版本+1
  • 开销问题:如果长时间循环对CPU的开销很大。
  • 只能保存一个共享变量的原子操作
    这个也可以解决,就是通过这个类AtomicReference。把需要保证原子操作的多个共享变量封装成一个类,然后创建对象作为AtomicReference的参数就可以,改的时候直接换对象就行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值