JAVA多线程

多线程

1、线程和进程

进程和线程的概述
  • 进程(Process):是计算机中程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • 线程(thread):是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中实际运行单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多线程,每条线程并行执行不同的任务。
并发和并行
  • 并发:指在同一时刻只能有一条指令执行,多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替执行。

  • 并行:真正意义的同时执行

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zHdRvFAy-1656332147870)(C:\Users\21158\AppData\Roaming\Typora\typora-user-images\image-20220427111145254.png)]

2、线程的生命周期

  • 线程的5种状态
    • 起始:当创建线程对象时
    • 就绪:当调用start方法启动时,指能运行但没有运行的过程,在等待CPU为其分配时间片
    • 运行:执行run方法时
    • 阻塞:当线程中断、主动出让、等待、睡眠时、会重新进入就绪状态
    • 结束:线程执行完毕或异常终止或执行stop();
image-20220427113505954

3、线程的创建

继承Thread类
  • 通过继承Thread类重新run方法
public class TestThread extends Thread{
    @Override
    public void run() {
        System.out.println(this.getName() + "  runnig");
    }

    public static void main(String[] args) {
        Thread thread = new TestThread();
        thread.start();//start启动线程
    }
}
实现Runnable接口
  • 通过实现Runnable接口重写run()方法
public class TestRunnable implements Runnable{
    @Override
    public void run() {
        //getName是在Thread类的currentThread()返回当前线程
        System.out.println(Thread.currentThread().getName()+ " running ");//返回当前线程名字
    }

    public static void main(String[] args) {
        //Thread类里面有可以放runnable参数的构造方法
        Runnable runnable = new TestRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        //匿名内部类实现
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 匿名内部类实现的线程");
            }
        }).start();
        //lambda表达式创建线程
        new Thread(() -> System.out.println(Thread.currentThread().getName() + " lambda表达式")).start();
    }
}
  • 好处是可以实现多个接口
继承Callable接口
  • 思考:怎么让线程计算0-100的值?
  • 实现Callable接口,可以定义返回指定的泛型,依赖FutuerTask类
public class TestCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName() + " running";
    }

    public static void main(String[] args) throws Exception{
        /**
         * Callable<V>有返回值存储在FutureTask中的
         * FutureTask继承RunnableFuture接口继承了Runnable所以线程创建是向上造型成了Runnable接口
         */
        FutureTask<String> futureTask = new FutureTask<String>(new TestCallable());
        //线程创建使用的构造方法:public Thread(Runnable target)
        Thread thread = new Thread(futureTask);
        thread.start();
        //get()获取线程的返回值
        System.out.println(futureTask.get());
    }
}
  • Callable线程创建过程
    • Callable有返回值存储在FutureTask中的
      FutureTask继承RunnableFuture接口继承了Runnable所以线程创建是向上造型成Runnable接口,然后使用Thread(Runnable)构造方法创建线程
image-20220428112252598
  • Runnable和Callable的区别和联系
    • 没有任何联系
    • Runnable没有返回值,Callable有返回值
线程池的创建
  • 工厂类Executors

    public class TestExecutors {
        public static void main(String[] args) throws Exception {
            //创建一个线程池,中有五个线程,newFixedTreadPool创建线程池
            ExecutorService executorService = Executors.newFixedThreadPool(5);
            //循环启动这个五个线程
            for (int i = 0; i < 100; i++) {
                executorService.execute(()-> System.out.println(Thread.currentThread().getName()));
            }
            //submit把线程交给Callable执行
            Future<String> future = executorService.submit( ()-> {return Thread.currentThread().getName();});
            System.out.println(future.get());
            //shutdown 关闭线程池
            executorService.shutdown();
        }
    }
    

4、线程的常用方法

