java多线程

文章详细介绍了Java中实现多线程的三种方式:继承Thread类、实现Runnable接口以及利用Callable和Future接口。同时,讨论了线程安全问题,包括同步代码块、同步方法和使用Lock,还提到了线程通信和线程池的概念。
摘要由CSDN通过智能技术生成

目录

一.概述

多线程的三种实现方式

1.继承Thread类

 2.实现Runnable接口

3.利用Callable接口和Future接口

三者对比

Thread常见成员方法

线程的生命周期

 线程安全

1.同步代码块

2.同步方法

3.同步锁Lock

线程通信

1.传统的线程通信

2.阻塞队列(BlockingQueue)

线程池


一.概述

进程:正在运行的程序,具有一定的独立功能。

线程:一个进程中,可能包含多个顺序执行流,每个顺序执行流就是一个线程。

        简单理解:软件中互相独立,可以同时运行的功能。

并发:单个处理器,同一时刻只能有一条指令执行,但多个指令被快速替换执行,到达"同时执行"的效果。

并行:多个处理器,同时执行多条指令

多线程的三种实现方式

1.继承Thread类

        Thread代表线程,所有线程对象必须是Thread类或者子类的实例 。

//1.定义一个Thread子类
class ThreadDemo extends Thread{
    //2.重写run方法,run方法被称为线程执行体
    @Override
    public void run(){
        for(int i=0;i<100;i++){
            System.out.println("线程"+getName());
        }
    }
}
public class Main {
    public static void main(String[] args) {
        //3.创建Thread子类实例,即创建线程对象
        ThreadDemo thread1=new ThreadDemo();
        ThreadDemo thread2=new ThreadDemo();
        thread1.setName("1");
        thread2.setName("2");
        //3.调用线程对象的start()方法,来启动该线程
        thread1.start();
        thread2.start();
    }
}

步骤: 

  1. 定义一个Thread子类
  2. 重写run方法,run方法被称为线程执行体
  3. 创建Thread子类实例,即创建线程对象
  4. 调用线程对象的start()方法,来启动该线程

#使用继承Thread类的方法创建线程,多个线程之间无法共享线程类的实例变量 

 2.实现Runnable接口

//1.定义Runnable接口的实现类
public class RunnableDemo implements Runnable {
    //2.重写run方法
    private int i=0;//多线程共享该实例变量
    @Override
    public void run(){
        for(;i<100;i++){
            Thread thread=Thread.currentThread();//获取当前线程对象
            System.out.println("线程"+thread.getName()+" i="+i);
        }
    }
    public static void main(String[] args){
        //3.创建Runnable实现类的实例
        RunnableDemo rd=new RunnableDemo();
        //4.以Runnable实现类的实例作为Thread的target来创建Thread的对象(真正的线程对象)
        Thread thread1=new Thread(rd,"1");
        thread1.start();
        Thread thread2=new Thread(rd,"2");
        thread2.start();
    }
}

步骤

  1. 定义Runnable接口的实现类,重写run方法
  2. 创建Runnable实现类的实例。
  3. 以该实例作为Thread的target来创建Thread的对象(真正的线程对象)

#多个线程共享一个target,因此多个线程共享一个target类的实例变量 

#通过Thread.currentThread();方法来获取当前线程对象

3.利用Callable接口和Future接口

        Callable接口提供了一个call()方法作为线程执行体,它可以有返回值,可以声明抛出异常。

但是它不是Runnable的子接口,不能直接作为target,况且call()方法作为线程执行体,如何获取返回值?

Future接口代表call()方法的返回值,并且Future接口的实现类FutureTask同时实现了Runnable接口。

 Future接口中的公共方法控制与其关联的Callable任务

  1. boolean cancel(bealean mayInterruptIfRunning);//试图取消关联任务
  2. V get();//返回任务中call的返回值,调用该方法会导致主线程阻塞,直到call方法结束。
  3. V get(long timeout,TimeUnit unit);//返回任务的返回值。程序最多阻塞timeout和unit指定的时间,时间过去仍然没有返回值,抛出TimeoutException异常
  4. boolean isCancelled();//任务是否在正常完成前被取消
  5. bool isDone();//如果任务完成,返回true
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
//1.创建Callable接口的实现类,并实现call方法
class MyCallable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception{
        int sum=0;
        for(int i=1;i<=100;i++){
            sum+=i;
        }
        return sum;
    }
}
public class callable {
    public static void main(String[] args) throws Exception{
        //2.创建Callable的实现类的实例,并且使用FutureTask来包装该实例
        MyCallable mc=new MyCallable();
        FutureTask<Integer> ft=new FutureTask<>(mc);
        //将FutureTask对象作为target,创建并启动线程
        Thread thread=new Thread(ft);
        thread.start();
        //获取线程的返回值
        System.out.println(ft.get());
    }
}

