多线程

了解多线程

并发和并行

进程和线程



  • 总结

多线程的实现方式--继承Thread

  • 实现步骤
package com.thread;

public class MyThread extends Thread{
    @Override
    public void run(){
        //run()里面的代码就是线程开启之后执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开始了"+i);
        }
    }

}

package com.thread;

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();//线程1
        MyThread myThread1 = new MyThread();//线程2
        myThread.start();//开启线程1
        myThread1.start();//开启线程2
    }
}


我们从一个线程的执行可能开不出来什么,什么我们同时开启了2个线程。可以看到这2个线程在并发交替执行

2个小问题

多线程的实现方式--实现Runnable接口

Thread构造方法里面传递的参数,表示线程执行对应myrnnnable的run 方法

  • 实现步骤
package com.runnable;

public class MyRunnableTest {
    public static void main(String[] args) {
        //创建了一个参数的对象
        MyRunnable myRunnable = new MyRunnable();
        //创建了一个线程的的对象并把参数传递给它
        Thread thread = new Thread(myRunnable);
        //开启线程

        //创建并执行线程2
        thread.start();
        MyRunnable myRunnable1 = new MyRunnable();
        Thread thread1 = new Thread(myRunnable1);
        thread1.start();
    }
}

package com.runnable;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //表示线程启动后执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开始了"+i);
        }
    }
}

多线程的实现方式--实现callable接口


package com.callable;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启后需要执行里面的cll方法
        MyCallable myCallable = new MyCallable();
        //Thread thread = new Thread(myCallable);不能直接将myCallable传递给Thread

        //1.FutureTask的泛型和MyCallable的泛型相同
        //2.将MyCallable传递给FutureTask
        //可以获取线程执行结束之后的结果
        FutureTask<String > future =  new FutureTask<>(myCallable);
        //将FutureTask传递给Thread
       
        Thread thread = new Thread(future);
        thread.start();
        //获取线程执行结束的结果
        final String reason = future.get();
        System.out.println(reason);
    }
}

package com.callable;

import java.util.Objects;
import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {//泛型类型表示返回值的数据类型
    //返回值表示线程运行结束之后的结果

    @Override
    public String call() throws Exception {
        return "hell world";
    }
}

  • ouput:hello world

注意:当我们的get()方法在我们t1.start()线程开启之前执行,此时我们将不可能获取到线程执行的结果。并且由于get()方法
当线程还没有执行结束将会一直处于等待状态。当我们将get方法放在get之前,此时我们的程序将一直停留在get()处,不会继续运行

三种实现方式的对比

Thread方式--设置获取名字


  • 通过构造方法设置线程名
    我们Mythread的父类是有类似Thread(String name)的构造方法专门用来设置线程名的,但是由于构造方法不能继承,所有我们的子类要想使用设置线程名的构造方法来创建子类的话,就需要在MyThread中创创建单参构造并调用父类单参构造

Thread方法--获取线程对象



获取当先线程对象的一般使用场景

Thread----sheep方法

  • 异常小计
    如果一个类或者接口里面的方法没有抛出异常,那么这个类或者接口的实现类所重写的方法也不能抛出异常

线程的优先级


  • 优先级的获取和设置

1.优先级1-10,默认为5

  • 2.线程优先级越高,只能说抢到cup的概率越高,不是只可能是该线程执行**
package com.callable;

import java.util.concurrent.Callable;

public class MyCallable1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"线程开始了"+i);
        }
        return null;
    }
}

package com.callable;

import java.util.concurrent.FutureTask;

public class Test1 {
    public static void main(String[] args) {
        //线程1
        MyCallable1 myCallable = new MyCallable1();
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        System.out.println(thread.getPriority());//默认5
        //线程2
        MyCallable1 myCallable2 = new MyCallable1();
        FutureTask<String> futureTask2 = new FutureTask<>(myCallable2);
        Thread thread2 = new Thread(futureTask2);
        System.out.println(thread2.getPriority());//默认5
        //设置线程名
        thread.setName("飞机");
        thread2.setName("坦克");
        //启动线程
       // thread2.start();
        //thread.start();
    }
}

Thread方法--守护线程

  • 解释

我们将QQ里面的聊天和传递文件看成是2个线程,如果我们将QQ关闭,聊天和传递文件也会随之关闭,没有存在的必要了。聊天和传递文件就是2个守护线程

package com.thread;

public class MyThread1 extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(currentThread().getName()+"线程开始了"+i);
        }
    }
}

package com.thread;

