Java多线程学习

线程离不开进程。如果进程消失了线程肯定消失,反之如果线程消失了进程不一定消失。


  • 目录


多线程

如果想要在Java中实现多线程有两种途径
- 继承Thread类;
- 实现Runnable接口(Callable接口);

继承Thread类

Thread类是一个支持多线程的功能类,只要有一个子类他就可以实现多线程的支持。

class MyThread extends Thread { //这就是一个多线程的操作类

}

我们都知道,所有程序的起点是main()方法,但是所有的线程也有一个自己的起点,那么这个起点就是run()方法,也就是说在多线程的每个主体类中我们必须覆写Thread()类中的run()方法;

public void run(){
}

这个方法没有返回值,也就是说明线程一旦开始就要一直执行下去不能够返回内容。
所有的线程与进程都是一样的,都必须轮流去抢占资源,所以多线程的执行都应该是多个线程彼此交替执行,也就是说直接调用run方法并不能够启动线程,多线程的启动的唯一方法就是Thread类中的start()方法;

public void start();
//此方法调用的方法体是run方法定义的

使用start()方法后,线程之间才可以交替执行。
使用Thread类的start()方法不仅仅要启动多线程的执行代码,还要去根据不同的操作系统进行资源分配。

实现Runnable接口

虽然Thread类可以实现多线程主体类定义,但是它有一个问题,java具有单继承局限,所以我们应该尽量找的使用类得继承来实现多线程,为了解决单继承的闲置,在java中提供了Runnable接口,这个接口定义如下:

@FunctionalInterface
public interface Runnable{
    public void run();
}

所以只需要让一个类实现Runnable接口即可,并且也需要覆写run()方法;

  • 与继承Thread类相比,多线程的类在接口上与之前是没有区别的,但是有一点严重区别,如果此时继承了Thread,那么可以直接继承start()方法,但是如果实现的Runnable接口,并没有start()方法可以被继承。
  • 不管何种情况下,想要启动多线程,都需要依靠Thread类来完成,在Thread里面定义有
    构造方法(Thread(Runnable target));接收到额是Runnable接口对象
class MyThread implements Runnable{
    private String name ;
    public MyThread (String Thread){
        this.name=name;
    }
    @Override
    public void run(){//覆写run方法
        for(int i=0;i<1000;i++){
            System.out.println(this.name+" --> "+i);
        }
    }
}
public class Text{
    public static void main (String args[]){//主类
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();
        MyThread my3 = new MyThread();
        new Thread(my1).start();
        new Thread(my2).start();
        new Thread(my3).start();
    }
}

这样就避免了单继承的局限性,在实际工作中使用Runnable是最合适的

多线程两种方法的区别?

首先需要说明,Runnable接口和Thread类相比,解决了单继承的限制。
Thread实现过程

public class Thread extends Object implements Runnable
//来自java开发文档

这里我们可以看出来Thread实现了Runnable接口,而当我们使用Runnable接口来实现多线程的时候需要使用Thread中的方法
这里写图片描述

  • 此时看起来像是代理设计模式,但是如果是代理设计模式,客户端调用的代理类的方法也应该是接口里提供的方法,那么也应该是run()方法才对。(设计者最早想到了代理的结构,但是他没有做到代理的样子,所以才提供了两种多线程的实现方式)

  • 出来以上的联系之外还有:使用Runnable接口可以比Thread类更好的描述出数据共享这一概念。此时的数据共享指的是多个线程访问统一资源的操作

总结:
1. Thread类是Runnable接口的子类,使用Runnable可以避免单继承的局限;
2. Runnable实现的多线程可以比Thread类实现的多线程更加清晰得 描述数据共享的概念。

callable接口

使用Runnable接口实现的多线程可以避免单继承局限,但是Runnable接口中的run()没有返回值;

callable在java.util.concurrent.Callable内

@FunctionalInterface
public interface Callable<V>{
    public V call()  throws Exception;
}
call()方法执行完线程的主题功能之后就会返回一个结果,而且返回结果的类型由Callable接口上的泛型来决定。

范例:定义泛型主题类

class My implements Callable<String>{
    private int tacket = 10;

