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 接口

import java.util.concurrent.Callable;

public class ThreadCallable implements Callable {
    @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 {
    
    @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 future = new FutureTask(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 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/