• 中文
    • English
  • 注册
  • 查看作者
  • Java-多线程

    一.  多线程

    多线程则指的是在单个程序中可以同时运行多个不同的线程执行不同的任务,Java支持多线程。

    二. 线程

    线程是程序执行的最小单元,线程一旦启动很难控制,由CPU决定线程的执行顺序。

    三.  进程

    一个进程可以包含多个线程,多个线程共享进行所拥有的系统资源(网络,CPU,磁盘,内存)

    Java实现多线程的方式

    • 继承Thread类

    • 实现Runnable接口

    • 实现Callable接口(从jdk1.5加入)

    接下来我们分别讲解一下上面的三种方式

    四.  继承Thread类

    我们可以通过集成Thread类的方式来实现多线程, 使用Thread类实现多线程步骤:

    1.  继承Thread类

    2.  重写run方法

    3.  调用start方法启动线程

    public class Threads extends Throwable{
        public static void main(String[] args) {
            MyThread t1 = new MyThread();
            t1.setName("线程A");
            MyThread t2 = new MyThread();
            t2.setName("线程B");
            //直接调用run方法,会顺序执行,不会启动线程
    //        t1.run();
    //        t2.run();
    
            //想启动线程,需要使用start方法, 可以看到每个线程不规律的交替执行
            t1.start();
            t2.start();
    //      t1.start(); 已经启动的线程不能重复启动,会报java.lang.IllegalThreadStateException
        }
    }
    
    /*
    * 使用Thread类实现多线程步骤:
    * 1.  继承Thread类
    * 2.  重写run方法
    * */
    class MyThread extends Thread{
    
        @Override
        public void run(){
            for (int i = 0; i < 10; i++) {
                //获取当前线程的名字
                System.out.println(getName()+"=" + i);
                
            }
        }
    }

    五.  实现Runnable接口

    在上一节中,我们讲解了如何继承Thread来实现多线程,但是种继承的方式有一个弊端就是一个类只能有一个父类,一旦继承了Thread,就无法再继承其他的类,但是接口不同,接口可以多实现,还可以在实现接口的同时去继承别的类,所以我们再来看一下如何通过实现Runnable接口的方式实现多线程。

    1.  创建一个类实现Runnable接口

    2.  重写run方法

    3.  将Runnable对象传入Thread类的构造方法

    4.  调用Thread的方法启动线程

    我们可以Thread.currentThread() 方法来获取当前进程

    public class RunnableDemo {
        public static void main(String[] args) {
            MyRunnable myRunnable = new MyRunnable();
            Thread t1 = new Thread(myRunnable);
            t1.setName("线程A");
            Thread t2 = new Thread(myRunnable,"线程B");
            t1.start();
            t2.start();
    
    
        }
    }
    class MyRunnable implements Runnable {
        @Override
        public void run(){
            for (int i = 0; i < 10; i++) {
    
    //            System.out.println(getName()+"=" + i);  // 无法直接获取当前线程的名字,需要先获取当前线程
                Thread thread = Thread.currentThread();
                System.out.println(thread.getName()+"=" + i);
    
    
            }
        }
    }

    六.  线程之间的数据共享

    我们可以通过一个模拟售票的场景来讲解一下线程之间的数据共享。假如现在一个售票窗口有10张票,有20个人来买,我们先用继承Thread类方式来模拟售票:

    public class TicketThreadDemo {
        public static void main(String[] args) {
    
            TicketThread t1 = new TicketThread();
            TicketThread t2 = new TicketThread();
            t1.setName("窗口1");
            t2.setName("窗口2");
            t1.start();
            t2.start();
        }
    }
    
    class TicketThread extends Thread{
        private int ticket = 10;
        private int users = 20;
    
        @Override
        public void run() {
            for (int i = 0; i < users; i++) {
    
                if(ticket > 0) {
                    System.out.println(getName() + "卖出了第" + ticket-- + "张票");
                }
    
            }
        }
    }
    
    /*输出:
    窗口1卖出了第10张票
    窗口1卖出了第9张票
    窗口1卖出了第8张票
    窗口1卖出了第7张票
    窗口1卖出了第6张票
    窗口1卖出了第5张票
    窗口1卖出了第4张票
    窗口1卖出了第3张票
    窗口1卖出了第2张票
    窗口1卖出了第1张票
    窗口2卖出了第10张票
    窗口2卖出了第9张票
    窗口2卖出了第8张票
    窗口2卖出了第7张票
    窗口2卖出了第6张票
    窗口2卖出了第5张票
    窗口2卖出了第4张票
    窗口2卖出了第3张票
    窗口2卖出了第2张票
    窗口2卖出了第1张票
    
    Process finished with exit code 0
    
     */

    可以看到每创建一个Thread类的对象就会多出10张票,

    接下来,再来看一下用实现Runnable接口的方式来模拟售票:

    public class TicketRunnableDemo {
        public static void main(String[] args) {
    
            TicketRunnable ticketRunnable = new TicketRunnable();
            //创建两个线程
            Thread t1 = new Thread(ticketRunnable,"窗口1");
            Thread t2 = new Thread(ticketRunnable,"窗口2");
            t1.start();
            t2.start();
        }
    }
    
    class TicketRunnable implements Runnable{
        private int ticket = 10;
        private int users = 20;
    
        @Override
        public void run() {
            for (int i = 0; i < users; i++) {
                try {
                    Thread.sleep(500);
    //在指定的时间内睡眠(暂停)线程。添加该方法是为了方便我们查看问题所在,但这并不是导致问题出现的原因
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if(ticket > 0) {
                    Thread thread = Thread.currentThread();
                    System.out.println(thread.getName() + "卖出了第" + ticket-- + "张票");
                }
    
            }
        }
    }
    
    /*输出:
    窗口2卖出了第9张票
    窗口1卖出了第10张票
    窗口2卖出了第8张票
    窗口1卖出了第7张票
    窗口2卖出了第6张票
    窗口1卖出了第5张票
    窗口2卖出了第4张票
    窗口1卖出了第3张票
    窗口2卖出了第2张票
    窗口1卖出了第2张票
    窗口1卖出了第1张票
    窗口2卖出了第0张票
     */

    上面的代码也存在两个问题:

    重复卖票

    卖出不存在的票

    而且在控制台输出的时候,每500毫秒都是一次蹦出两条结果,这是因为这两个线程访问一个数据对象时,对数据造成了破坏,举个简单的例子,当还剩2张票的时候,线程1和线程2都进入if(ticket > 0),线程1卖出一张票,ticket变成1,而线程2又卖出一张票,ticket变成0。

    我们可以使用synchronized 线程同步语句块来解决上面的问题,线程的同步是保证多线程安全访问竞争资源的一种手段。

    public class TicketRunnableDemo2 {
        public static void main(String[] args) {
    
            TicketRunnable2 ticketRunnable = new TicketRunnable2();
            //创建两个线程
            Thread t1 = new Thread(ticketRunnable,"窗口1");
            Thread t2 = new Thread(ticketRunnable,"窗口2");
            t1.start();
            t2.start();
        }
    }
    class TicketRunnable2 implements Runnable{
        private int ticket = 10;
        private int users = 20;
    
        @Override
        public void run() {
            for (int i = 0; i < users; i++) {
                synchronized (this){
                //同步语句块中的代码同一时间只能有一个线程进入操作
                    if(ticket > 0) {
                        try {
                            Thread.sleep(500); //加在哪里都不会影响线程的数据的正确性
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Thread thread = Thread.currentThread();
                        System.out.println(thread.getName() + "卖出了第" + ticket-- + "张票");
                    }
                }
    
            }
        }
    }
    
    /*
    输出:
    窗口1卖出了第10张票
    窗口1卖出了第9张票
    窗口1卖出了第8张票
    窗口1卖出了第7张票
    窗口2卖出了第6张票
    窗口2卖出了第5张票
    窗口2卖出了第4张票
    窗口2卖出了第3张票
    窗口2卖出了第2张票
    窗口2卖出了第1张票
    */

    此时控制台,每隔50毫秒输出1条数据,且数据正常。

    除了使用同步语句块的同步线程,还可以使用同步方法来同步线程:

    public class TicketRunnableDemo3 {
        public static void main(String[] args) {
    
            TicketRunnable3 ticketRunnable = new TicketRunnable3();
            //创建两个线程
            Thread t1 = new Thread(ticketRunnable,"窗口1");
            Thread t2 = new Thread(ticketRunnable,"窗口2");
            t1.start();
            t2.start();
        }
    }
    
    class TicketRunnable3 implements Runnable{
        private int ticket = 10;
        private int users = 20;
    
        @Override
        public void run() {
            for (int i = 0; i < users; i++) {
                   sale();
    
            }
        }
    //在方法中添加synchronized关键字,就可以将该方法设置为同步方法
        //同步方法同一时间只允许一个线程进入方法
        private synchronized void sale(){
            if(ticket > 0) {
                try {
                    Thread.sleep(500); //加在哪里都不会影响线程的数据的正确性
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Thread thread = Thread.currentThread();
                System.out.println(thread.getName() + "卖出了第" + ticket-- + "张票");
            }
        }
    }
    
    /*
    输出:
    窗口1卖出了第10张票
    窗口1卖出了第9张票
    窗口1卖出了第8张票
    窗口1卖出了第7张票
    窗口1卖出了第6张票
    窗口1卖出了第5张票
    窗口1卖出了第4张票
    窗口2卖出了第3张票
    窗口2卖出了第2张票
    窗口2卖出了第1张票
    */

    七. 通过Callable接口实现多线程

    Callable是JDK1.5新加入的功能,Callable是一个泛型接口,和Runnable不同的是,Callable没有run()方法,只有一个call()方法,我们可以call()方法抛出异常,并且返回一个指定的泛型类型对象,比如返回一个字符串,利用Callable接口,我们可以使用父线程来获取子线程的运行结果。

    使用Callable接口实现多线程的步骤

    1.  创建一个类实现Callable接口

    2.  重写call方法

    3.  将Callable对象传入FutureTask类的构造方法

    4.  将FutureTask对象传入Thread类的构造方法,注意:FutureTask实现了Runnable接口和Future接口

    5.  调用Thread的start方法启动线程

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class CallableDemo {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            MyCallable myCallable = new MyCallable();
            FutureTask<String> futureTask = new FutureTask<>(myCallable);
            Thread thread = new Thread(futureTask,"有返回值的线程");
            //启动线程
            thread.start();
            //使用get方法获取返回值
            System.out.println(futureTask.get());
    
        }
    }
    
    class MyCallable implements Callable<String>{ //泛型的类型就是返回值的类型
        private int ticket = 10;
        private int users = 20;
        @Override
        public String call() throws Exception {
            for (int i = 0; i <users ; i++) {
                Thread thread = Thread.currentThread();
                System.out.println(thread.getName() + "卖出了第" + ticket-- + "张票");
            }
            return "ZhangJia";
        }
    
    }

    参考资料

    51CTO博客

    山东省
  • 0
  • 0
  • 0
  • 1.2k
  • pearPLUS

    请登录之后再进行评论

    登录
    单栏布局 侧栏位置: