• Java 多线程,Java 四种方式创建线程,实现Runnable 接口和继承 Thread类的区别


    Java 多线程,Java创建线程4种方式,Java 四种方式创建线程

    实现Runnable 接口和继承 Thread类的区别

    ================================

    ©Copyright 蕃薯耀 2021-04-16

    https://www.cnblogs.com/fanshuyao/

    一、Java创建线程的四种方式

    1、继承 Thread类

    public class Threadable extends Thread{
        @Override
        public void run() {
            System.out.println("Threadable 当前线程是:" + Thread.currentThread().getName());
        }
    }

    2、实现 Runnable 接口

    public class ThreadRun implements Runnable{
        @Override
        public void run() {
            System.out.println("ThreadRun 当前线程是:" + Thread.currentThread().getName());
        }
    }

    实现Runnable 接口和继承 Thread类的区别:

    • synchronized(this)中使用this时,this是当前对象,继承 Thread类的方式创建多个线程时,this是不一样的,会出问题。实现Runnable 接口因为只有一个,所以没问题。
    • synchronized(obj):可以在线程类创建一个obj对象,表示线程共用对象,这样给加锁的对象是同一个。需要注意的是:实现Runnable接口创建的对象不用加static,继承 Thread类创建的对象必须加static,不然对象就不是同一个。
    • synchronized(XXX.class):使用类,类只有一个,能解决synchronized(obj)的问题(继承 Thread类创建的对象必须加static)。在Java中,任何东西都被看作对象,所以类也是可以的。

    所以一般情况,建设使用实现Runnable 接口的方式,避免继承Thread存在的问题。

    3、实现 Callable<V> 接口

    import java.util.concurrent.Callable;
    
    public class ThreadCallable implements Callable<Object> {
        @Override
        public Object call() throws Exception {
            return null;
        }
    }

    和实现 Runnable 接口的区别是:

    • Callable可以往外抛异常
    • Callable有返回值

    4、使用线程池创建

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ThreadTest5Executors {
    
        public static void main(String[] args) {
            
            ThreadRunData threadRun = new ThreadRunData();
            
            //创建线程池,池里有10个线程
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            
            executorService.execute(threadRun);//execute是没返回的
            executorService.execute(threadRun);//execute是没返回的
            executorService.submit(threadRun);//submit是有返回的
            executorService.submit(threadRun);//submit是有返回的
            
            //executorService.submit(task);//submit是有返回的,如果使用Callable且有返回值,则需要使用submit
            
            //关闭线程池
            executorService.shutdown();
        }
        
    }

    二、Java 多线程 Synchronized 关键字使用

    synchronized(object对象)

    • synchronized(this):this表示调用的当前对象
    • synchronized(obj):可以在线程类创建一个obj对象,表示线程共用对象,这样给加锁的对象是同一个。需要注意的是:实现Runnable接口创建的对象不用加static,继承 Thread类创建的对象必须加static,不然对象就不是同一个。
    • synchronized(XXX.class):使用类,类只有一个,能解决synchronized(obj)的问题(继承 Thread类创建的对象必须加static)。在Java中,任何东西都被看作对象,所以类也是可以的。

    1、通过实现Runnable接口创建线程类

    public class ThreadRunData implements Runnable{
    
        private int ticket = 30;
    
        @Override
        public void run() {
            
            while(true) {
                synchronized(this) {
                    if(ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        
                        System.out.println("ThreadRunData " + Thread.currentThread().getName() + ":卖出第" + ticket + "票");
                        ticket--;
                        
                        
                    }else {
                        System.out.println("ThreadRunData " + Thread.currentThread().getName() + ":票已售完");
                        break;
                    }
                }
            }
            
        }
    
    }

    2、多线程共同卖票,共同的数据是ticket

    public class ThreadTest2 {
    
        public static void main(String[] args) {
            
            ThreadRunData threadRun = new ThreadRunData();
            
            Thread thread = new Thread(threadRun);
            thread.setName("线程一");
            thread.start();
            
            Thread thread2 = new Thread(threadRun);
            thread2.setName("线程二");
            thread2.start();
            
            Thread thread3 = new Thread(threadRun);
            thread3.setName("线程三");
            thread3.start();
            
            Thread thread4 = new Thread(threadRun);
            thread4.setName("线程四");
            thread4.start();
        }
        
    }

    结果:

    未加synchronized关键字前:

    存在问题:

    • 票重复卖
    • 票超量卖
    ThreadRunData 当前线程是:线程二,卖出第30票
    ThreadRunData 当前线程是:线程三,卖出第30票
    ThreadRunData 当前线程是:线程四,卖出第30票
    ThreadRunData 当前线程是:线程一,卖出第30票
    ThreadRunData 当前线程是:线程四,卖出第26票
    ThreadRunData 当前线程是:线程一,卖出第26票
    ThreadRunData 当前线程是:线程二,卖出第26票
    ThreadRunData 当前线程是:线程三,卖出第26票
    ThreadRunData 当前线程是:线程二,卖出第22票
    ThreadRunData 当前线程是:线程三,卖出第22票
    ThreadRunData 当前线程是:线程一,卖出第22票
    ThreadRunData 当前线程是:线程四,卖出第22票
    ThreadRunData 当前线程是:线程四,卖出第18票
    ThreadRunData 当前线程是:线程二,卖出第18票
    ThreadRunData 当前线程是:线程一,卖出第18票
    ThreadRunData 当前线程是:线程三,卖出第18票
    ThreadRunData 当前线程是:线程三,卖出第14票
    ThreadRunData 当前线程是:线程二,卖出第14票
    ThreadRunData 当前线程是:线程一,卖出第14票
    ThreadRunData 当前线程是:线程四,卖出第14票
    ThreadRunData 当前线程是:线程二,卖出第10票
    ThreadRunData 当前线程是:线程三,卖出第10票
    ThreadRunData 当前线程是:线程四,卖出第10票
    ThreadRunData 当前线程是:线程一,卖出第10票
    ThreadRunData 当前线程是:线程四,卖出第6票
    ThreadRunData 当前线程是:线程二,卖出第6票
    ThreadRunData 当前线程是:线程三,卖出第6票
    ThreadRunData 当前线程是:线程一,卖出第6票
    ThreadRunData 当前线程是:线程三,卖出第2票
    ThreadRunData 当前线程是:线程一,卖出第2票
    ThreadRunData 当前线程是:线程一,票已售完
    ThreadRunData 当前线程是:线程二,卖出第2票
    ThreadRunData 当前线程是:线程二,票已售完
    ThreadRunData 当前线程是:线程四,卖出第2票
    ThreadRunData 当前线程是:线程四,票已售完
    ThreadRunData 当前线程是:线程三,卖出第-2票
    ThreadRunData 当前线程是:线程三,票已售完

    加了synchronized关键字之后:

    解决了上面的问题。

    ThreadRunData 当前线程是:线程一,卖出第30票
    ThreadRunData 当前线程是:线程一,卖出第29票
    ThreadRunData 当前线程是:线程一,卖出第28票
    ThreadRunData 当前线程是:线程一,卖出第27票
    ThreadRunData 当前线程是:线程一,卖出第26票
    ThreadRunData 当前线程是:线程一,卖出第25票
    ThreadRunData 当前线程是:线程一,卖出第24票
    ThreadRunData 当前线程是:线程一,卖出第23票
    ThreadRunData 当前线程是:线程一,卖出第22票
    ThreadRunData 当前线程是:线程一,卖出第21票
    ThreadRunData 当前线程是:线程一,卖出第20票
    ThreadRunData 当前线程是:线程一,卖出第19票
    ThreadRunData 当前线程是:线程一,卖出第18票
    ThreadRunData 当前线程是:线程一,卖出第17票
    ThreadRunData 当前线程是:线程一,卖出第16票
    ThreadRunData 当前线程是:线程一,卖出第15票
    ThreadRunData 当前线程是:线程一,卖出第14票
    ThreadRunData 当前线程是:线程一,卖出第13票
    ThreadRunData 当前线程是:线程一,卖出第12票
    ThreadRunData 当前线程是:线程四,卖出第11票
    ThreadRunData 当前线程是:线程四,卖出第10票
    ThreadRunData 当前线程是:线程四,卖出第9票
    ThreadRunData 当前线程是:线程三,卖出第8票
    ThreadRunData 当前线程是:线程三,卖出第7票
    ThreadRunData 当前线程是:线程三,卖出第6票
    ThreadRunData 当前线程是:线程三,卖出第5票
    ThreadRunData 当前线程是:线程三,卖出第4票
    ThreadRunData 当前线程是:线程三,卖出第3票
    ThreadRunData 当前线程是:线程二,卖出第2票
    ThreadRunData 当前线程是:线程二,卖出第1票
    ThreadRunData 当前线程是:线程二,票已售完
    ThreadRunData 当前线程是:线程三,票已售完
    ThreadRunData 当前线程是:线程四,票已售完
    ThreadRunData 当前线程是:线程一,票已售完

    三、Java 多线程 ReentrantLock 使用

    ReentrantLock 锁和synchronized的区别:

    • ReentrantLock要手动加锁和释放锁;synchronized关键字可以在代码块或者方法中,运行完后自动释放锁,避免忘记释放锁,形成死锁
    • ReentrantLock可以尝试获取锁,在多次获取后,可以终止并释放锁;synchronized会一直等待。

    一般情况,建议还是使用synchronized吧。

    1、线程中使用ReentrantLock 加锁

    import java.util.concurrent.locks.ReentrantLock;
    
    public class ThreadRunDataLock implements Runnable{
    
        private int ticket = 30;
        
        private ReentrantLock reentrantLock = new ReentrantLock();
    
        @Override
        public void run() {
            
            while(true) {
                
                try {
                    //加锁
                    reentrantLock.lock();
                    
                    if(ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        
                        System.out.println("ThreadRunData " + Thread.currentThread().getName() + ":卖出第" + ticket + "票");
                        ticket--;
                        
                        
                    }else {
                        System.out.println("ThreadRunData " + Thread.currentThread().getName() + ":票已售完");
                        break;
                    }
                    
                }finally {
                    //释放锁
                    reentrantLock.unlock();
                }
                
            }
            
        }
    
    }

    2、创建多个线程调用

    public class ThreadTest4Lock {
    
        public static void main(String[] args) {
            
            ThreadRunDataLock threadRun = new ThreadRunDataLock();
            
            Thread thread = new Thread(threadRun);
            thread.setName("线程一");
            thread.start();
            
            Thread thread2 = new Thread(threadRun);
            thread2.setName("线程二");
            thread2.start();
            
            Thread thread3 = new Thread(threadRun);
            thread3.setName("线程三");
            thread3.start();
            
            Thread thread4 = new Thread(threadRun);
            thread4.setName("线程四");
            thread4.start();
            
        }
        
    }

    四、两个线程交替运行

    1、线程类:交替运行

    /**
     * 线程一、线程二交替卖票
     * @author liqiongy
     *
     */
    public class ThreadRunDataAlternate implements Runnable{
    
        private int ticket = 30;
    
        @Override
        public void run() {
            
            while(true) {
                synchronized(this) {
                    //唤醒一个其它在等待的线程。存在多个等待的线程只唤醒一个,因为只有2个线程,所以唤醒的一定是另一个
                    this.notify();
                    
                    if(ticket > 0) {
                        try {
                            //线程睡眠,不释放锁和资源
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        
                        System.out.println("ThreadRunData " + Thread.currentThread().getName() + ":卖出第" + ticket + "票");
                        ticket--;
                        
                        try {
                            //线程等待,释放锁和资源
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        
                    }else {
                        System.out.println("ThreadRunData " + Thread.currentThread().getName() + ":票已售完");
                        break;
                    }
                }
            }
            
        }
    
    }

    2、创建两个线程测试:

    public class ThreadTest3Alternate {
    
        public static void main(String[] args) {
            
            ThreadRunDataAlternate threadRun = new ThreadRunDataAlternate();
            
            Thread thread = new Thread(threadRun);
            thread.setName("线程1");
            thread.start();
            
            Thread thread2 = new Thread(threadRun);
            thread2.setName("线程二");
            thread2.start();
            
        }
        
    }

    3、结果:

    ThreadRunData 线程1:卖出第30票
    ThreadRunData 线程二:卖出第29票
    ThreadRunData 线程1:卖出第28票
    ThreadRunData 线程二:卖出第27票
    ThreadRunData 线程1:卖出第26票
    ThreadRunData 线程二:卖出第25票
    ThreadRunData 线程1:卖出第24票
    ThreadRunData 线程二:卖出第23票
    ThreadRunData 线程1:卖出第22票
    ThreadRunData 线程二:卖出第21票
    ThreadRunData 线程1:卖出第20票
    ThreadRunData 线程二:卖出第19票
    ThreadRunData 线程1:卖出第18票
    ThreadRunData 线程二:卖出第17票
    ThreadRunData 线程1:卖出第16票
    ThreadRunData 线程二:卖出第15票
    ThreadRunData 线程1:卖出第14票
    ThreadRunData 线程二:卖出第13票
    ThreadRunData 线程1:卖出第12票
    ThreadRunData 线程二:卖出第11票
    ThreadRunData 线程1:卖出第10票
    ThreadRunData 线程二:卖出第9票
    ThreadRunData 线程1:卖出第8票
    ThreadRunData 线程二:卖出第7票
    ThreadRunData 线程1:卖出第6票
    ThreadRunData 线程二:卖出第5票
    ThreadRunData 线程1:卖出第4票
    ThreadRunData 线程二:卖出第3票
    ThreadRunData 线程1:卖出第2票
    ThreadRunData 线程二:卖出第1票
    ThreadRunData 线程1:票已售完
    ThreadRunData 线程二:票已售完

    五、Callable 实现线程

    1、实现Callable接口定义线程类

    import java.util.concurrent.Callable;
    
    public class ThreadCallable implements Callable<Integer> {
        
        @Override
        public Integer call() throws Exception {
            
            int sum= 0;
            //计算从1加到100
            for(int i=1; i<=100; i++) {
                sum += i;
            }
            return sum;
        }
    }

    方式一:普通线程实现

    import java.util.concurrent.FutureTask;
    
    /**
     * Callable 实现线程
     *
     */
    public class ThreadTest7Callable {
    
        public static void main(String[] args) throws Exception{
            
            //实现Callable的线程类
            ThreadCallable callableThread = new ThreadCallable();
            
            //封装回返对象
            FutureTask<Integer> future = new FutureTask<Integer>(callableThread);
            
            //启动线程
            new Thread(future).start();
            
            System.out.println("1加到100的和是:" + future.get());
            
        }
        
    }

    方式二:线程池实现

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    /**
     * Callable 实现线程
     *
     */
    public class ThreadTest6Callable {
    
        public static void main(String[] args) throws Exception{
            
            ThreadCallable thread = new ThreadCallable();
            
            //创建线程池,池里有10个线程
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            
            Future<Integer> future = executorService.submit(thread);//submit是有返回的,如果使用Callable且有返回值,则需要使用submit
            
            System.out.println("1加到100的和是:" + future.get());
            
            //关闭线程池
            executorService.shutdown();
        }
        
    }

    (如果文章对您有所帮助,欢迎捐赠,^_^)

    ================================

    ©Copyright 蕃薯耀 2021-04-16

    https://www.cnblogs.com/fanshuyao/

    今天越懒,明天要做的事越多。
  • 相关阅读:
    如何使用Flash来实现本地存储.续
    一个简单自动监控nginx 504错误的php脚本
    Spark,一种快速数据分析替代方案
    使用浏览器原生函数优化动画
    QWrap简介之核心库定制
    计算tcp每秒并发数一则
    Sina的CMS模型
    Web 2.0 桌面与移动应用程序安全性设计
    FLEXmadel模态窗口透明度设置
    使用 ASM 实现 Java 语言的“多重继承”
  • 原文地址:https://www.cnblogs.com/fanshuyao/p/14666208.html
Copyright © 2020-2023  润新知