public class TestCommon implements Runnable{
    @Override
    public void run() {
        //currentThread()获取当前线程对象
        Thread thread = Thread.currentThread();
        System.out.println("当前线程:" + thread);//Thread[Thread-0,5,main]
        //getName()获取线程名字 setName()设置线程名字,也可以通过构造方法设置
        System.out.println("当前线程名字:" + thread.getName());
        //getId()获取线程id
        System.out.println("当前线程ID:" + thread.getId());
        /**
         * 优先级Priority
         *      MIN_PRIORITY = 1 最小优先级
         *      NORM_PRIORITY = 5 默认优先级
         *      MAX_PRIORITY = 10 最大优先级
         *      注:并不是说优先级高的就一定先执行完
         */
        //setPriority()设置线程优先级
        thread.setPriority(Thread.MIN_PRIORITY);//设置为最小优先级 1
        //getPriority()获取线程优先级
        System.out.println("当前线程优先级为:" + thread.getPriority());//1
        //isAlive() 查看当前线程是否活动
        System.out.println("当前线程是否活动:" + thread.isAlive());//true
        try {
            thread.sleep(1000);//线程休眠1000毫秒
            //yielde()暂停当前正在执行的对象,目的就是为了能够轮换调度
            thread.yield();
            thread.join(1000);//等待thread线程1000毫秒后在执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //interrupt() 中断线程
        thread.interrupt();
        //isInterrupted() 查看当前线程是否中断
        System.out.println(thread.isInterrupted());//true 当前线程已中断
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new TestCommon());
        System.out.println(thread.getName());
        //main线程的优先级为5
        System.out.println(Thread.currentThread().getPriority());//5
        //start()启动线程
        thread.start();
    }
}
  • Object提供的等待
守护线程
  • 是在最后一个线程执行完自动停止
/*
守护线程Daemon()
    在Thread0 创建一个死循环
    main()中休眠1秒
    发现main线程结束后Thread0线程自动结束了
 */
public class TestDaemon implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("Daemon running...");
        }
    }

    public static void main(String[] args)  throws  Exception{
        Thread thread = new Thread(new TestDaemon());
        thread.setDaemon(true);
        thread.start();
        Thread.sleep(1000);//主线程休眠1秒
    }
}

5、线程同步问题

  • 案例-五个人同时抢购问题

    /**
     * 5个人同时抢购,不同线程抢占同一资源,数据混乱
     */
    public class RushToBuy implements Runnable{
        private Integer stock = 100;//库存
        private Boolean flag = true;//标志库存还有
    
        public static void main(String[] args) {
            RushToBuy rushToBuy = new RushToBuy();
            //创建5个线程ABCDE分别代表五个用户
            new Thread(rushToBuy,"A").start();
            new Thread(rushToBuy,"B").start();
            new Thread(rushToBuy,"C").start();
            new Thread(rushToBuy,"D").start();
            new Thread(rushToBuy,"E").start();
        }
        @Override
        public void run() {
            while (flag){
                    buy();
            }
    
        }
        private void buy(){
            if(stock <= 0 ){
                flag = false;
                return;
            }
                    System.out.println(Thread.currentThread().getName() + "剩余库存" + (--stock));
        }
    }
    
    • 发现这个结果出现乱的情况和逻辑想的不一样
  • 线程同步:同一个资源,多个人想使用,解决问题上锁,一个资源在使用时上锁,别人只能等待,等资源使用完时解锁。

同步方法
  • synchronized修饰的方法

  • 对run方法进行加锁

    @Override
    public synchronized void run() {
        while (flag){
                buy();
        }
    
    }
    

    加锁了可以保证同一资源被多个对象使用,这样保证了数据的一致性

同步代码块
synchronized (obj){}
  • Obj:称为监听器

    • Obj可以是任何对象,但是推荐使用共享资源作为同步监听器
    • 同步方法中无序指定同步监听器,因为同步方法中国监听器就是this
  • 修改run方法

    @Override
    public  void run() {
        while (flag){
            synchronized (this){
                buy();//买
            }
        }
    }
    

    同步代码块是指锁住所控制的那一块代码,方法中其他的内容不受影响。比起同步方法来说更节省了资源调度时间。

Lock锁
  • 通过显示定义同步锁对象(Lock)来完成同步

  • ReentrantLock类实现了Lock接口,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁和释放锁。

  • 修改run方法

    private final Lock lock = new ReentrantLock();
    
    public  void run() {
        while (flag){
            try {
                //进入加锁
                lock.lock();
                buy();//买
            } finally {
                //解锁
                lock.unlock();
            }
        }
    }
    
    • Lock是显示锁可以看到加锁和解锁,synchronized是隐式锁,出了作用域自动释放
    • Lock只有代码块锁,synchronized有代码块锁和方法锁
    • 使用Lock锁,JVM将花费较小的时间来调整线程,性能好。并且具有更好的扩展性(提供了更多的子类)
