美文网首页
第13章 多线程

第13章 多线程

作者: wqjcarnation | 来源:发表于2021-01-14 16:23 被阅读0次

    目标

    1、实现方式
    2、生命周期
    3、 线程调度常用方法

    1、实现方式

    • 继承Thread类
        public class MyThread extends Thread {
        
            @Override
            public void run() {
                for (int i = 0; i <100; i++) {
                    System.out.println("thread1---->"+i);
                }
                
            }
        }
    
    
        public class MyThread2 extends Thread {
        
            @Override
            public void run() {
                for (int i = 0; i <100; i++) {
                    System.out.println("thread2---->"+i);
                }
            }
        
        }
    
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        System.out.println("线程1启动");
        thread1.start();// 一定是start,不是run
    
        MyThread2 thread2 = new MyThread2();
        System.out.println("线程2启动");
        thread2.start();
    }
    
    • 实现Runnable接口

        public class RunnableDemo implements Runnable {
        
            @Override
            public void run() {
                for (int i = 0; i <1000; i++) {
                    System.out.println("thread1---->"+i);
                    
                }
        
            }
        
        }
      
        public class RunnableDemo2 implements Runnable {
        
            @Override
            public void run() {
                for (int i = 0; i <100; i++) {
                    System.out.println("thread2---->"+i);
                }
        
            }
        
        }
      

    public static void main(String[] args) {

        RunnableDemo run1=new RunnableDemo();
        Thread thread1 = new Thread(run1);
        System.out.println("线程1启动");
        thread1.start();// 一定是start,不是run
    
        RunnableDemo2  run2= new RunnableDemo2();
        Thread thread2 = new Thread(run2);
        System.out.println("线程2启动");
        thread2.start();
    }
    

    2、线程生命周期

    线程的生命周期即线程的生老病死,即线程的状态
    可以通过getState()返回此线程的状态。
    返回值为枚举类型Enum Thread.State

    线程状态。 线程可以处于以下状态之一:

    • [NEW]新建状态
      尚未启动的线程处于此状态。新创建了一个线程对象,但未调用start方法

    • [RUNNABLE]就绪状态,即可运行状态,复合状态
      包含ready和running两个状态
      ready:表示该线程处于可被调度调度的状态
      运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
      Thread.yield()方法可以将线程由Running状态变为ready 状态

    • [BLOCKED]阻塞状态
      被阻塞等待监视器锁定的线程处于此状态。
      线程发起I/O操作或者申请由其他线程独占的资源,不占用CPU
      当阻塞I/O执行完毕可或者获得了申请的资源,状态就变为RUNNABLE

    • [WAITING]
      正在等待另一个线程执行特定动作的线程处于此状态。
      线程执行了object.wait() 或thread.join()方法就会把线程转换为等待状态,执行object.notify()方法或者加入的线程执行完毕,线程将重回RUNNABLE状态

    • [TIMED_WAITING]
      正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
      与WAITING的区别是不会无限期的等待,而是等待指定时间后不管期望的操作是否执行完毕,线程都将重回RUNNABLE状态

    • [TERMINATED]终止状态
      已退出的线程处于此状态。

    线程状态图


    image.png

    3、线程常用方法

    方法 功能
    getName() 获取当前线程名称
    setName() 获取当前线程名称
    isAlive() 判断线程是否存活(就绪、运行、阻塞是存活状态)
    getPriority() 获取线程优先级
    setPriority() 设置线程优先级
    Thread.sleep() 强迫线程睡眠(单位:毫秒)(不释放同步锁)
    join() (插队)等待某线程结束,再恢复当前线程的运行
    yield() 让出CPU资源,当前线程进入就绪状态
    wait() 当前线程进入等待状态,即进入等待池。(释放同步锁,由notify()唤醒)
    notify() 唤醒等待池中的某个线程
    notifyAll() 唤醒等待池中的全部等待线程

    1.5.1 判断线程是否存活

    ThreadTest t1 = new ThreadTest();
    ThreadTest t2 = new ThreadTest();
    t1.start();
    t2.start();
    try {
    Thread.sleep(1000);              //让主线程睡眠1秒,t1线程肯定死亡。
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
      System.out.println(t1.isAlive());
    

    1.5.2 线程的优先级
    Java线程有优先级,优先级高的线程会获得
    较多的CPU运行机会。

    Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
    Java线程的优先级用整数表示,取值范围是1~10; 10最高级。
    每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY(5)。

    ThreadTest t1 = new ThreadTest();
    t1.setPriority(1);                     //设置成最低级
    ThreadTest t2 = new ThreadTest();
    t2.setPriority(10);                    //设置成最高级
    t1.start();
    t2.start();
    

    1.5.3 线程睡眠
    Thread.sleep() 强迫线程睡眠(单位:毫秒)

    class ThreadTest extends Thread{
        public void run(){
            for(int i=0;i<10;i++){
                System.out.println(i);
                try {
                    Thread.sleep(1000);         //每隔一秒后输出
                } catch (InterruptedException e) {  //线程睡眠期间如果被打断,将抛出异常。
                e.printStackTrace();
                }
            }
        }
    }
    

    1.5.4 合并线程
    join()的功能是:等待某线程结束,再恢复当前线程的运行
    或者说:join()把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。

    class ThreadTest extends Thread{
        public void run(){
            for(int i=0;i<10;i++){
                System.out.println(this.getName() + ":" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    //相当于将tt线程和main线程合并成一个线程。
    //那么main线程会等待tt线程结束后再运行。

    public static void main(String[] args) {
        ThreadTest tt = new ThreadTest ();
        tt.start();
        try {
            tt.join();
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
       for(int i=0;i<10;i++){
        System.out.println("main:" + i);
        }
    

    }

    1.5.5 让出CPU资源
    yield() 让出CPU资源,当前线程进入就绪状态

    class ThreadTest extends Thread{
        public void run(){
            for(int i=0;i<100;i++){
                System.out.println(this.getName() + ":" + i);
                if(i%10==0){
                    yield();
                }
        }
    }
    }
    public static void main(String[] args) {
    ThreadTest tt1 = new ThreadTest ();
    ThreadTest tt2 = new ThreadTest ();
    tt1.start();
    tt2.start();    
    }
    

    运行结果:
    运行到10时,线程让出CPU资源,进入就绪状态。
    注意: yield()是让出资源,但并不放弃。它会进入就绪状态,也就是说:它还会与其他线程
    一起抢占资源,所以yield()的线程,仍然有可能再次抢占资源。
    在加上线程运行的不确定性,所以会导致上面的结论并不是绝对的,只是出现的
    概率要高一些。

    1.5.6 线程等待与唤醒
    wait():当前线程进入等待状态,即进入等待池。(释放同步锁,由notify()唤醒);
    notify()/notifyAll():唤醒等待池中的某个或全部等待线程。

    wait()和notify()一系列的方法,是属于对象的,不是属于线程的。它们用在线程同步时,synchronized语句块中。因为他们是操作同步锁的。

    通俗的说:
    wait()意思是说,我等会儿再用这把锁,CPU也让给你们,我先休息一会儿!
    notify()意思是说,我用完了,你们谁用?

    也就是说,wait()会让出对象锁,同时,当前线程休眠,等待被唤醒,如果不被唤醒,就一直等在那儿。
    notify()并不会让当前线程休眠,但会唤醒休眠的线程。

    多线程同步

    synchronized:同步锁(互斥锁):
    在java语言中,引入了同步锁的概念,用以保证共享数据的安全性问题。
    关键词synchronized用来给某个方法或某段代码加上一个同步锁。

    https://blog.csdn.net/weixin_39214481/article/details/80489586

        package com.neuedu.thread.sync;
        
        public class Bank {
            private int count=0;
            //同步的第一种 方式在方法上加synchronized关键字
           //存钱
            public synchronized void add(int money){
                
                count+=money;
                System.out.println(System.currentTimeMillis()+"存入"+money);
                
            }
           //取钱
            
            public void sub(int money){
                
                synchronized (this) {
                    if(count>=money){
                        count-=money;
                        System.out.println(System.currentTimeMillis()+"取"+money);
                    }else{
                        System.out.println(System.currentTimeMillis()+"取"+money+"余额不足");
                    }
                }
                
                
                
            }
            
            
           //查看余额
            public void search(){
                System.out.println("余额"+count);
            }
        }
    
    
    
        package com.neuedu.thread.sync;
        
        public class TestBank {
            
            
            public static void main(String[] args) {
                final Bank bank=new Bank();
                Thread t1=new Thread(new Runnable(){
        
                    @Override
                    public void run() {
                        //在这个线程里做向bank里存钱的操作,并查询余额
                        for(int i=0;i<20;i++){
                        bank.add(100);
                        bank.search();
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        }
                        
                    }
                    
                });
                
                Thread t2=new Thread(new Runnable(){
        
                    @Override
                    public void run() {
                        //在这个线程里做向bank里存钱的操作,并查询余额
                        
                        for(int i=0;i<20;i++){
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        bank.sub(100);
                        bank.search();
                        }
                    }
                    
                });
                
                t1.start();
                t2.start();
                
                
            }
            
        
        }
    

    生产者消费者问题

    生产者-消费者(producer-consumer)问题,也称作有界缓冲区(bounded-buffer)问题,两个线程共享一个公共的固定大小的缓冲区。其中一个是生产者,用于将消息放入缓冲区;另外一个是消费者,用于从缓冲区中取出消息。问题出现在当缓冲区已经满了,而此时生产者还想向其中放入一个新的数据项的情形,其解决方法是让生产者此时进行休眠,等待消费者从缓冲区中取走了一个或者多个数据后再去唤醒它。同样地,当缓冲区已经空了,而消费者还想去取消息,此时也可以让消费者进行休眠,等待生产者放入一个或者多个数据时再唤醒它。

    1、仓储类

        public class Storage {
            private int count = 0;
        
            public synchronized void add() {
                if (count >= 5) {
                    try {
                        System.out.println("库存>=5个,暂停生产" + count);
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                count++;
                System.out.println("生产了一个,仓库中有:" + count);
                // 生产完了,通知消费者消费
                this.notify();
            }
        
            public synchronized void sub() {
                if (count <= 0) {
                    try {
                        System.out.println("库存<=0个,暂停售卖" + count);
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                count--;
                System.out.println("消费了一个,仓库中有:" + count);
                // 没有库存了,通知生产者生产
                this.notify();
            }
            
    
        }
    

    生产者

        public class Producer extends Thread{
            private Storage storage;
            
            public Producer(Storage storage) {
                this.storage=storage;
            }
            @Override
            public void run() {
                for(int i=0;i<50;i++){
                    this.storage.add();
                }
            }
            
        }
    

    消费者

        public class Consumer extends Thread {
            private Storage storage;
            
            public Consumer(Storage storage) {
                this.storage=storage;
            }
        
            @Override
            public void run() {
                for(int i=0;i<50;i++){
                    this.storage.sub();
                }
        
            }
        
        }
    

    测试生产/消费

        public static void main(String[] args) {
                    Storage storage = new Storage();
                    Producer producer = new Producer(storage);
                    Consumer customer = new Consumer(storage);
                    
                    customer.start();
                    producer.start();
                }
    

    线程死锁演示

        public class TestLock {
            public static String objA = "strA";
            public static String objB = "strB";
            
            public static void main(String[] args) {
                Lock1 l1=new Lock1();
                Thread t1=new Thread(l1);
                
                Lock2 l2=new Lock2();
                Thread t2=new Thread(l2);
                
                t1.start();
                t2.start();
            }
        }
    
    
    
        public class Lock1 implements Runnable{
        
            @Override
            public void run() {
                try{
                                System.out.println("Lock1 running");
                                 while(true){
                                     synchronized(TestLock.objA){
                                         System.out.println("Lock1 lock strA");
                                        Thread.sleep(5000);//获取strA后先等一会儿,让Lock2有足够的时间锁住strB
                                         synchronized(TestLock.objB){
                                             System.out.println("Lock1 lock strB");
                                         }
                                     }
                                 }
                             }catch(Exception e){
                                 e.printStackTrace();
                             }
                
            }
        
        }
    
    
        package com.neuedu.thread.lock;
        
        public class Lock2 implements Runnable{
        
            @Override
            public void run() {
                try{
                                System.out.println("Lock2 running");
                                 while(true){
                                     synchronized(TestLock.objB){
                                         System.out.println("Lock1 lock strB");
                                        Thread.sleep(5000);//获取strA后先等一会儿,让Lock2有足够的时间锁住strB
                                         synchronized(TestLock.objA){
                                             System.out.println("Lock1 lock strA");
                                         }
                                     }
                                 }
                             }catch(Exception e){
                                 e.printStackTrace();
                             }
                
            }
        
        }
    

    总结:
    一、产生死锁的必要条件:
    虽然线程在运行过程中可能会发生死锁,但产生死锁是必须具备一定条件的。产生死锁必须同时具备下面四个必要条件,只要其中任意一个条件不成立,死锁就不会产生:
    (1)互斥条件:线程对所分配到的资源进行排他性使用,即在一段时间内,某资源只能被一个线程占用。如果此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的线程释放该资源。
    (2)请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己以获得的资源保持不放。
    (3)不可抢占条件:线程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时由自己释放。
    (4)循环等待条件。在发生死锁时,必然存在一个线程—资源的循环链,即线程集合{P0,P1,P2,P3,...,Pn}中的P0正在等待P1占用的资源,P1正在等待P2占用的资源,... ... ,Pn正在等待已被P0占用的资源。

    二、处理死锁的方法
    (1)预防死锁。该方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个来预防产生死锁。
      (2)避免死锁。在资源的动态分配过程中,用某种方法防止系统进入不安全状态,从而可以避免产生死锁。
    (3)检测死锁。通过检测机构及时地检测出死锁的发生,然后采取适当的措施,把进程从死锁中解脱出来。
      (4)解除死锁。当检测到系统中已发生死锁时,就采取相应的措施,将进程从死锁状态中解脱出来。常用方法是---撤销一些进程,回收他们的资源,将他们分配给已处于阻塞状态的进程,使其能继续运行。

    相关文章

      网友评论

          本文标题:第13章 多线程

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