盒子
盒子
文章目录
  1. Java并发的三大概念
  2. volatile

Java多线程

创建线程的两种方法:

  • extends Thread
  • 1
    2
    3
    4
    5
    6
    7
    class MyThread extends Thread{
    @Override
    public void run(){

    }
    }
    new MyThread().start();

  • implements Runable
  • 1
    2
    3
    4
    5
    6
    7
    class MyThread implements Runable{
    @Override
    public void run(){

    }
    }
    new Thread(new MyThread()).start();

    第一种和第二种的区别:

    1. 第一种扩展性不好(Java不允许多重继承)
    2. 第一种是独享资源,第二种是共享资源

    当启动多个线程时,即使时同一个任务,每个任务的资源(成员变量)都是相互独立的,这会导致一些多线程不应该出现的问题,如:本来打算出售10张票,如果采用第一种方式开了3个线程,就会出售30张票,而第二种方式不会出现这种情况

    run or start

    当创建了线程实例调用start时会真正启动一个线程并执行run方法,但直接调用run方法知识单纯地执行run方法而已

    上锁无效:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private void createThread(){
    lock=new Object();
    for(int i=0;i<10;i++)
    new MyThread().start();
    }
    class MyThread{
    @Override
    public void run() {
    synchronized(this){
    for(int i=0;i<500;i++)
    MainTool.log(Thread.currentThread().getName()+" i:"+i);
    }
    }
    }

    上锁有效:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    synchronized(MyThread.class){
    for(int i=0;i<500;i++)
    MainTool.log(Thread.currentThread().getName()+" i:"+i);
    }
    //or
    Object lock=new Object();
    synchronized(lock){
    for(int i=0;i<500;i++)
    MainTool.log(Thread.currentThread().getName()+" i:"+i);
    }

    每新建一个线程都会创建一个不同的对象,使用this即MyThread对象作为锁,由于不是同一把锁所以失效

    多线程状态

  • Thread.yield()
  • 将该线程转为就绪队列,主动让CPU重新分配进入运行状态的线程

  • lock.wait()
  • 将该线程任务置于等待队列中,直到被其它线程notify/notifyAll后才会恢复

  • myThread.join()
  • 阻塞执行myThread.join()方法所在的线程,并让myThread线程执行到结束为止,可通过该方法将异步转换成同步

    1
    2
    3
    4
    5
    MyThread myThread1=new MyThread();
    MyThread myThread2=new MyThread();
    myThread1.start();
    myThread1.join();
    myThread2.start();

  • locak.notify()
  • 唤醒了处于等待队列中的线程后,该线程会处于锁定状态,并不能立即进入就绪状态,当唤醒处于等待状态的线程的线程称为唤醒线程执行完当前任务时会释放锁,让处于锁定状态的线程能进入就绪状态

  • myThread.setDaemon()
  • true: 使子线程成为守护线程,没有前台线程可服务时自动结束线程

    false: 即使主线程(前台线程结束了,该子线程仍然在执行,不受管理)

    Java并发的三大概念

    1. 原子性

    2. 可见性

    线程1修改了某个变量的值还没来得及放入到内存中,线程2就直接读取了那个还未更新的变量

    1. 有序性

    处理器为了优化代码会对代码执行顺序重新排列,但会确保不影响最后的执行结果,但遇到多线程,处理器将无法保证最终结果一致,eg:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //线程1
    context = init();
    inited = true;

    //线程2
    while(!inited ){
    sleep()
    }
    handle(context);

    此时若线程1中的1,2条语句对换位置,在线程1中是不会影响最终结果,但考虑到由于inited=true线程2会跳出循环context还未被初始化handle方法就开始被执行。

    volatile

    1. 使用volatile关键字会强制将修改的值立即写入主存;

    2. 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);

    3. 由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

    因此volatile保证了可见性

    volatile不能保证原子性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    for(int i=0;i<20;i++){
    new Thread(new TestThread()).start();
    }
    while(Thread.activeCount()>2){
    Thread.yield();
    }
    MainTool.log("count:"+count);
    class TestThread implements Runnable{
    @Override
    public void run() {
    for(int i=0;i<10000;i++){
    MainTool.log(Thread.currentThread().getName()+" i:"+i);
    locKer.lock();
    MainTool.log(Thread.currentThread().getName()+" count:"+count);
    count=count+4*100*108/123*9;
    locKer.unlock();
    }
    }
    }

    count:631800000

    如果不加锁输出的值<=631800000,因为存在以下情况:

    当线程1取到count的值准备加1的时候,被线程2阻塞后线程2拿到count加了1,然后线程1开始加1,得到1,但此时count已经为1,而线程1不知道,最终结果本应该是2,却变为1。

    支持一下
    扫一扫,支持Grooter
    • 微信扫一扫
    • 支付宝扫一扫