public class Test1 {
    public static void main(String[] args) {
        //守护线程:当普通线程执行完了,守护线程也没有继续运行下去的必要了
        MyThread1 myThread1 = new MyThread1();
        MyThread1 myThread2 = new MyThread1();
        myThread1.setName("女神");
        myThread2.setName("备胎");
        myThread2.setDaemon(true);//将第二个线程设置成守护线程
        myThread1.start();
        myThread2.start();
    }
}

02线程安全问题

线程安全问题--买票案例的实现

package com.itheima.threadsecture;

public class Ticket implements Runnable
{
    private int ticketCount = 100;//剩下的票数
    @Override
    public void run() {
        while (true){
            if(ticketCount>0){
                ticketCount--;
                System.out.println(Thread.currentThread().getName()+"正在出售票"+"还剩下"+ticketCount);

            }else {
                //当票数为0时,线程结束
                break;
            }
        }
    }
}

package com.itheima.threadsecture;

public class TicketDemo {
    public static void main(String[] args) {
        //Ticket作为参数相当于是要执行的内容
        //各个线程的参数必须一致,要不然将会出现3份票数
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

线程安全--原因分析

  • 在Ticket类中增加延迟
package com.itheima.threadsecture;

public class Ticket implements Runnable
{
    private int ticketCount = 100;//剩下的票数
    @Override
    public void run() {
        while (true){
            if(ticketCount>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ticketCount--;
                System.out.println(Thread.currentThread().getName()+"正在出售票"+"还剩下"+ticketCount);


            }else {
                //当票数为0时,线程结束
                break;
            }
        }
    }
}



出现线程安全的原因

  • 本质上是多个线程操作共享数据

同步代码块解决线程安全



用同步代码块实现购票代码

  • 我们将操作共享资源的代码放在同步代码块中
package com.itheima.threadsecture;

public class Ticket implements Runnable
{
    private int ticketCount = 100;//剩下的票数
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj) {//锁对象是任意的,但是必须保证各个线程面对的是同一把锁
                if(ticketCount>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName()+"正在出售票"+"还剩下"+ticketCount);


                }else {
                    //当票数为0时,线程结束
                    break;
                }
            }
        }
    }
}

package com.itheima.threadsecture;