    public String call() throws Exception {
        for(int i=0;i<100;i--){
            if(tacket>=0){
                System.out.println("买票:ticket= "+this.tacket--);
            }
        }
        return "票买完了";
    }
}

java.util.concurrent Class FutureTask<V>//这个类主要是负责callable接口对象操作的,这个接口的定义接口为

public class FutureTask<V> extends Object implements RunnableFuture<V>

发现FutureTask实现了RunnableFuture接口,那么可以在查看RunnableFuture可以发现:

public interface RunnableFuture<V> extends Ruuture<V>

RunnableFuture又实现了Runnable接口!!!!

在FutureTask类里面定义有如下的构造方法
FutureTask(Callable<V> callable) 

FutureTask接受的目的只有一个,那么就是去的call()方法的返回结果。


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class My implements Callable<String>{
    private int tacket = 10;

    public String call() throws Exception {
        for(int i=0;i<100;i--){
            if(tacket>=0){
                System.out.println("买票:ticket= "+this.tacket--);
            }
        }
        return "票买完了";
    }
}
public class Main {
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        My my1 = new My();
        My my2 = new My();
        FutureTask<String> task1 = new FutureTask<String>(my1);
        FutureTask<String> task2 = new FutureTask<String>(my2);
        //FutureTask是Runnable接口子类,所以可以使用Thread类的构造来接受task对象
        new Thread(task1).start();
        new Thread(task2).start();
        //多线程结束后我们可以取得内容,根绝FutureTask的父接口Future中的get()方法完成
        System.out.println("A线程的返回结果为:"+task1.get());
        System.out.println("B线程的返回结果为:"+task2.get());
    }
}

第三种方法最麻烦在问题在于需要接受返回值信息,并且又要于原始的多线程的实现靠拢(想Thread靠拢)

总结

  1. 对月多线程的实现,终点在于Runnable接口与Thread类启动的配合上;
  2. 对于JDK1.5新特性,了解就可以了,知道他们之间的区别在于返回结果;

扩展

线程的命名与取得

因为每一个线程运行都是不同的结果,需要去抢占属于自己的资源,如果想要区分每一个线程,那么我们就需要为每一个线程进行命名。建议在线程启动之前就命名。
如果先要对线程进行命名,需要是用到Thread类;
- 构造方法:Thread(Runnable target, String name)
- 设置名字:public final void setName(String name)
- 取得名字: public final String getName()
在为线程命名的时候由于这些方法是在Thread类里面的,所以我们在使用Runnable方式实现多线程的时候,想要获取线程名字,那么我们可以取得当前执行方法的线程名字。所以在Thread里面有一个这样的方法,
- 取得当前线程对象:static Thread currentThread()


import java.util.concurrent.ExecutionException;

class MyThread implements Runnable{
    private int tacket = 10;
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class Main {
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        MyThread mt1 = new MyThread();
        new Thread(mt1).start();
        new Thread(mt1).start();
        new Thread(mt1).start();
    }
}

运行结果为:

Thread-0
Thread-1
Thread-2

通过以上程序我们可以知道,在实例化Thread类对象的时候没有为其命名,那么会自动进行编号命名。

  • 设置名字的时候只需要在构造方法的参数后面加上线程的名字即可。
new Thread(mt1,"线程1").start();
  • 同时我们观察一下程序会发现
System.out.println(Thread.currentThread().getName());//不要放在线程里面
//输出为main

我们可以知道,其实main是一个主线程,而在main主方法上面创建的线程为main的子线程。
- 通过以上程序我们可以发现,线程一直存在(主方法就是主线程),可是进程在哪儿呢?

每当的使用java命令去解释一个程序类的时候,对于操作系统而言都相当与启动了一个新的进程,而main只是新新进程上的子线程。
- 每一个jvm在启动的时候会启动几个线程呢?
1. main线程,程序的主要执行以及启动子线程
2. gc线程,垃圾收集。

线程的休眠

线程休眠即:让线程速度变慢一点,休眠方法为:

public static void sleep(long millis) throws InterruptedException

实例:


import java.util.concurrent.ExecutionException;

