美文网首页
Java多线程

Java多线程

作者: 海人为记 | 来源:发表于2018-07-20 20:52 被阅读9次

    进程:执行中的程序;一个进程至少包含一个线程

    线程:进程中负责程序执行的执行单元
    线程本身依靠程序进行运行
    线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
    单线程:程序中只存在一个线程,实际上主方法就是一个主线程
    多线程:指的是这个程序(一个进程)运行时产生了不止一个线程,目的是为了更好地使用CPU资源,多个线程并发执行可以提高程序的效率,可以同时完成多项工作,多线程节约的是执行程序的等待时间,如果程序排列紧密,没有等待时间,多线程不能真正的提高效率。

    相比于多进程,多线程的优势有:

    • 进程之间不能共享数据,线程可以
    • 系统创建进程需要为该进程重新分配系统资源,故创建线程代价比较小
    • Java语言内置了多线程功能支持,简化了Java多线程编程

    线程的实现

    继承Tread类的实现
    在java.lang包中定义,继承Thread类必须重写run()方法

    public class ThreadLearn extends Thread {
        
        @Override
        public void run() {
            System.out.println("主动创建线程");
        }
    }
    public class Example {
        public static void main(String[] args) {    
            ThreadLearn thread = new ThreadLearn();
            thread1.start();
        }
    }
    打印:主动创建线程
    
    public class ThreadLearn extends Thread {   
        private String name;
        public ThreadLearn(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            System.out.println("name"+this.name+"子线程ID"+Thread.currentThread().getId());
        }
    }
    public static void main(String[] args) {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        ThreadLearn thread1 = new ThreadLearn("thread1");
        thread1.start();        
        ThreadLearn thread2 = new ThreadLearn("thread2");
        thread2.run();
    }
    //打印结果
    主线程ID:1
    namethread2子线程ID1
    namethread1子线程ID11
    

    从输出结果可以得出以下结论:

    • thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;
    • 虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

    定义类实现Runnable接口
    在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。

    public class RunnableLearn implements Runnable {
        @Override
        public void run() {
            System.out.println("子线程ID:"+Thread.currentThread().getId());
        }
    }
    public class Example {
    
    public static void main(String[] args) {
        System.out.println("主线程ID:"+Thread.currentThread().getId());
        RunnableLearn runnable = new RunnableLearn();
        Thread thread = new Thread(runnable);
        thread.start();
    }
    //打印
    主线程ID:1
    子线程ID:11
    

    Thread类中定义了一个Runnable类型的成员变量target用来接收Runnable的子类对象;当调用Thread对象的start()方法的时候,方法内首先会判断target是否为null。如果不为null就调用Runnable子类对象的run方法;多个Thread对象可以共享一个Runnable子类对象。

    在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。
    继承Thread的多线程实现可以直接使用Thread类中的方法,代码简单;弊端是如果已经有了父类,就不能用这种方法(Java只允许单继承),所以如果自定义类需要继承其它类,则只能选择实现Runnable接口。而实现Runnable接口的类不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂。

    匿名内部类实现多线程的两种方式

    继承Thread类
    // 匿名内部类的多继承的实现
    new Thread() {
        public void run() {
            while(true) 
                System.out.println("继承Thread的线程");
        }
    }.start();
    Thread thread = new Thread() {
        public void run() {
            while(true) 
                System.out.println("继承Thread的线程");
        }
    };
    thread.start();
    //创建Thread对象,提供Runnable对象
    new Thread(new Runnable() {
        public void run() {
            while(true) {
                System.out.println("实现Runnable接口的线程");
            } 
        }
    }).start();
    Thread thread1 = new Thread(new Runnable() {
        public void run() {
        while(true)
                System.out.println("实现Runnable接口的线程");
        }
    });
    thread1.start();
    

    多线程的方法

    • 通过getName()方法获取线程对象的名字
    • 通过构造方法传入String类型的名字或通过setName(String name)方法设置线程对象的名字
    • 获取当前线程的对象Thread.currentThrad()
    • 休眠线程Thread.sleep(millis)//参数为毫秒
    • 守护线程setDaemon(): 围绕着其它非守护线程运行,该线程不会单独运行,当其它非守护线程都执行结束后,自动退出
    • 加入线程join()/join(int)
    • 礼让线程yield
    • 设置线程的优先级setPriority()
    public class Example {
    
        public static void main(String[] args) {
            // 获取当前主线程ID
            System.out.println("主线程ID:"+Thread.currentThread().getId());
            Thread thread = new Thread("线程一"); // 传入字符串设置线程名字
            String threadName = thread.getName(); // 获取线程名字
            System.out.println(threadName);  //线程一
            thread.setName("线程二");  // 将线程一改名为线程二
            threadName = thread.getName();
            System.out.println(threadName);  // 线程二
            
            try {
                Thread.sleep(1000);//休眠线程(设置线程等待1秒)
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }  
            
            //守护线程
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.print("线程t1运行中");
                }
            });
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("守护线程t2运行中");
                }
            });
            Thread t3 = new Thread(new Runnable() {
                @Override
                public void run() {
                        System.out.print("线程t3运行中");
                }
            });
            t2.setDaemon(true); // 设置守护线程
            t1.start();
            t3.start();
            t2.start(); // t1和t3运行之后,自动退出
            
            // 加入线程
            // 当前线程暂停,等待指定的线程执行结束后,当前线程在继续
            
            Thread tj1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i = 0; i < 50; i++) {
                        System.out.println("线程一运行中...");
                        try {
                            Thread.sleep(10);
                        }catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                    }   
                }
            });
            Thread tj2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    
                    for (int i = 0; i < 50; i++) {
                        System.out.println("线程二运行中..........");
                        try {
    //                      tj1.join(); //让线程一先执行
                            tj1.join(100);//让线程一先执行100毫秒,100毫秒之后,当前加入线程之后就不起作用了
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            //只有调用了加入线程的线程才会停止运行,其他线程不受影响
            Thread tj3 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    
                    for (int i = 0; i < 50; i++) {
                        System.out.println("线程三运行中..........");
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            tj1.start();
            tj2.start();
            tj3.start();
            
            // 礼让线程
            //让出当前线程的执行权
            Thread ty1 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    
                    for (int i = 0; i < 50; i++) {
                        System.out.println("y线程一运行中");
                        //让出当前线程的执行权
                        Thread.yield();
                    }
                }
                
            });
            
            Thread ty2 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    
                    for (int i = 0; i < 50; i++) {
                        System.out.println("y线程二运行中..........");        
                    }
                }
            });
    
            ty1.start();
            ty2.start();
            
            //设置线程的优先级
            //每个线程都有优先级 默认是5,范围是1-10,1表示优先级最低
            //优先级高的线程在争夺cpu的执行权上有一定的优势,但不是绝对的
            Thread tp1 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    
                    for (int i = 0; i < 50; i++) {
                        System.out.println("p线程一运行中");
                        
                    }
                }
                
            });
            
            Thread tp2 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    
                    for (int i = 0; i < 50; i++) {
                        System.out.println("p线程二运行中..........");
                        
                    }
                }
            });
            
            tp1.setPriority(1);
            tp2.setPriority(10); //线程二的优先级高
            tp1.start();
            tp2.start();
        }
    }
    

    共享资源及安全问题

    所谓的共享资源,指的是多条线程在运行时操作的(共享的)同一内容

    • 实现Runnable接口:
      操作共享资源,只需要在Runnable类中创建成员变量,通过run方法来操作该成员变量,该成员变量就是共享资源
    public class Demo2_apple {
    
        public static void main(String[] args) {
            // 思路:
            // 1.创建两条线程,接收实现Runnable接口的对象
            SubRunnable myRunnable = new SubRunnable();
            Thread t1 = new Thread(myRunnable);
            Thread t2 = new Thread(myRunnable);
            t1.setName("小明");
            t2.setName("小清");
            t1.start();
            t2.start();
        }
    
    }
    class SubRunnable implements Runnable {
        int appleCount = 5;
        @Override
        public void run() {
            while(appleCount>0) {
                appleCount--;
                System.out.println(Thread.currentThread().getName()+"抢到了一个苹果"
                        + ",还剩"+appleCount+"个苹果");
            }
        }
    }
    
    • 继承Thread类的方式:
      通过将共享资源作为对象,通过构造器传递给Thread
    public class Demo1_apple {
    
        // 两个小朋友,一个叫刘文浩,一个叫龚陈超,两个小朋友去抢苹果,
        // 一共有五个苹果,一次只能拿一个,看谁抢的多
        public static void main(String[] args) {
            // 思路:
            // 1.创建两条线程,将Apple类对象通过构造器传入
            Apple apple = new Apple();
            Thread t1 = new SubThread(apple);
            Thread t2 = new SubThread(apple);
            t1.setName("小明");
            t2.setName("小清");
            t1.start();
            t2.start();
        }
    
    }
    // 共享的资源
    class Apple {
        int appleCount = 5;
    }
    class SubThread extends Thread {
        Apple apple; // 共享资源
        public SubThread(Apple apple) {
            this.apple = apple;
        }
    
        @Override
        public void run() {
            while(apple.appleCount>0) {
                apple.appleCount--;
                System.out.println(Thread.currentThread().getName()+"抢到了一个苹果"
                        + ",还剩"+apple.appleCount+"个苹果");
            }
        }
    }
    

    发现了线程安全的问题
    在多条操作同一资源时,一条线成夹在另一条线程内执行,对共享资源进行多次修改,上面的代码执行的结果为

    通过实现Runnable接口实现的共享资源,打印的内容
    小明抢到了一个苹果,还剩4个苹果
    小清抢到了一个苹果,还剩4个苹果
    小明抢到了一个苹果,还剩3个苹果
    小清抢到了一个苹果,还剩2个苹果
    小明抢到了一个苹果,还剩1个苹果
    小清抢到了一个苹果,还剩0个苹果
    通过继承Thread类的方式实现的共享资源,打印的内容
    小明抢到了一个苹果,还剩3个苹果
    小明抢到了一个苹果,还剩2个苹果
    小明抢到了一个苹果,还剩1个苹果
    小清抢到了一个苹果,还剩3个苹果
    小明抢到了一个苹果,还剩0个苹果
    

    上面的输出结果,两条线程有输出为一样的结果,这就是两个线程都对资源进行了修改,这样就出现了线程安全问题,我们要怎么解决这个问题呢,答案是使用锁.

    多线程加锁

    Java通过①synchronized关键字;②Java.util.concurrent包中的lock接口和ReentrantLock实现类这两种方式来实现加锁.

    synchronized

    • 同步代码块:谁充当着锁,synchronized(对象锁)
      同步代码块使用的是对象锁,"对象锁"对对象没有要求,任何对象都可以充当锁,对象锁的意义是形成互斥
      注意:当一个对象充当锁时,该对象锁要同时被多条线程共享才有意义,否则不能形成互斥; 对象锁的互斥是面向所有线程的.
    • 同步方法:方法所在的对象充当着锁.
    public synchronized void method(){}
    
    • 静态同步方法:所在类的类模板(字节码)充当着锁
    public static synchronized void method() {} // 是这个类的类对象充当着锁 Class.class
    

    当我们给线程加上所之后

            while(apple.appleCount>0) {
                synchronized(apple) {
                    if(apple.appleCount>0) {
                        apple.appleCount--;
                        System.out.println(Thread.currentThread().getName()+"抢到了一个苹果"
                        + ",还剩"+apple.appleCount+"个苹果");
                    }
                }
            }
    

    执行的结果,就不会出现了重复的苹果

    ReentrantLock

    使用ReentrantLock对象实现上锁

    class MyRunnable1 implements Runnable {
        int num = 0;
        Lock lock = new ReentrantLock();
        @Override
        public void run() {
            while(num<100) {
                lock.lock();
                num++;
                System.out.println(Thread.currentThread().getName()+":"+num);           
                lock.unlock();
            }
        }
    }
    

    线程的死锁

    死锁的定义:使用同步的多个线程同时持有对方运行时所需的资源
    多线程同步时,当线程需要需要的锁被另一条线程占用,另一条线程没有办法释放锁,因此当前线程也无法获得所,于是程序就卡住了.
    应该尽量避免使用嵌套的synchronized,因为这样会很容易出现死锁问题.

    public class Demo4_deadLock {
        public static void main(String[] args) {        
            final StringBuffer sb1 = new StringBuffer();
            final StringBuffer sb2 = new StringBuffer();
            new Thread() {
                @Override
                public void run() {
                    synchronized(sb1) {
                        sb1.append("a");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        synchronized(sb2) {
                            sb2.append("b");
                        }
                    }
                    
                }
            }.start();      
            new Thread() {
                @Override
                public void run() {
                    synchronized(sb2) {
                        sb2.append("c");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        synchronized(sb1) {
                            sb1.append("d");
                        }
                    }
                }
            }.start();      
            System.out.println(sb1);
            System.out.println(sb2);
        }
    }
    
    image

    线程的通信

    1. 使用synchronized实现线程的通信

    多条线程之间实现通信,需要使用以下几个方法

    • wait() 让当前线程等待
    • notify() 唤醒正在等待的某一条线程,选择是随机的
    • notifyAll() 唤醒正在等待的所有线程

    我们现在用一个例子来说明线程之间的通信

    // 线程的通信 synchronized
    public class Demo5_communication {
        // 让两个线程交替打印1~100
        public static void main(String[] args) {
            
            MRCommRunnable runnable = new MRCommRunnable();
            Thread t1 = new Thread(runnable);
            Thread t2 = new Thread(runnable);
            t1.setName("线程1");
            t2.setName("线程2");
            t1.start();
            t2.start();
            
        }
    }
    class MRCommRunnable implements Runnable {
        int num = 1;
        @Override
        public void run() {
            while(num < 100) {
                synchronized(this) {
                    notifyAll();
                    num++;
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    这样使用就可以实现线程之间的交替打印
    我们在main方法中在创建一个线程

    public static void main(String[] args) {
        Thread t3 = new Thread( new Runnable() {
            @Override
            public void run() {
                while(num1 < 100) {
                    synchronized(this) {
                        this.notifyAll();
                        num1++;
                        System.out.println(Thread.currentThread().getName()+":"+num1);
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        t3.setName("线程3");
        t3.start();
    }
    

    当在执行后,这个方法进入线程等待状态 wait(),而在Runnable子类中的唤醒功能却唤醒不了t3线程,这是因为它们的对象锁不是一样的.
    注意:这几个方法,只能在synchronized中使用;对象锁拥有两个队列,用于存放被等待的线程,和被唤醒的线程,因此,wait, notify和notifyAll方法是属于Object的;对象锁是谁,就由谁来调用这三个方法

    synchronized(a) {
            a.wait();  //wait()==this.wait()  不行
    }
    

    2.使用Lock实现线程的通信

    因为wait, notify,notifyAll只能使用在synchronized关键字中,因此,Lock使用Condition对象的await,singal, singalAll来代替.通过Lock对象的newCondition()来获得Condition对象.

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    // 线程通信 Lock
    public class Demo6_comm_lock {
        // 让两个线程交替打印1~100
        public static void main(String[] args) {
            MRCommRunnableLock runnable = new MRCommRunnableLock();
            Thread t1 = new Thread(runnable);
            Thread t2 = new Thread(runnable);
            t1.setName("线程1");
            t2.setName("线程2");
            t1.start();
            t2.start();
        }
    }
    class MRCommRunnableLock implements Runnable {
        int num = 1;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition(); // 通过Condition对象实现线程的通信
        @Override
        public void run() {
            while(num < 100) {
                lock.lock(); // 上锁
                condition.signalAll(); // 唤醒线程
                num++;
                System.out.println(Thread.currentThread().getName()+":"+num);
                try {
                    condition.await(); // 让线程等待
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } finally {
                    lock.unlock(); // 解锁
                }
            }
        }
    }
    

    线程的的局部变量-------ThreadLocal

    堆空间中的资源往往对于多线程来说都是共享的.在设计一个类时,如果希望类的某些属性不被多线程共享,就可以把这些属性设计交给ThreadLocak来维护.于是,每条线程在使用该类时,都会自动创建ThreadLocal的副本,在实际操作中,操作的每个线程独有的那个ThreadLocak,而不会实现共享.

    public class Demo7_threadLocal {
    
        public static void main(String[] args) {
                
            MRThreadLocalRunnable runnable = new MRThreadLocalRunnable("aaa", 123);
            Thread t1 = new Thread(runnable);
            Thread t2 = new Thread(runnable);
            t1.setName("肖邦");
            t2.setName("爱迪生");
            t1.start();
            t2.start();
        }
    
    }
    class MRThreadLocalRunnable implements Runnable {
        private String threadName = new String();
        int num;
        public MRThreadLocalRunnable(String threadName, int num) {
            this.threadName = threadName;
            this.num = num;
        }
        @Override
        public void run() {
            for(int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"name:"+threadName);
                // 设置当前线程的名字
                if(i==5)
                    threadName = Thread.currentThread().getName();
            }
        }
    }
    这输出打印的是
    肖邦name:aaa
    肖邦name:aaa
    肖邦name:aaa
    肖邦name:aaa
    肖邦name:aaa
    肖邦name:aaa
    肖邦name:肖邦
    肖邦name:肖邦
    肖邦name:肖邦
    肖邦name:肖邦
    爱迪生name:aaa
    爱迪生name:肖邦
    爱迪生name:肖邦
    爱迪生name:肖邦
    爱迪生name:肖邦
    爱迪生name:肖邦
    爱迪生name:爱迪生
    爱迪生name:爱迪生
    爱迪生name:爱迪生
    爱迪生name:爱迪生
    
    public class Demo7_threadLocal {
    
        public static void main(String[] args) {
                
            MRThreadLocalRunnable runnable = new MRThreadLocalRunnable("aaa", 123);
            Thread t1 = new Thread(runnable);
            Thread t2 = new Thread(runnable);
            t1.setName("肖邦");
            t2.setName("爱迪生");
            t1.start();
            t2.start();
        }
    
    }
    class MRThreadLocalRunnable implements Runnable {
        // 线程的局部变量
        private ThreadLocal<String> threadName = new ThreadLocal<>();
        int num;
        public MRThreadLocalRunnable(String threadName, int num) {
            this.threadName.set(threadName); // 设置线程的局部变量
            this.num = num;
        }
        @Override
        public void run() {
            for(int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+"name:"+threadName.get());
                // 设置当前线程的名字
                if(i==5)
                    threadName.set(Thread.currentThread().getName());
            }
        }
    }
    输出打印的是
    爱迪生name:null
    爱迪生name:null
    爱迪生name:null
    肖邦name:null
    肖邦name:null
    肖邦name:null
    爱迪生name:null
    肖邦name:null
    爱迪生name:null
    肖邦name:null
    肖邦name:null
    爱迪生name:null
    肖邦name:肖邦
    爱迪生name:爱迪生
    爱迪生name:爱迪生
    爱迪生name:爱迪生
    爱迪生name:爱迪生
    肖邦name:肖邦
    肖邦name:肖邦
    肖邦name:肖邦
    

    这是因为使用线程的局部变量 ThreadLocal,你在设置名字的时候,它设置的都是属于每个线程的变量,而不是共享的变量

    线程池

    当频繁地创建和启动多线程,来完成一些比较小的任务,那么这样的代价是非常大的。因此,可以通过线程池来实现,将线程的创建和销毁都交给线程池来管理,而我们需要使用线程时,将任务提交给线程池即可,由线程池来为我们安排一条线程,来执行我们的任务。
    jdk1.5之后就提供了相应的类,实现线程池。

    1. 创建线程池的方式

    • 创建一个有多条线程的线程池,如果线程池中的线程没有可用,就新建一条线程
    Executors.newCachedThreadPool()
    
    • 创建一个有多条线程的线程池,如果线程池中的线程没有可用,就进入到等待队列,直到有可用的线程为止
    Executors.newFixedThreadPool(int nThreads)
    

    2. 线程池的使用

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Demo8_threadPool {
    
        public static void main(String[] args) {
            // 创建一个指定线程数量的线程池
            ExecutorService pool = Executors.newFixedThreadPool(5);
            // 创建任务
            MYRunnable my = new MYRunnable();
            // 将任务交给任务池,有任务池安排线程来执行任务
            pool.submit(my);
            pool.submit(my);
            pool.submit(my);
            pool.submit(my);
            pool.submit(my);
            pool.submit(my);
            pool.submit(my);
            pool.submit(my);
            // 关闭线程池
            pool.shutdown();
        }
    
    }
    class MYRunnable implements Runnable {
        @Override
        public void run() {
    
            System.out.println("aaaaaaaaaabbbbbccdddeeefffggghhiizzkk");
        }
    }
    

    注意:之前创建的线程都只能执行一次,但是使用线程池的方式,可以将任务执行多次.

    生产者和消费者模式

    1.是否涉及到多线程? 是
    2.是否涉及到共享资源? 是

    1. 是否涉及到线程安全问题? 是
    2. 是否涉及到线程的通信? 是
      生产者和消费者都是多线程,它们共享的是商店这个资源,对它进行操作,具体的操作放在共享资源中进行操作

    什么时候会失去CPU

    • 时间片到了
    • 被强占了
    • 使用了 yield() 礼让线程
    • 使用了 join() 加入线程
    • 使用了sleep() 休眠线程
    • 使用了 wait() 使线程处于等待状态

    是么时候会释放锁

    • wait()
    • run结束
    • 程序出现了异常

    相关文章

      网友评论

          本文标题:Java多线程

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