#Callable接口有泛型限制,泛型形参必须和call()方法返回值一致

#Callable接口是函数式接口,可以使用Lambda表达式

#call方法允许抛出异常

三者对比

  1. 继承Thread编写简单,通过this可以直接获取当前线程。但单继承扩展性差
  2. 后俩者编程稍复杂,但线程类可以继承其他的类,同时共用一个target 

Thread常见成员方法

守护线程:当其他非守护线程结束,守护线程会陆续结束。

出让线程:暂停线程,让出cpu。

插入线程:将指定线程插入到当前线程之前。

线程的生命周期

 阻塞:

  1. sleep()方法
  2. 阻塞式IO方法
  3. 线程试图获得一个同步监视器,但该同步监视器被其他线程占用
  4. 正在等待某个通知(notify)
  5. 程序调用了该线程的suspend()方法,将其挂起。容易死锁

 

死亡:

  1. run()或call()方法执行完毕
  2. 抛出一个未捕获的异常
  3. 直接调用该线程的stop();方法;容易死锁 

 线程安全

        当使用多个线程来访问同一个数据时,容易出现线程安全问题。

 使用同步监视器解决

1.同步代码块

//1.定义Runnable接口的实现类
public class RunnableDemo implements Runnable {
    //2.重写run方法
    private int i=0;//多线程共享该实例变量
    static Object obj=new Object();
    @Override
    public void run(){
        for(;i<100;i++){
            synchronized (obj){
                Thread thread=Thread.currentThread();//获取当前线程对象
                System.out.println("线程"+thread.getName()+" i="+i);
            }
        }
    }
    public static void main(String[] args){
        //3.创建Runnable实现类的实例
        RunnableDemo rd=new RunnableDemo();
        //4.以Runnable实现类的实例作为Thread的target来创建Thread的对象(真正的线程对象)
        Thread thread1=new Thread(rd,"1");
        thread1.start();
        Thread thread2=new Thread(rd,"2");
        thread2.start();
    }
}

线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

synchronized()内就是同步监视器

通常推荐使用可能被并发访问的共享资源充当同步监视器

#细节:1.synchronized加在循环外和循环内是不一样的   2.同步监视器应当唯一

2.同步方法

        使用synchronized关键字修饰方法。锁住方法内所有的代码,同步监视器不能自己指定。

synchronized修饰的实例方法,无需显示指定同步监视器,同步监视器就是this。

如果是static方法,就是当前类的字节码文件对象。

//1.定义Runnable接口的实现类
public class RunnableDemo implements Runnable {
    //2.重写run方法
    private int i=0;//多线程共享该实例变量
    static Object obj=new Object();
    @Override
    public void run(){
        try {
            for(;i<100;i++){
                synchronized (obj){
                    Thread thread=Thread.currentThread();//获取当前线程对象
                    System.out.println("线程"+thread.getName()+" i="+i);
                    thread.sleep(10);
                }
            }
            method();
        }catch (Exception e){
            
        }
    }
    synchronized void method() throws Exception{
        for(;i<200;i++){
            Thread thread=Thread.currentThread();//获取当前线程对象
            System.out.println("线程"+thread.getName()+" i="+i);
            thread.sleep(10);
        }
    }
    public static void main(String[] args){
        //3.创建Runnable实现类的实例
        RunnableDemo rd=new RunnableDemo();
        //4.以Runnable实现类的实例作为Thread的target来创建Thread的对象(真正的线程对象)
        Thread thread1=new Thread(rd,"1");
        thread1.start();
        Thread thread2=new Thread(rd,"2");
        thread2.start();
    }
}

