Java多线程------内存的可见性、线程池、计时器

内存的可见性

1. 首先我们通过一个案例来看一下什么是Java的内存可见性问题
  • 案例演示
    在子线程中定义一个boolean类型的标记,并提供get方法,并在子线程阻塞10ms之后改变标记的值,然后主线程中去ge到t这个标记并使用。
 public static void main(String[] args) {
        MyRunable myRunable = new MyRunable();
        new Thread(myRunable).start();
        while (true) {

                if (myRunable.getFlag()) {
                    System.out.println("进来了。。。。");
                    break;
                }


        }
    }
}


class MyRunable implements Runnable {
   boolean flag = false;

    public boolean getFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        synchronized (MyRunable.class){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //改变flag的值
            flag = true;
            System.out.println(Thread.currentThread().getName() + " " + flag);
        }

    }
}
  • 程序运行结果:
    在这里插入图片描述从运行结果来看子线程中的flag的值似乎还是false,然而事实是flag的值已经改变为True,只是还没有写回主线程。
2. Java的内存模型

要理解为什么会出现刚才的那个问题,首先来看下Java的内存模型

Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,
线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。
线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。
不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

3.Java中的可见性以及解决方法

对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,
当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,
并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
***所以我们对刚才的flag加上volatile关键字来修饰,就可以保证flag值得变化即使写回主线程

 volatile boolean flag = false;

线程池

1.线程池的概述

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

2.内置线程池的使用概述
  • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
    public static ExecutorService newCachedThreadPool(): 根据任务的数量来创建线程对应的线程个数
    public static ExecutorService newFixedThreadPool(int nThreads): 固定初始化几个线程,在实际使用中指定的个数并没什么用,你还是可以给它多个Runnable对象或者Callable对象,来开启线程
    public static ExecutorService newSingleThreadExecutor(): 初始化一个线程的线程池
    这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。

  • 使用步骤:
    创建线程池对象
    创建Runnable实例
    提交Runnable实例
    关闭线程池

  • 案例演示: 线程池的使用

public class MyTest {
    public static void main(String[] args) {
        //ExecutorService 线程池
        //获取线程池对象
        ExecutorService executorService = Executors.newCachedThreadPool();
        //给线程池提交任务即可
        executorService.submit(new MyRunable());
        executorService.submit(new MyRunable());
        executorService.submit(new MyRunable());
        executorService.submit(new MyRunable());
        executorService.submit(new MyRunable());
        //关闭线程池
        executorService.shutdown();

    }
}

//runable 接口
public class MyRunable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" 线程任务执行完了");
    }
}


匿名内部类实现多线程

  • 跟普通的匿名内部类没什么区别
  • 代码演示
public class MyTest3 {
    public static void main(String[] args) {
        //匿名内部类来开启线程
        //方式1
        new Thread() {
            @Override
            public void run() {
                System.out.println("线程执行了");
            }
        }.start();

        //方式2
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行了");
            }
        }).start();
    }
}


定时器

1.定时器概述

定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。简单说就是定时执行。
在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。

2.方法介绍
  • timer和TimerTask
    可以调度TimerTask。TimerTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力。
  • 常用方法
    Timer()
    创建一个新的定时器
    cancle()
    取消此计时器任务。发生在编译期
    void schedule (TimerTask task, Date time)
    安排在指定的时间执行指定的任务。
    void schedule (TimerTask task, Date firstTime,long period)
    安排指定的任务在指定的时间开始进行重复的固定延迟执行。
    void schedule (TimerTask task,long delay)
    安排在指定延迟后执行指定的任务。
    void schedule (TimerTask task,long delay, long period)
    安排指定的任务从指定的延迟后开始进行重复的固定延迟执行
  • 代码演示

主程序

public class MyTest {
    public static void main(String[] args) throws ParseException {
        //定时器:Timer

      
        Timer timer = new Timer();
        MyTimerTask myTimerTask = new MyTimerTask(timer);
       // timer.schedule(myTimerTask, 2000); //2秒之后执行定时任务
        // timer.schedule(myTimerTask,2000,1000);//第一次等2秒执行,后面每隔一秒重复执行。
      //  myTimerTask.cancel();//取消定时任务

        //在指定的日期来执行任务
        String str = "2019-06-17 21:17:59";
        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);

        timer.schedule(myTimerTask, date);

     // timer.cancel();//取消定时器

    }
}
  • TimerTask 定义子线程
public class MyTimerTask extends TimerTask {
    Timer timer;
    public MyTimerTask(Timer timer) {
        this.timer=timer;

    }

    @Override
    public void run() {
        System.out.println("砰~~~ 爆炸了");
        //timer.cancel();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值