public class TicketDemo {
    public static void main(String[] args) {
        //Ticket作为参数相当于是要执行的内容
        //各个线程的参数必须一致,要不然将会出现3份票数
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

线程安全问题--锁对象唯一

同步方法


  • 证明同步方法的锁对象是this
package com.runnable;

public class MyRunnable1 implements Runnable{
    private int ticketCount = 100;//剩下票数
 
    @Override
    public void run() {
        while (true)
        {
            if("窗口1".equals(Thread.currentThread().getName())){
                try {
                    synchronizedMethod();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
           if("窗口2".equals(Thread.currentThread().getName())){
              synchronized (this){//锁对象为当前调用的对象
                  if(ticketCount<=0){
                      break;
                  }else {
                      try {
                          Thread.sleep(100);
                      } catch (InterruptedException e) {
                          throw new RuntimeException(e);
                      }
                      ticketCount--;
                      System.out.println(Thread.currentThread().getName()+"正在卖票  还剩下"+ticketCount+"张票");
                  }

                  }
              }
           }
        }
    }

    private void synchronizedMethod() throws InterruptedException {//锁对象默认为this
        if(ticketCount<=0){
            return;
        }else {
            Thread.sleep(100);
            ticketCount--;
            System.out.println(Thread.currentThread().getName()+"正在卖票  还剩下"+ticketCount+"张票");
        }
    }
}

package com.runnable;

import com.thread.MyThread1;

public class MyRunnableTest1 {
    public static void main(String[] args) {
        MyRunnable1 runnable1 = new MyRunnable1();//参数对象相同
        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable1);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread1.start();
        thread2.start();
    }
}

我们使用Runnable接口实现同步。我们故意使用同步方法和同步代码块实现了相同的内容,但是我们的Run方法的内容是由MyRunnable负责的,即如果我们使用的是同一个MyRunnable对象,我们面对的就是同一把锁。我们的Thread对象传递的都是相同的参数对象。最后发现2种情况下是同步执行的。可以得出结论,同步方法的锁对象是this

Lock


主要是因为我们的之前的锁不够形象,使用这个Lock对象,操作起来比较形象

  • 使用Lock代替同步代码块
package com.itheima.threadsecture;

import java.util.concurrent.locks.ReentrantLock;

public class Ticket1 implements Runnable
{
    private int ticketCount = 100;//剩下的票数
    private Object obj = new Object();
    ReentrantLock lock = new ReentrantLock();//创建锁对象
    @Override
    public void run() {
        while (true){
           // synchronized (obj) {//锁对象是任意的,但是必须保证各个线程面对的是同一把锁
            lock.lock();//上锁

                if(ticketCount>0){
                    try {
                        Thread.sleep(100);
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName()+"正在出售票"+"还剩下"+ticketCount);

                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }finally {

                        lock.unlock();//关锁
                    }


                }else {
                    //当票数为0时,线程结束
                    break;
                }

           // }
        }
    }
}

package com.itheima.threadsecture;

public class TicketDemo {
    public static void main(String[] args) {
        //Ticket作为参数相当于是要执行的内容
        //各个线程的参数必须一致,要不然将会出现3份票数
        Ticket1 ticket = new Ticket1();
        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

由于锁是一种资源,所有为了确保其被关闭,一般放在finaly语句中

死锁

  • 如果锁进行了嵌套,可能会出现死锁问题

03生产者和消费者(等待唤醒机制)

生产者和消费者思路分析

  • 理想情况(生产者抢到执行权,然后消费者再抢到执行权,并且以此反复)

    描述:生产者和消费者2个线程轮流执行。生产者生产一个食物,消费者然后吃这个食物

  • 消费者等待

  • 生产者等待的情况

  • 等待唤醒机制总结(结合前面的所有情况)

生产者消费者代码实现

  • 需要使用的方法

关于此时的锁对象
我们有生产者和消费者2个线程,我们需要保证2个线程的锁是一样的,就必须保证有相同的锁对象。如果我们将锁对象定义再其中一个
线程中,这样在另外一个线程中用该锁对象就需要创建对象,这样不是很方便,故将锁对象定义在Desk中

不能随意调用等待和唤醒方法

  • 必须要使用什么对象当作锁,必须使用该对象去调用等待和唤醒方法

  • 关于notify和notifyAll
    多个线程对应同一把锁,notify()方法只能唤醒多个等待线程中的任意一个线程,而notifyAll方法则是可以唤醒所有的正在等待的线程

  • 书写的套路

  • Cook

package com.itheima.foodieandcook;

public class Cook extends Thread{
    @Override
    public void run() {





            while (true){
                synchronized (Desk.lock){
                    if(Desk.count == 0){//没有食物可以卖了(在执行步骤之前先判断是否有食物)
                        break;
                    }else {
                        //1.判断桌子上是否有食物
                        if(Desk.flag){
                            //2.1如果有则进行等待
                            try {
                                Desk.lock.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }else {
                            //2.2如果没有则进行生产
                            System.out.println("开始做食物了");
                            //2.3把做好的食物放在桌子上
                            Desk.flag = true;
                            //2.4叫醒消费者开吃
                            Desk.lock.notifyAll();
                        }
                    }
                }
            }
    }
}

  • Foodie
package com.itheima.foodieandcook;

public class Foodie extends Thread{
    @Override
    public void run() {




        while (true){
            synchronized (Desk.lock){

                if(Desk.count == 0){//没有食物可以卖了(在执行步骤之前先判断是否有食物)
                  break;
                }else {
                    //1.判断桌子上是否有食物
                    if(Desk.flag){
                        //3.如果有就开吃

                        System.out.println("开始吃食物");
                        //4.1吃完后桌子上就没有食物了
                        //4.2 通知生产者进行生产
                        //4.3食物的总数-1
                        Desk.flag = false;
                       Desk.lock.notifyAll();
                        Desk.count--;
                    }else {
                        //2.如果没有就进行等待
                        //必须要使用锁的对象去调用等待和唤醒方法
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                }
            }
        }

    }
}
  • Desk
package com.itheima.foodieandcook;

//Desk类用于标记桌子上是否有食物
public class Desk {
    //定义一个标记
    //true:表示桌子上有食物,此时运行吃货执行
    //false:表示桌子上没有食物,此时运行厨师执行
    public static boolean flag = false;
    //定义可以卖的食物的总数量
    public static int count = 10;
    //锁对象
    public static final Object lock = new Object();

}
  • 测试类
package com.itheima.foodieandcook;

public class Demo {
    public static void main(String[] args) {
        Foodie foodie = new Foodie();
        Cook cook = new Cook();
        foodie.start();
        cook.start();
    }
}

  • 线程有序执行

生产者消费者---代码改写

  • 改进目标
    为了让我们的代码更面向对象,努力将Desk类修改成javaBean类,并随即改进Foodie和Cook类
    注意:
    我们不能在Foodie和Cook类中创建Desk对象,因为这样将会导致,FOODie和Cook各自创建了一个Desk对象,这样将会导致创建了2个不同的Lock对象。将会导致2个线程所面对的锁不同
  • 解决方法
    我们在Fooie和Cook中仅仅定义Desk的引用,然后在此时类中创建Desk对象,并通过构造方法进行传递。这样就会保证2个线程所面对的是同一个锁
  • Cook
package com.itheima.foodieandcook;

public class Cook extends Thread{
    private Desk desk;
    public Cook(){

    }
    public Cook(Desk desk){
        this.desk = desk;
    }
    @Override
    public void run() {





            while (true){
                synchronized (desk.getLock()){
                    if(desk.getCount() == 0){//没有食物可以卖了(在执行步骤之前先判断是否有食物)
                        break;
                    }else {
                        //1.判断桌子上是否有食物
                        if(desk.isFlag()){
                            //2.1如果有则进行等待
                            try {
                               desk.getLock().wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }else {
                            //2.2如果没有则进行生产
                            System.out.println("开始做食物了");
                            //2.3把做好的食物放在桌子上
                            desk.setFlag(true);
                            //2.4叫醒消费者开吃
                          desk.getLock().notifyAll();
                        }
                    }
                }
            }
    }
}
  • Foodie
package com.itheima.foodieandcook;

public class Foodie extends Thread{
 private Desk desk;
 //定义构造方法
 public Foodie(){

 }
 public Foodie(Desk desk){
     this.desk = desk;
 }
    @Override
    public void run() {




        while (true){
            synchronized (desk.getLock()){

                if(desk.getCount() == 0){//没有食物可以卖了(在执行步骤之前先判断是否有食物)
                  break;
                }else {
                    //1.判断桌子上是否有食物
                    if(desk.isFlag()){
                        //3.如果有就开吃

                        System.out.println("开始吃食物");
                        //4.1吃完后桌子上就没有食物了
                        //4.2 通知生产者进行生产
                        //4.3食物的总数-1
                       desk.setFlag(false);
                       desk.getLock().notifyAll();
                       desk.setCount(desk.getCount()-1);
                    }else {
                        //2.如果没有就进行等待
                        //必须要使用锁的对象去调用等待和唤醒方法
                        try {
                           desk.getLock().wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                }
            }
        }

    }
}
package com.itheima.foodieandcook;

//Desk类用于标记桌子上是否有食物
public class Desk {
    //定义一个标记
    //true:表示桌子上有食物,此时运行吃货执行
    //false:表示桌子上没有食物,此时运行厨师执行

    private boolean flag;
    //定义可以卖的食物的总数量

    private int count = 10;
    //锁对象

    private final Object lock = new Object();


     public Desk() {
    }

    public Desk(boolean flag, int count) {
        this.flag = flag;
        this.count = count;

    }

    /**
     * 获取
     * @return flag
     */
    public boolean isFlag() {//注意:方法名不同
        return flag;
    }

    /**
     * 设置
     * @param flag
     */
    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    /**
     * 获取
     * @return count
     */
    public int getCount() {
        return count;
    }

    /**
     * 设置
     * @param count
     */
    public void setCount(int count) {
        this.count = count;
    }
   public Object getLock(){//注意lock只有getLock方法
       return lock;
   }
    public String toString() {
        return "Desk{flag = " + flag + ", count = " + count + ", lock = " + lock + "}";
    }
}
package com.itheima.foodieandcook;

public class Demo {
    public static void main(String[] args) {
        Desk desk = new Desk();//创建Desk的对象
        Foodie foodie = new Foodie(desk);
        Cook cook = new Cook(desk);
        foodie.start();
        cook.start();
    }
}

阻塞队列的基本使用

使用等待队列会让我们的代码更加间接



  • 2种阻塞队列
  • 正常的存储一个取出一个
package com.itheima.foodieandcook;

import java.util.concurrent.ArrayBlockingQueue;

//阻塞队列的使用
public class BlockQueueTest0 {
    public static void main(String[] args) throws InterruptedException {
        //创建了阻塞队列的对象,其容量为10
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
        //存储元素
        queue.put("汉堡包");
        //取元素
        System.out.println(queue.take());
        System.out.println("程序结束了");
    }
}
  • 当我们存储一个,然后连续取出2个,当取出第二个时,按照前面说的当娶不到时将会进行等待
package com.itheima.foodieandcook;

import java.util.concurrent.ArrayBlockingQueue;

//阻塞队列的使用
public class BlockQueueTest0 {
    public static void main(String[] args) throws InterruptedException {
        //创建了阻塞队列的对象,其容量为10
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
        //存储元素
        queue.put("汉堡包");
        //取元素
        System.out.println(queue.take());//取一个
        System.out.println(queue.take());//取第二个

        System.out.println("程序结束了");
    }
}
  • 此时程序陷入了等待

阻塞对象实现等待唤醒机制

  • Cooker
package com.itheima.queuefoodieandcook;

import java.util.concurrent.ArrayBlockingQueue;

public class Cooker extends Thread{
    private ArrayBlockingQueue<String> list;//阻塞队列成员
    public Cooker(ArrayBlockingQueue<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true){
            try {
                list.put("汉堡包");
                System.out.println("厨师放了一个汉堡包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
  • Foodie
package com.itheima.queuefoodieandcook;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread{
    private ArrayBlockingQueue<String> list;//阻塞队列成员
    public Foodie(ArrayBlockingQueue<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true) {
            try {
                final String take = list.take();
                System.out.println("吃货从队列中获取了"+take);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 测试类
package com.itheima.queuefoodieandcook;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo {
    public static void main(String[] args) {
        //创建阻塞队列
        ArrayBlockingQueue<String> list = new ArrayBlockingQueue<>(1);
        //创建消费者和生产者线程
        Cooker cook = new Cooker(list);
        Foodie foodie = new Foodie(list);
        cook.start();
        foodie.start();
    }
}


从输出结果看好像有连续打印,不符合,做一个,吃一个。其实这时因为我们的输出语句不在锁中,因为锁是由put和take方法底层加的。这种情况并不影响,等待唤醒机制

线程池和volatile(多线程高级)

线程状态


线程处于运行状态时是和cup产生的关于,所以在我们的虚拟机中没有没有定义运行态

  • 在Thread类中的内部类State定义了线程的状态

线程池---基本原理

  • 情景引入
    小明同学要吃饭,但是没有碗,所有要到小佳同学开的超市去买完,买了碗后吃完饭,(因为没有地方放碗)即把碗摔了,以此反复.......
  • 解决方案
  • 以前写法的弊端


线程池--Executors默认线程池(线程池不指定线程上限)


我们在提交线程任务后,如果没有关闭线程池则我们的程序不会截至,因为我们的线程还存在,我们需要关闭线程池我们的程序才结束

  • 注意:如果有空闲的线程将不会去重新创建新的线程

Executors创建制定线程上限的线程池对象

ThreadPoolExecutor(自定义线程池)

在前面的创建线程池的对象中,我们都是调用Executors的静态方法(newCaceThreadPool或newFixedThreadPool)来创建线程池的对象,在实际上是调用其他类的代码来间接创建的而不是直接创建的

  • newCachedThreadPool的原码
  • newFixedThreadPool原码


  • 代码简单截图(和之前的一样)

线程池-- 参数详解

  • 任务队列 让任务在队列里面等待,等有线程空闲了再从队列中获取任务并执行
  • 创建线程工厂:通过查看原码,本质上就是按照默认的方式创建线程(我们规定的数量的线程)

任务拒绝策略
-1.什么时候会拒绝

  • 不会拒绝
  • 不会拒绝
  • 刚好装满,不会拒绝

当线程池里面的线程全部再执行任务,而且任务队列全部都装满了,如果还有多余的任务将会被拒绝


非默认任务拒绝策略



从上面可以看到它仅仅是打印出来了1,2,3,所有可以知道它拒绝了任务4和5,并且没有抛出异常


将任务10前面的的任务都决绝了(等待时间长)而执行了任务10


**此时当我们的线程忙不过来了,就会交给别的线程执行

volatile问题



但是此时A线程还再循环中无法获取到moeny的最新数据,还是使用10w

volatile解决

  • 内存图理解volatile问题

    我们的线程有自己的内存,对于共享数据,他们会复制一份作为变量副本存于字节的内存中,当他们要改变共享数据时,会先改变自己
    的变量副本,然后将变量副本复制到共享数据中,从而改变共享数据。而其他的线程要知道共享数据是否发生改变是需要自己到访问
    共享数据进行查看的,至于什么时候进行查看,这个我们无法控制


  • 解决方案

  • 此时我们发现我们的程序打印出来了结果

synchronized同步代码块解决

  • 线程1中加锁

  • 线程2中加锁

  • 运行代码:此时已经有了运行结果

  • 为什么同步代码块也可以解决上述的问题呢

posted @ 2023-07-06 15:32  一往而深,  阅读(6)  评论(0编辑  收藏  举报