Java多线程

Posted brilliantZC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程相关的知识,希望对你有一定的参考价值。

一、程序,进程,线程的基本概念

程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。

进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。进程是资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

二、线程的创建与使用

1、线程的相关API

1、start():启动当前线程,调用当前线程run();
2、run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3、currentThread():静态方法,返回当前执行代码的线程
4getName():获得当前线程名
5setName():设置当前线程名
6yield():释放当前CPU执行权
7、join():在线程 A中调用线程B的join(),此时线程A就会进入阻塞状态,直到线程B完全执行完后,线程A才会结束阻塞状态
8、sleep(long millitime):让当前线程睡眠指定的millitime毫秒数,在指定的millitime毫秒时间内,当前线程是阻塞状态
9、isAlive():判断当前线程是否存活

2、创建多线程

方式一:继承于Thread类

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run()方法,并将此线程执行的操作声明在此run()中
  3. 创建Thread类的子类对象
  4. 通过此对象调用start() 方法
//1、创建一个继承于Thread类的子类
class MyThread extends Thread
    //、重写Thread类的润();
    @Override
    public void run() 
        for (int i = 0; i < 100; i++) 
            if(i%2==0)
                System.out.println(Thread.currentThread().getName()+":"+i);
            
        
    

public class ThreadTest 
    public static void main(String[] args) 
        //3、创建Thread类的子类对象
        MyThread t1=new MyThread();
        //4、通过此对象调用start();1、启动当前线程  2、调用当前线程的run();
        t1.start();
        //我们不能通过调用run方法方式启动线程
        //t1.run();
        //不可以用已经start的线程再次执行 再次调用t1.start();将发生错误
        MyThread t2=new MyThread();
        t2.start();
        for (int i = 0; i < 100; i++) 
            if(i%2==0)
                System.out.println(Thread.currentThread().getName()+":"+i);
            
        
    

方式二:实现Runnable接口类

  1. 创建一个实现了Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()
//1、创建一个实现了Runnable接口的类
class WindowSell implements Runnable
    //2、实现类去实现Runnable中的抽象方法:run()

    @Override
    public void run() 
        for (int i = 0; i < 100; i++) 
            if(i%2==0)
                System.out.println(Thread.currentThread().getName()+":"+i);
            
        
    

public class WindowTest1 
    public static void main(String[] args) 
        //3、创建实现类的对象
        WindowSell ws=new WindowSell();
        //4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1=new Thread(ws);
        //5、通过Thread类的对象调用start();
        t1.start();
    

方式三:实现Callable接口

  1. 创建一个实现Callable的实现类
  2. 实现call方法,将此线程需要执行的操作申明在call()中
  3. 创建Callable接口实现类对象
  4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start
  6. 获取Callable中的call方法返回值
//1、创建一个实现Callable的实现类
class NumThread implements Callable
    int sum=0;
    //2、实现call方法,将此线程需要执行的操作申明在call()中
    @Override
    public Object call() throws Exception 
        for (int i = 0; i < 100; i++) 
            if (i%2==0)
                System.out.println(i);
                sum+=i;
            
        
        return sum;
    

public class ThreadNew 
    public static void main(String[] args) 
        //3、创建Callable接口实现类对象
        NumThread numThread=new NumThread();
        //4、将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask=new FutureTask(numThread);
        //5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start
        Thread t=new Thread(futureTask);
        t.start();
        try 
            //6、获取Callable中的call方法返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object sum = futureTask.get();
            System.out.println("总和为:"+sum);
         catch (InterruptedException e) 
            e.printStackTrace();
         catch (ExecutionException e) 
            e.printStackTrace();
        

    

Callable接口方式创建的多线程比实现Runnable接口创建多线程方式强大
  1、call()可以有返回值
  2、call()可以抛出异常,被外面的操作捕获,获取异常信息
  3、Callable可以支持泛型

方式四:使用线程池

  1. 提供指定数量的线程池 ExecutorService service= Executors.newFixedThreadPool(10);
  2. 执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
  3. 关闭线程池
class NumberThread implements  Runnable
    @Override
    public void run() 
        for (int i = 0; i < 100; i++) 
            if (i%2==0)
                System.out.println(Thread.currentThread().getName()+":"+i);
            
        
    


public class ThreadNew2 
    public static void main(String[] args) 
        //1、提供指定数量的线程池
        ExecutorService service= Executors.newFixedThreadPool(10);

        ThreadPoolExecutor service1=(ThreadPoolExecutor)service;
        service1.setCorePoolSize(15);
        //2、执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合用于Runnable
        //service.submit(Callable callable);//适合用于Callable
        //3、关闭线程池
        service.shutdown();
    

使用线程池的好处:
  1、提高响应速度(减少了创建线程的时间)
  2、降低了资源消耗(重复利用线程池中的线程,不需要再次创建)
  3、便于线程管理
    corePoolSize:核心池大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多久的时间会终止

三、线程的生命周期

JDK中用Thread.State类定义了线程的几种状态

新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束


四、线程的同步

方式一:同步代码块

synchronize(同步检视器)
  //需要被同步的代码