3.同步锁Lock

         Lock为接口,ReentrantLock(可重入锁)为其常用的实现类。

ReentrantLock对象可以显示加锁和释放锁。

#为防止死锁,可使用finally释放锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//1.定义一个Thread子类
class ThreadDemo extends Thread{
    //2.重写run方法,run方法被称为线程执行体
    static Lock lk=new ReentrantLock();
    static int i=0;
    @Override
    public void run() {
        for (; i < 100; i++) {
            try {
                lk.lock();
                System.out.println("线程" + getName() + " " + i);
            } catch (Exception e) {

            } finally{
            lk.unlock();
        }
    }
    }
}
public class Main {
    public static void main(String[] args) {
        //3.创建Thread子类实例,即创建线程对象
        ThreadDemo thread1=new ThreadDemo();
        ThreadDemo thread2=new ThreadDemo();
        thread1.setName("1");
        thread2.setName("2");
        //3.调用线程对象的start()方法,来启动该线程
        thread1.start();
        thread2.start();
    }
}

死锁 :多个线程互相等待对方释放同步监视器,就会发生死锁。尤其是在线程中出现多个同步监视器的情况下。

线程通信

1.传统的线程通信

        借助Object提供的wait(),notify(),notifyAll()三个方法实现。这三个方法必须由同步监视器对象来调用

  1. 同步方法的监视器就是this,所以可以在同步方法中直接调用
  2. 同步代码块,通过括号里面的监视器调用
public class touch {
    public static void main(String[] args) {
        Thread tc=new Cook();
        Thread tf=new foodie();
        tc.start();
        tf.start();
    }
}
class Desk {
    public static int Foodflag=0;
    public static int count=10;
    //同步监视器
    public static Object lock=new Object();
}
class foodie extends Thread{
    public void run(){
        while(true){
            synchronized (Desk.lock){
                if(Desk.count==0){
                    break;
                }else{
                    if(Desk.Foodflag==0){
                        try {
                            //没有食物,就等待
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        Desk.count--;
                        System.out.println("吃掉这个食物,还能再吃"+Desk.count);
                        //吃掉后,唤醒厨师
                        Desk.lock.notifyAll();
                        //
                        Desk.Foodflag=0;
                    }
                }
            }
        }
    }
}
class Cook extends Thread{
    public void run(){
        while(true){
            synchronized (Desk.lock){
                if(Desk.count==0){
                    break;
                }else{
                    if(Desk.Foodflag==1){
                        try {
                            //有食物,就等待
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        System.out.println("厨师制作食物");
                        Desk.Foodflag=1;
                        //做好后,通知食客
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

2.阻塞队列(BlockingQueue)

       接口,其实现类有ArrayBlockingQueue和LinkedBlockingQueue

        两个线程必须使用同一个阻塞队列

public class BlockingQueueDemo {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(1);
        cook c=new cook(queue);
        Foodie f=new Foodie(queue);
        c.start();
        f.start();
    }
}
class Foodie extends Thread{
    ArrayBlockingQueue<String> queue;
    public Foodie(ArrayBlockingQueue<String> que){
        this.queue=que;
    }
    @Override
    public void run(){
        while (true){
            //不断从阻塞队列中获取
            try{
                String food= queue.take();
                System.out.println(food);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
class cook extends Thread{
    ArrayBlockingQueue<String> queue;
    public cook(ArrayBlockingQueue<String> que){
        this.queue=que;
    }
    @Override
    public void run(){
        while (true){
            //不断从阻塞队列中获取
            try{
                queue.put("noodles");
                System.out.println("制作面条");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

插入元素:put   取出对首元素:take  两个方法都会阻塞线程

线程池

      Executors:线程池工具类,通过调用方法返回不同类型的线程池对象

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Threadpool {
    public static void main(String[] args) throws Exception{
        ExecutorService pool= Executors.newFixedThreadPool(3);
        Runnable target=()->{
            for(int i=0;i<100;i++){
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
        };
        pool.submit(target);
        pool.submit(target);
        pool.submit(target);
        pool.shutdown();
    }
}

自定义线程池

 

插图来源于黑马程序员

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值