死锁
  • 死锁就是两个或多个线程在使用时相互都需要对方的资源,相互都不会释放的状态,形成一个永久等待状态。

    如:A线程程需要1资源,运行途中它又需要2资源,然后这时2资源已经被B使用(则A就需要等待B使用完)B线程程运行到途中它又需要1资源(1资源已经被A使用了B需要等待),这样就形成了两个线程都在永久等待的状态。

  • 死锁的四个必要条件:

    • 互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
    • 请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
    • 非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
    • 循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
  • 解决死锁:如果打破上述3任何一个条件,便可让死锁消失

    //死锁案例
    public class TesetDeadlock {
        public static String data1 = "data1";//资源1
        public static String data2 = "data2";//资源2
    
        public static void main(String[] args) {
            TreadDl treadDl = new TreadDl();
            treadDl.threadA.start();//启动线程A
            treadDl.threadB.start();//启动线程B
        }
    
    }
    
    class TreadDl {
    
        //线程A
        public Thread threadA = new Thread(() -> {
            synchronized (TesetDeadlock.data1) {
                //得到资源1
                System.out.println("A 获取" + TesetDeadlock.data1);
                try {
                    Thread.sleep(1000);//为了测试休眠1秒让B线程能运行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A 等待获取资源2");
                //需要获取资源2,资源2已经被B使用处于等待状态
                synchronized (TesetDeadlock.data2) {
                    System.out.print("A");
                }
            }
    
    
        });
    
        //线程B
        public Thread threadB = new Thread(() -> {
            synchronized (TesetDeadlock.data2) {//锁住资源2
                //得到资源2
                System.out.println("B 获取" + TesetDeadlock.data2);
                try {
                    Thread.sleep(1000);//为了测试休眠1秒让A线程能运行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("B 等待获取资源1");
                //需要获取资源1,资源1被A使用处于等待状态
                synchronized (TesetDeadlock.data1) {//锁住资源1
                    System.out.println("B 获取" + TesetDeadlock.data1);
    
                }
            }
        });
    }
    

6、线程通信

  • wait()等待,notify()唤醒属于Object类中

  • 注:需要在同步代码块或同步方法中执行,必须是同一个同步监视器

  • 生产者消费者

    • 生产者线程生产后进行等待消费者消费,消费者消费了生产者生产
    image-20220430220011644
**
 * 线程通信
 *      wait()等待,notify()唤醒属于Object类中
 *      注:需要在同步代码块或同步方法中执行,
 *      必须是同一个同步监视器
 * 生成者消费者问题
 *      线程1生产一个之后进行等待线程2消费
 *      线程2消费了唤醒线程1生产
 *      用户输入需要生产的数量
 */
public class TestTreadSignal {
    public static void main(String[] args) {
        Object obj = new Object();//创建一个监听对象
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入生产数量:");
        int quantity = scanner.nextInt();//生产数量
        //生产者线程
        Thread thread1 = new Thread(() -> {
            //同步代码块监视器obj
            synchronized (obj) {
                //生产quantity
                for (int i = 1; i <= quantity; i++) {
                    System.out.println("生产:" + i);
                    try {
                        if (i > 1) {
                            obj.notify();//生产完成唤醒消费者线程消费
                        }
                        obj.wait();//生产完等待消费

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            //同步代码块监视器obj
            synchronized (obj) {
                for (int i = 1; i <= quantity; i++) {
                    System.out.println("消费 " + i);
                    obj.notify();//消费完成唤醒生产线程生产
                    try {
                        obj.wait();//等待生产者线程生产
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread1.start();//启动生产者线程
        thread2.start();//启动消费者线程
    }
}
       for (int i = 1; i <= quantity; i++) {
                    System.out.println("消费 " + i);
                    obj.notify();//消费完成唤醒生产线程生产
                    try {
                        obj.wait();//等待生产者线程生产
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread1.start();//启动生产者线程
        thread2.start();//启动消费者线程
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值