线程

作者: 金石_832e | 来源:发表于2020-01-20 17:38 被阅读0次

    创建线程的三种方式

    1、继承Thread类
    2、实现Runnable
    3、实现Callable


    继承Thread

    public class MyThread extends Thread {
        @Override
        public void run() {
            for(int i =0; i<=10;i++){
                System.out.println("Thread线程执行"+i+"次:时间"+ LocalTime.now());
            }
        }
    }
    

    实现Runnable

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            for(int i =0; i<=10;i++){
                System.out.println("Runable线程执行"+i+"次:时间"+ LocalTime.now());
            }
        }
    }
    

    实现Callable

        public String call() throws Exception {
            for(int i =0 ;i<=10;i++){
                System.out.println("Callable主线程执行"+i+"次:时间"+ LocalTime.now());
            }
            return "完成";
        }
    

    主线程main

    public class ThreadCreateDemo {
        public static void main(String[] args) {
    //        1、创建自定义线程类实例
    //        2、启动线程
    //        3、在main线程打印信息
    //        一、Thread线程
    //        MyThread myThread =new MyThread();
    //        myThread.run();
    //        二、Runnable线程
    //        Thread thread=new Thread(new MyRunnable());
    //        thread.start();
    //        三、Callble线程
            FutureTask<String> futureTask =new FutureTask<>(new MyCallable());
            Thread thread =new Thread(futureTask,"MyCallable");
            thread.start();
            try {
    
                System.out.println("Callable结果为:"+futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            for(int i =0 ;i<=10;i++){
                System.out.println("主线程执行"+i+"次:时间"+ LocalTime.now());
            }
        }
    }
    

    线程的生命周期

    image.png

    新建
    new关键字创建了一个线程之后,该线程处于新建状态
    jvm为线程分配内存,初始化成员变量

    就绪
    当线程对象调用了start()方法之后,该线程处于就绪状态
    jvm为线程创建方法栈和程序计数器,等待线程调度器调度

    运行
    就绪状态的线程获取cpu资源,开始运行run()方法,该线程处于运行状态

    阻塞
    当发生如下情况时,线程会进入阻塞状态

    • 线程调用sleep()方法主动放弃所占用的处理器资源
    • 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
    • 线程在等待某个通知(notify)
    • 线程视图获得一个同步锁(同步监视器),但该同步锁正在被其它线程所持有的
    • 程序调用了线程的suspend()方法将该线程挂起,但这个方法容易导致死锁,所以应该尽量避免发方法

    死亡
    线程会在以下情况时,被处于死亡状态

    • run()或call()方法执行完成时,线程正常结束
    • 线程跑出一个未补货的Exception或Error
    • 调用该线程的stop()方法结束该线程,该方法容易导致死锁,不推荐使用

    线程池Executor

    创建和维护线程非常耗时,在企业开发中常见的办法是创建线程池,线程的创建和维护交给线程池处理。


    image.png
    public class ExecutorsCreateDemo {
        //使用Executors获取线程对象
        //通过线程池对象获取线程,并执行myRunnable实例
        //分别打印线程池和主线程输出
        public static void main(String[] args) {
    //        创建并设置线程池的大小
            ExecutorService executorService =Executors.newFixedThreadPool(5);
    //        执行
            executorService.execute(new MyRunnable());
            for(int i =0 ;i<=10;i++){
                System.out.println("主线程执行"+i+"次:时间"+ LocalTime.now());
            }
        }
    }
    

    线程安全

    如果多个线程同时运行同一个实现了Runnable接口的类,程序每次运行结果和单线程运行结果是一样的,二其他的变量的值也和预期一致,就是线程安全;反之,线程不安全。


    单线程卖火车票

    火车票对象

    public class Ticket implements Runnable {
        //    假设票有100张
        private int ticketNum = 100;
    
        @Override
        public void run() {
            while (true) {
                if (ticketNum > 0) {
    //                有票,让线程睡眠100毫秒
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    //                打印当前售出的票数和线程名
                    String threadName = Thread.currentThread().getName();
                    System.out.println("线程" + threadName + "当前剩余火车票" + ticketNum-- + "张");
                }
            }
        }
    }
    

    卖票窗口(一个线程一个窗口)

    public class ThreadSafeDemo {
        public static void main(String[] args) {
    //        创建电影票对象
            Ticket ticket = new Ticket();
    //        创建一个线程:售票窗口
            Thread thread = new Thread(ticket, "售票口1");
            thread.start();
        }
    }
    

    一个线程时,观察结果没有任何问题!
    对上面代码进行修改,增加一个买票窗口,再观察结果!

    加入了一个买票窗口

    public class ThreadSafeDemo {
        public static void main(String[] args) {
    //        创建电影票对象
            Ticket ticket = new Ticket();
    //        创建一个线程:售票窗口
            Thread thread1 = new Thread(ticket, "售票口1");
            thread1.start();
            Thread thread2 = new Thread(ticket, "售票口2");
            thread2.start();
            Thread thread3 = new Thread(ticket, "售票口3");
            thread3.start();
        }
    }
    
    image.png
    • 此时出现了线程不安全的情况

    当线程thread(窗口1)抢占到了cpu的执行权后并调用了sleep()方法后,线程会处于阻塞状态,ticketNum没有进行自剪操作,当窗口2拿到的票数还是之前的,就造成了两个窗口打印的票数一样的结果。

    问题分析

    线程安全问题都是由全局变量及静态变量引起的。
    若每个线程对全局变量、静态变量只读不写,一般来说,这个变量线程安全。
    若多个线程同时执行写的操作,一般都需要考虑线程同步,否则可能会影响线程安全。

    解决方法-线程同步

    要解决以上问题,只要在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺cpu资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的对象。
    java引用了7中线程同步机制。

    • 同步代码块(synchronized)
    • 同步方法(synchronized)
    • 同步锁(ReenreantLock)
    • 特殊域变量(volatile)
    • 局部变量(ThreadLocal)
    • 阻塞队列(LinkedBlockingQueue)
    • 原子变量(Atomic*)

    加锁synchronized:在同步代码块或同步方法上加锁

    public class Ticket implements Runnable {
        //    假设票有100张
        private int ticketNum = 100;
    
        private Object lock = new Object();
    
        @Override
        public void run() {
            while (true) {
    //            synchronized (lock) {
    //                if (ticketNum > 0) {
    ////                有票,让线程睡眠100毫秒
    //                    try {
    //                        Thread.sleep(100);
    //                    } catch (InterruptedException e) {
    //                        e.printStackTrace();
    //                    }
    ////                打印当前售出的票数和线程名
    //                    String threadName = Thread.currentThread().getName();
    //                    System.out.println("线程" + threadName + "当前剩余火车票" + ticketNum + "张");
    //                    ticketNum--;
    //                }
    //            }
                saleTickt();
            }
        }
    
        synchronized void saleTickt() {
    //        当该方法是非静态方法,synchronized锁的是调用该方法的对象
    //        当该方法是static,synchronized锁的是当前类的字节码对象。synchronized(Ticket.class)
            if (ticketNum > 0) {
    //                有票,让线程睡眠100毫秒
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    //                打印当前售出的票数和线程名
                String threadName = Thread.currentThread().getName();
                System.out.println("线程" + threadName + "当前剩余火车票" + ticketNum + "张");
                ticketNum--;
            }
        }
    }
    

    结果:全是窗口1在卖票
    上面方法看不到加锁的过程,所以有了同步锁(有好多实现类)
    同步锁

    public class Ticket implements Runnable {
        //    假设票有100张
        private int ticketNum = 100;
    
        //    重入锁
        // true:公平锁(每个线程都拥有拿到这个线程的权利);false:独占锁(只有某个线程执行完毕后才释放该锁)。独占锁相当于synchronized
        private Lock locked = new ReentrantLock(true);
    
        @Override
        public void run() {
            while (true) {
    //            加锁
                locked.lock();
                try {
                    if (ticketNum > 0) {
    //                有票,让线程睡眠100毫秒
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    //                打印当前售出的票数和线程名
                        String threadName = Thread.currentThread().getName();
                        System.out.println("线程" + threadName + "当前剩余火车票" + ticketNum + "张");
                        ticketNum--;
                    }
                } catch (Exception e) {
    
                } finally {
    //                解锁(必须要解锁,否则会出现死锁)
                    locked.unlock();
                }
            }
        }
    }
    
    image.png

    synchronized和Lock区别

    synchronized是jvm内置关键字,在jvm层面,Lock是java的一个类。
    synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁
    synchronized会自动释放锁(线程一执行完同步代码会释放锁,线程二在执行过程中发生异常会释放锁),Lock需在finally中手动释放锁(调用unlock()方法),否则容易发生死锁
    用synchronized关键字的两个线程1和2,如果当线程1获得锁,线程2等待。如果线程1阻塞,线程2则会一致等待下去;而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了。
    synchronized的锁可重入,不可中断、非公平,而LOCK锁可重入,可判断、公平和独占都可设置
    Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码量少的同步问题


    死锁的产生条件

    互斥条件
    进程要求对所有分配的资源(如打印机)进行排他性空值,即在一段时间内某资源仅为一个进程所战友。此时若有其他进程请求该资源,则请求进程只能等待

    不可剥夺条件
    进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己释放(只能是主动释放)

    请求与保持条件
    进程已经保持了至少一个资源,但又提出新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已经获得的资源保持不放

    循环等待条件
    存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求,即存在一个处于等待状态得集合(循环)。

    相关文章

      网友评论

          本文标题:线程

          本文链接:https://www.haomeiwen.com/subject/ggujzctx.html