操作共享数据的代码,即为需要同步的代码
共享数据:多个线程共同操作的变量
同步监视器:俗称锁,任何一个类的对象都可以充当锁,多个线程必须共用同一把锁
class Window implements Runnable
    private int ticket=100;
    //Object obj=new Object();
    @Override
    public void run() 
        while (true)
        
            synchronized (this)  //此时的this:唯一的Window的对象    //synchronized (obj)
                if (ticket>0)
                
                    try
                        Thread.sleep(100);
                    catch (InterruptedException e)
                        e.printStackTrace();
                    
                    System.out.println(Thread.currentThread().getName()+"卖票:票号为:"+ticket);
                    ticket--;
                
                else 
                    break;
                
            
        
    

public class WindowTicket 
    public static void main(String[] args) 

        Window w=new Window();
        Thread t1=new Thread(w);
        Thread t2=new Thread(w);
        Thread t3=new Thread(w);

        t1.setName("窗口1:");
        t2.setName("窗口2:");
        t3.setName("窗口3:");
        t1.start();
        t2.start();
        t3.start();
    

方式二:同步方法

  1. 同步方法仍然涉及到同步检测器,只是不需要我们显示的声明
  2. 非静态的同步方法,同步检测器是:this。静态的同步方法,同步监测是:当前类本身。
class Window2 implements Runnable
    private int ticket=100;
    Object obj=new Object();
    @Override
    public void run() 
       while (true)
           show();
       
    
    public synchronized void show()  //同步监视器:this
        if (ticket>0) 
            try 
                Thread.sleep(100);
             catch (InterruptedException e) 
                e.printStackTrace();
            
            System.out.println(Thread.currentThread().getName() + "卖票:票号为:" + ticket);
            ticket--;

        
    

public class Window1 
    public static void main(String[] args) 
        Window2 w2=new Window2();
        Thread t1=new Thread(w2);
        Thread t2=new Thread(w2);
        Thread t3=new Thread(w2);

        t1.setName("窗口1:");
        t2.setName("窗口2:");
        t3.setName("窗口3:");

        t1.start();
        t2.start();
        t3.start();
    

方式三:lock锁

  1. 实例化 private ReentrantLock look=new ReentrantLock();
  2. 调用锁定方法look()
  3. 调用解锁方法:unlock()
class LockTest implements Runnable
    private int ticket=100;

    //1、实例化
    private ReentrantLock look=new ReentrantLock();
    @Override
    public void run() 
        while (true)
            try 
                //2、调用锁定方法look()
                look.lock();
                if (ticket>0)
                    try 
                        Thread.sleep(100);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    System.out.println(Thread.currentThread().getName()+"售票:票号为"+ticket);
                    ticket--;
                else 
                    break;
                
            finally 

                //3、调用解锁方法:unlock();
                look.unlock();
            

        

    

public class ThreadLockTest 
    public static void main(String[] args) 

        LockTest lt=new LockTest();
        Thread t1=new Thread(lt);
        Thread t2=new Thread(lt);
        Thread t3=new Thread(lt);

        t1.start();
        t2.start();
        t3.start();
    

synchronized 和Lock的异同点

  • 相同:二者都可以解决线程安全问题
  • 不同点:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
    Lock需要手动的启动同步(lock),同时结束同步也需要手动的实现(unlock)

五、线程通信

wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify()一旦执行此方法,就会唤醒锁wait的一个线程,如果有多个线程被wait,就唤醒优先级高的线程
notifyAll()一旦执行此方法,就会唤醒所有的wait的线程

说明:
1、wait()、notify()、notifyAll()三个方法必须使用在同步代码块或者同步方法中
2、wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的监视器,否则会出现异常
3、wait()、notify()、notifyAll()三个方法都定义在Java.Lang.Object类中

sleep()和wait()异同
同:一旦执行方法,都可以使当前的线程进入阻塞状态
不同:

  • 两个方法申明的位置不同:Thread类中声明sleep(),Object类中声明wait()
  • 调用范围不同:sleep()可以再任何需要的场景下调用,wait()必须在同步代码块或者同步方法中调用
  • 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放,wait()会释放
class Clerk
    private int productCount=0;
    //生产产品
    public synchronized  void produceProduct()
        if(productCount<20)
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
            notify();
        else
            //等待
            try 
                wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    
    //消费产品
    public synchronized void consumeProduct() 
        if (productCount>0)
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
            productCount--;
            notify();
        else
            //等待
            try 
                wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    


class Producer implements Runnable
    private Clerk clerk;
    public Producer(Clerk clerk)
        this.clerk=clerk;
    
    @Override
    public void run() 
        System.out.println(Thread.currentThread().getName()+":开始生产产品.......");
        while (true)
            try 
                Thread.sleep(100);
             catch (InterruptedException e) 
                e.printStackTrace();
            
            clerk.produceProduc

以上是关于Java多线程的主要内容,如果未能解决你的问题,请参考以下文章

深入Java多线程锁策略

java多线程ArrayBlockingQueue阻塞队列

关于Java多线程-interrupt()interrupted()isInterrupted()解释

如何编写测试类来测试多线程?

java面试题,15个java多线程面试题及答案

15个java多线程面试题及回答