class MyThread implements Runnable{
    public void run() {
        for(int i=0;i<100;i++){
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"x= "+i);
        }

    }
}
public class Main {
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        MyThread mt1 = new MyThread();
        new Thread(mt1,"线程1").start();
    }
}

默认情况下在休眠的时候设置了多个线程对象,那么所有的线程对象将一起进入到run()方法,所以会出现资源抢占问题。

线程优先级

优先级高的线程有可能先执行

在Thread里面有两个方法来设置优先级

设置优先级:public final void setPriority(int newPriority)

取得优先级:public final int getPriority()

设置和取得的返回值是int类型,对这个内容有三种取值:

  • 最大优先级:public static final int MAX_PRIORITY(10)
  • 默认优先级:public static final int NORM_PRIORITY(5)
  • 最大优先级:public static final int MIN_PRIORITY(1)

实例程序:

import java.util.concurrent.ExecutionException;

class MyThread implements Runnable{
    public void run() {
        for(int i=0;i<100;i++){
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"x= "+i);
        }

    }
}
public class Main {
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        MyThread mt1 = new MyThread();
        Thread th1 =new Thread(mt1,"线程1");
        Thread th2 =new Thread(mt1,"线程2");
        Thread th3 =new Thread(mt1,"线程3");

        th1.setPriority(Thread.MAX_PRIORITY);

        th1.start();
        th2.start();
        th3.start();
    }
}

通过这个代码我们可以大致了解到,线程优先级高的并不一定先执行。

  • 总结

    1. Thread.currentThread可以取得当前线程对象;
    2. Thread.sleep();主要是休眠,感觉是一起休眠,但实际上室友先后顺序的;
    3. 优先级越高的咸亨对象有可能限制性

线程同步问题

同步问题即:多个线程访问同一个资源是所需要考虑的问题。

这里写图片描述
在java中要实现同步需要使用synchronized关键字。这个关键字有两种使用方法:

  • 同步代码块
synchronized(锁定对象){
    //需要同步执行的代码;
}
  • 同步方法
public synchronized 返回值 函数名(){
    //需要同步的代码;
}

同步操作和异步操作相比较:

  • 同步代码执行速度较慢,但是数据安全性高,是相对安全的线程操作;
  • 异步操作执行速度较快,但是数据有可能会因为竞争同一个资源而出现差错。

死锁

有时候为了解决数据安全,启用了线程同步,但是启用的线程同步越多,遇到的死锁可能性就越大,所以为了避免这种逻辑错误,需要进行调试和优化。

生产者和消费者问题

生成这和消费者指的是两个不同的线程对象,操作统一资源的情况,具体操作流程如下。

  • 生产者负责生产数据
  • 生产者没生产完一组数据之后,消费者就要取走一组数据。

唤醒与等待

  • 等待public final void wait() throws InterruptedException
  • 唤醒public final void notify()
  • 唤醒正在等待的全部线程:public final void notifyAll()

生产者和消费者问题代码实例:
生产者生产一个,只有当消费者消费完之后才可以在生产一个。

class Info {
    private String name;
    private String age;
    private boolean flag = true;

    public synchronized void set(String name, String age) {

        if (this.flag == false) {
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.age = age;

        this.flag = false;
        super.notify();
    }

    public  synchronized void get(){
        if(this.flag == true){
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.name + " -- " + this.age);

        this.flag=true;
        super.notify();
    }
}
class Productor implements Runnable{
    private Info info ;
    public Productor(Info info){
        this.info=info;
    }

    public void run() {
        for(int i=0;i<100;i++){
            if(i%2==0) {
                this.info.set("ssss", "23333");
            }else{
                this.info.set("xx","18");
            }
        }
    }
}
class Consumer implements Runnable{
    private Info info ;
    public Consumer(Info info){
        this.info=info;
    }
    public void run() {
        for(int i=0;i<100;i++){
            this.info.get();
        }
    }
}
public class Main {
    public static void main(String args[]) throws  Exception{
        Info info =new Info();
        Productor pr = new Productor(info);
        Consumer co = new Consumer(info);

        new Thread(pr).start();
        new Thread(co).start();

    }
}
原文地址:https://www.cnblogs.com/lanaiwanqi/p/10445665.html