Java多线程与并发(一)

多线程与并发的基础问题

并发就是指程序同时处理多个任务的能力(一个程序被多个用户访问都能看到自己预期的结果)
并发的根源在于对多任务情况下访问资源的有效控制!

  • 并发背后的问题
public class DownloadSimple {

    private static int user = 1;//同时模拟的并发用户访问数量
    //private static int user = 10;
    private static int dowloadCounts = 5000;//用户的真实下载数
    private static int count= 0;//计数器

    public static void main(String[] args) {
        //调度器,jdk1.5之后引入current对于并发的支持

        ExecutorService executor = Executors.newCachedThreadPool();
        //信号量 ,用于模拟并发用户数
        final Semaphore semaphore = new Semaphore(user);
        for(int i =0;i<dowloadCounts ;i++){
            //通过多线程模拟多个用户访问的下载次数
            executor.execute(new Runnable() {

                @Override
                public void run() {
                    try{
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    }catch(Exception e){
                        e.printStackTrace();
                    }

                }
            });
        }
        try {
            //延迟主线程结束--让for循环中代码执行完毕
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }
    private static void add(){
        count++;
    }

user=1单个用户访问
这里写图片描述

user=10多个用户访问的时候:

这里写图片描述

无处不存在并发的问题:
并发控制是关键问题

  • 课程导图
    基本概念
    多线程(Thread)
    线程池(Executor)
    并发容器(Collections)
    同步工具(Tools)
    原子对象(Atomic)

  • 基础概念

程序、进程和线程的区别?
程序是静态的概念,windows下是指.exe程序
进程是动态的概念,是程序在运行状态,进程说明程序在内存中的边界
线程是指进程内的一个“基本任务”,每个线程都有自己的功能,是cpu分配与调度的基本单位。

一个程序有多个进程(之间互不影响)

这里写图片描述

多核CPU是线程并行的
单核CPU是线程并发的(CPU的时间片)

同步和异步:

这里写图片描述

回调(异步)–Ajax
NIO-非阻塞IO

临界区:(eg:办公室中的一台打印机)
临界区表示一种公共资源与共享数据,可以被多个线程使用。
同一时间只能有一个线程访问临界区域(阻塞状态),其它资源必须等待。

这里写图片描述

避免以上锁发生的解决办法:为每一个线程分配一个自己独立的不被其他影响的资源

线程安全:在拥有共享数据的多个线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

线程安全不安全就是区别单用户多用户对公共访问资源的处理是否符合预期处理

线程安全的三大特性:
原子性:即一个操作或者多个操作,要么全部执行且执行的过程不会被其他因素打断,要么就都不执行 i = i +1
可见性:当多个线程访问同一个变量的时候,一个线程修改了这个变量的值,其它线程能够立即看得到修改的值

这里写图片描述
用户1修改之后,没有通知用户2线程

有序性: 如果在本线程内观察,所有的操作都是有序的,如果在一个线程内观察另外一个线程,所有操作都是无序的;

这里写图片描述
JVM自动进行防止重排序,所以应该不会出现重排序,打乱有序性

  • *Java内存模型
    Java Memory Model(在真实物理内存上开辟的空间)

这里写图片描述

堆内存(Heap):用于存储对象,JVM全局唯一,堆是不连续的,执行效率低,所有线程共享
方法区(静态区):类结构信息,静态变量和静态方法信息,存储内容是不变的,存储字符串,在堆中开辟的一块空间

栈内存(Stack):每个线程创建一个栈,存储执行方法的执行信息,线程私有,无法共享。连续存储,执行效率高,先进后出,后进先出

这里写图片描述

直至main函数弹出栈
销毁线程栈,销毁线程

  • Java多线程
    创建多线程的三种方式:
    1.继承Thread类创建线程
    2.实现Runnable接口来创建线程
    3.使用Callable和Future创建线程

//使用继承Thread的方式来实现多线程

public class Methed1{

    public static void main(String[] args) {
        new ExtendsThread().start();//创建一个新的线程并启动
        //setName("wanghaoxin")可以设置线程名称
    }

}
class ExtendsThread  extends Thread{
    public void run(){
        Integer  speed = new Random().nextInt(100);
        for(int i = 0;i< 100 ;i++){
            try {
                //当前线程休眠1s
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印当前线程的名字
            System.out.println(this.getName()+"---已前进--" +(i*speed)+"---速度----"+speed);

        }
    }

}

三个并列的线程可能执行顺序不一样

        ExtendsThread liuxiang = new ExtendsThread();

        liuxiang.setName("刘翔");
        ExtendsThread wanghaoxin = new ExtendsThread();

        wanghaoxin.setName("王五");
        ExtendsThread lisan = new ExtendsThread();

        lisan.setName("路飞");

        liuxiang.start();//创建一个新的线程并启动
        wanghaoxin.start();//创建一个新的线程并启动
        lisan.start();//创建一个新的线程并启动

执行顺序依赖于线程的优先级,可以手动设置

程序一旦程序启动之后有五个线程

这里写图片描述
java本身运行的过程中就是多线程的

垃圾回收线程和main主线程

  • 实现Runnable接口实现线程

在Runnable接口中无法获取this.getName()获取线程名称
只能用:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

通过实现Runnable接口来新建线程


public class Methed2 {

    public static void main(String[] args) {
        ImplRunnable liuxiang = new ImplRunnable();
        Thread thread = new Thread(liuxiang);
        thread.setName("liuxian");
        Thread thread2 = new Thread(new ImplRunnable());
        thread2.setName("WANGHAXIN");

        thread.start();
        thread2.start();
    }


}
class ImplRunnable implements Runnable{

    @Override
    public void run() {

        Integer  speed = new Random().nextInt(100);
        for(int i = 0;i< 100 ;i++){
            try {
                //当前线程休眠1s
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //打印当前线程的名字
            //此处不可以用this.getName(因为不getName属于Thread的方法,Runnable接口中并没有)
            System.out.println(Thread.currentThread().getName()+"---已前进--" +(i*speed)+"---速度----"+speed);

        }


    }

}
  • 实现Callable接口–利用线程池
    jdk1.5之后为我们专门提供了一个并发工具包,java.util.concurrent。

java.util.concurrent包含许多线程安全、测试良好、高性能的并发构建块。创建concurrent的目的就是要实现Collection框架对数据结构所执行的并发操作。通过提供一组可靠的,高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性

      Callable<Integer> myCallabel= new implCall();                
      FutureTask<Integer> ft=new  FutureTask<Integer>(myCallabel);                
      System.out.println(Thread.currentThread().getName()+" "+i);
      Thread th= new Thread(ft);
      th.start();    

专门用于并发机制

需要线程的数目已经在创建线程池时已经定义好
liuxiang是实现Callable接口的实例化对象,Future用于接收返回值

implCall liuxiang = new implCall();
Future<Object> future = executorService.submit(liuxiang);

代码案例:(线程池实现)

//对于一个线程而言,Callable有返回值,
public class Methed3 {
    public static void main(String[] args) {
        //利用Executors线程调度器来实现  ---Executors是调度器对线程池进行管理
        ExecutorService executorService = Executors.newFixedThreadPool(3);//创建一个线程池,里边添有三个空线程

        implCall liuxiang = new implCall();//实例化Callable对象
        implCall wanghaoxin = new implCall();
        implCall lufei = new implCall();
        //Future用于接收线程内部call方法的返回值
        //将这个对象扔到线程池中,线程池自动分配一个线程来运行liuxiang这个对象的call方法
        Future<Object> future = executorService.submit(liuxiang);
        Future<Object> future2 = executorService.submit(wanghaoxin);
        Future<Object> future3 = executorService.submit(lufei);
        //此时线程池容量大小是3,再往线程池中添加时会报错

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            executorService.shutdown();//关闭线程池释放所有资源
        }
        try {
            System.out.println("累计跑了" + future.get());
            System.out.println("累计跑了" + future2.get());
            System.out.println("累计跑了" + future3.get());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        }

}
/**
 * 实现Callable接口可以允许我们的线程返回值或者抛出异常
 * @author Administrator
 *
 */
class implCall implements Callable<Object>{

    @Override
    public Object call() throws Exception {

        Integer  speed = new Random().nextInt(100);
        Integer distince = 0 ; //总共奔跑的距离
        for(int i = 0;i< 100 ;i++){
            distince = i*speed;
            //打印当前线程的名字
            System.out.println(Thread.currentThread().getName()+"---已前进--" +(i*speed)+"---速度----"+speed);

        }


        return distince;
    }

}

这三个线程是彼此没有任何交集,利用线程池手段是开发的主要实现方案–方便对线程池进行有效管理(最著名–数据库连接池,数据库中的每一个连接其实就是一个线程,线程池就是连接池的底层实现)

线程池更安全—-保证线程安全的工具类

这里写图片描述

1)继承Thread:优点:编程简单,执行效率高,但继承,无法对线程组进行有效控制,任何使用场景都不推荐
2)实现Runnable接口,面向接口编程,执行效率高,无法对线程组进行有效控制,没有返回值。异常,使用场景:适用于简单的多线程程序
3)利用线程池:容器管理线程,允许返回值和异常,执行效率相对低(容器本身有调度的时间耗费,线程切换等等),编程麻烦,使用场景:企业级应用+推荐使用。

猜你喜欢

转载自blog.csdn.net/qq_19704045/article/details/81610072