一文搞懂Java多线程

Java多线程的运行机制

Java多线程的创建

Java多线程的创建有三种方式:
1.通过继承Thread类创建进程
2.通过Runnable接口创建线程
3.通过Callable接口和Future接口创建线程

1.通过继承Thread类创建进程

  • 步骤1:创建子类,重写该类的run()方法,实现该线程的功能
  • 步骤2:创建Thread子类的实例,即创建进程对象
  • 步骤3:调用的线程对象的start()方法来启动该线程

Thread常用方法

方法 说明
void run() 线程运行时所执行的代码
void interript 中断进程
void start() 使该线程开始执行
static void yield() 暂停当前正在执行的进程 并执行其他进程
Thread.State getState() 返回该线程的状态
final boolean isAlive() 测试该线程是否处于活动的状态
String getName() 返回该线程的名称
void setName(String name) 改变线程的名称
public class www {
    
    
    public static void main(String[] args){
    
    
        for (int i = 0; i < 3; i++) {
    
    
            Test test = new Test(i);
            test.start();
        }
    }
}
class Test extends Thread{
    
    
    int name;
    public Test(int name){
    
    
        this.name = name;
    }
    public void run() {
    
    
        System.out.println("线程"+name);
    }
}

在这里插入图片描述

每次输出的结果可能不同,这是因为子线程执行的进度是不确定的,它们是并发运行的。

缺点 :若该类已经继承一个类,则无法继承Thread类。

2.通过Runnable接口创建线程

  • 步骤1:定义Runnable接口的实现类,并实现该接口的方法run()
  • 步骤2:定义Runnable实现类的实例,并以此为实例作为Thread类的target参数,来创建Thread线程对象,该Thread对象才是真的线程对象。
public class www {
    
    
    public static void main(String[] args){
    
    
        for (int i = 0; i < 3; i++) {
    
    
            Test test = new Test(i);
            Thread t = new Thread(test);
            t.start();
        }
    }
}
class Test implements Runnable{
    
    
    int name;
    public Test(int name){
    
    
        this.name = name;
    }
    public void run() {
    
    
        System.out.println("线程"+name);
    }
}

在这里插入图片描述

同样每次输出的结果可能不同,这也是因为子线程执行的进度是不确定的,它们是并发运行的。

3.通过Callable接口和Future接口创建线程

  • 步骤1:创建Callable接口的实现类,并实现call()方法,该方法将作为线程的执行体,并且有返回值。
  • 步骤2:创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
  • 步骤3:使用FutureTask对象作为Thread对象的target,创建并启动新线程。
  • 步骤4:调用FutureTask对象的get()方法来获得子线程执行结束的返回值。
public class www {
    
    
    public static void main(String[] args){
    
    
        for (int i = 0 ; i < 3 ; i++){
    
    
            Test test = new Test(i);
            //使用FutureTask来包装Callable对象
            FutureTask result = new FutureTask(test);
            //创建线程对象
            Thread thread = new Thread(result);
            //启动线程
            thread.start();
        }
    }
}
class Test implements Callable{
    
    
    int name;
    public Test(int name){
    
    
        this.name = name;
    }
    @Override
    public Object call() throws Exception {
    
    
        System.out.println("线程"+name);
        return null;
    }
}

在这里插入图片描述

同样每次输出的结果可能不同,这也是因为子线程执行的进度是不确定的,它们是并发运行的。

Java线程的生命周期

分为六种状态,具体为创建(New)状态、可运行(Runnable)状态、阻塞(Blocked)状态(不老科特)、等待状态(Waiting)状态、计时等待(Timed waiting)状态、终止(Terminated) 状态。

Java线程调度

线程睡眠——Sleep

可以让当前正在执行的线程暂停一段时间

public class www extends JFrame implements Runnable {
    
    
    JLabel jLabel1,jLabel2;
    public www (){
    
    
        jLabel1 = new JLabel("当前时间:");
        jLabel2 = new JLabel();
        Container containerPane = this.getContentPane();
        containerPane.setLayout(new FlowLayout());
        this.add(jLabel1);
        this.add(jLabel2);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(300,200);
        this.setVisible(true);
    }
    @Override
    public void run() {
    
    
        while (true){
    
    
            jLabel2.setText(getTime());
            //获取当前进程 并延时2000ms
            try {
    
    
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
    String getTime(){
    
    
        Date date =new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss Z");
        return simpleDateFormat.format(date);
    }
    public static void main(String[] args) {
    
    
        www test1 = new www();
        Thread thread1 = new Thread(test1);
        thread1.start();
    }
}

在这里插入图片描述

可以看出时间每隔2S(2000ms)刷新一次

线程让步——yield(youde)

yield()sleep()都会暂停执行当前的进程,不同的是yield()会先判读是否有比该进程优先级相同或高的进程,若是有则停止让相同或高优先级的先运行,若是没有则该进程继续运行。

public class www  implements Runnable {
    
    
    String str = "";
    @Override
    public void run() {
    
    
        for (int i = 1; i <= 9; i++) {
    
    
            str += Thread.currentThread().getName() + "----" + i + "     ";
            if(i%3 == 0){
    
    
                System.out.println(str);
                str = "";
                Thread.currentThread().yield();
            }
        }
    }
    public static void main(String[] args) {
    
    
        www test1 = new www();
        www test2 = new www();
        Thread thread1 = new Thread(test1,"线程1");
        Thread thread2 = new Thread(test2,"线程2");
        thread1.start();
        thread2.start();
    }
}

在这里插入图片描述

输出的结果线程是交替的,可能输出的结果不是交替的每次运行都不一样,所以通过yield()控制线程的执行方法是不可靠的。

线程协作——join

若一个线程运行到某一个点的时候,等待另一个线程运行结束后才能继续运行,这种情况可以通过调用另一个线程的join()方法来实现。

public class www {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new test();
        thread.start();
        for (int i = 1; i <= 10; i++) {
    
    
            System.out.println("主线程");
            if(i == 5){
    
    
                try {
    
    
                    thread.join();
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
class test  extends Thread{
    
    
    public void run(){
    
    
        for (int i = 1; i <= 10 ; i++) {
    
    
            System.out.println("子线程");
        }
    }
}

在这里插入图片描述

通常情况下,主线程创建并启动了线程,如果子线程运行过程中需要大量的时间,主线程往往早于子线程运行完之前,这个例子是在主线程运行完之前设置状态为join()等待子线程运行完再继续运行。

进程的优先级

Java中每个进程都对应的有优先级,优先级高的获得的运行机会多。线程优先级常用1~10之间的数字进行表示,数值越大优先级越高,线程默认等级为5。

Thread类中定义个三个常量

名称
MIN_PRIORITY 1
MAX_PRIORITY 10
NORM_PRIORITY 5

优先级的设定 setPrioriy() 方法 和 获取优先级的方法 getPrioriy()

守护进程

Java分为两类 用户线程守护线程

守护线程也称为后台线程 为其他线程提供服务 这类线程可以监控其他线程的运行 依赖与其他线程

用户线程是一般线程 负责业务逻辑

可通过setDaemon()方法来设置一个线程的类型 true为守护线程 只能在线程.start()之前调用 还可以通过isDaemon()方法来判断该进程是否是守护进程

线程同步

多线程引发的问题:在进行多线程的设计的时候,有时候需要多个线程共享一个部分代码块,从而实现共享一个私有成员变量或类的静态成员的目的。这时由于线程和线程之间相互争夺CPU资源,线程无序地进行访问这些共享资源,最终也可能无法得到正确的结果,这些问题通常称为线程安全问题。

例如:

public class www {
    
    
    public static void main(String[] args) {
    
    
        test test = new test();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
    
    
    public int num = 0;
    //使用temp是为了增加线程的切换几率
    private void add(){
    
    
        int temp;
        for (int i = 0; i < 1000; i++) {
    
    
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
    
    
        add();
    }
}

在这里插入图片描述

输出结果几乎没有正确1000倍数的,这是由于多线程的并发执行,多个线程同时对变量num进行修改的结果,解决这个问题就必须要有Java的同步机制。

同步代码块

Java为每个对象配备一把锁和一个等候集,这个对象可以是实例对象,也可以是类对象。对实例对象进行加锁,可以保证这个实例对象相关的线程可以互斥的使用对象锁;对类对象进行加锁可以保证这个类相关的线程可以被互斥的使用类对象的锁。通过new关键字创建实例对象,,从而获得对象的引用,要获得类对象的引用。我们可以通过forName成员方法。一个类的静态成员变量和静态成员方法隶属于类对象,而一个类的非静态成员变量和非静态成员方法属于类的实例对象。

synchronize(synObject){
    
    
    //关键code
}

当进程进入关键代码时系统会先检查对象的锁是否被其他线程获取,若没有则JVM把该对象的锁交给当前请求锁的进程,该线程获取锁后即可进入关键代码区域。

例如:

public class www {
    
    
    public static void main(String[] args) {
    
    
        test test = new test();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
    
    
    int num = 0;
    private void add(){
    
    
        int temp;
        for (int i = 0; i < 1000; i++) {
    
    
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
    
    
        synchronized (this){
    
    
            add();
        }
    }
}

在这里插入图片描述

这样下来每个线程执行完毕的结果都是整数了。

将add()方法使用synchronized修饰一样的结果,这就是下面要讲的同步方法

private synchronized void add(){
    
    
        int temp;
        for (int i = 0; i < 1000; i++) {
    
    
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }

同步方法

同步方法和同步代码块一样,都是利用互锁实现的代码同步访问。

public class www {
    
    
    public static void main(String[] args) {
    
    
        test test = new test();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
    
    
    int num = 0;
    private synchronized void add(){
    
    
        int temp;
        for (int i = 0; i < 1000; i++) {
    
    
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
    
    
            add();
    }
}

在这里插入图片描述

同步是一种高消耗的操作,因此尽量减少用synchronize设置大的同步方法,一般情况下使用synchronize代码块同步关键代码。

线程通讯

有时多线程会有执行过程中的次序问题,Java提供了三个方法来解决线程的通讯问题。分别是wait()notify()notifyAll()方法。这三个关键字只能在synchronized关键字作用范围内起作用,并且是在同一个方法中搭配这三个方法才有实际的意义。

wait()方法:可以使调用该方法的线程释放共享资源的锁,从可运行状态进入等待状态。知道再次被唤醒。

notify()方法:可以唤醒等待队列中第一个等待同一共享资源的线程,并使该进程退出等待状态,进入可运行状态。

notifyAll()方法:可以使所有正在等待队列中等待同一共享资源的进程从等待状态退出,进入可运行状态,若有多个进程,哪个优先级最高先运行哪个。在不知道该唤醒哪个进程的时候使用该方法。
知识点补充:
线程的5种状态详解

下面举个例子:

public class www {
    
    
    public static void main(String[] args) {
    
    
        //实例化一个ShareStore对象 并创建进程启动
        ShareStore shareStore = new ShareStore();
        new Consumer(shareStore).start();
        new Producer(shareStore).start();
    }
}

/***
 * 生产者
 */
class Producer extends Thread{
    
    
    private ShareStore shareStore;
    Producer(ShareStore shareStore){
    
    
        this.shareStore = shareStore;
    }
    @Override
    public void run() {
    
    
        int num = 1;
        while (true){
    
    
            shareStore.setShareNum(++num);
            System.out.println("Producer生产了一个数字"+num);
            //睡眠1s
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
}
/***
 * 消费者
 */
class Consumer extends Thread{
    
    
    private ShareStore shareStore;
    Consumer(ShareStore shareStore){
    
    
        this.shareStore = shareStore;
    }
    @Override
    public void run() {
    
    
        int num = 1;
        while (true){
    
    
            num = shareStore.getShareNum();
            System.out.println("Consumer消费了一个数字"+num);
            //睡眠1s
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
    }
}

/***
 * 用来管理的
 */
class ShareStore{
    
    
    private int num;
    private boolean writeable = true;
    public synchronized void setShareNum(int num){
    
    
        if (!writeable){
    
    
            try {
    
    
                wait();//等待消费者消费完成
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
        this.num = num;
        writeable = false;
        notify(); //通知消费者 生产者已经生产可以消费
    }
    public synchronized int getShareNum(){
    
    
        if (writeable){
    
    
            try {
    
    
                wait(); //等待生产者生产出来
            } catch (InterruptedException e) {
    
    
                throw new RuntimeException(e);
            }
        }
        writeable = true;
        notify();//通知可以生产
        return this.num;
    }
}

在这里插入图片描述

输出的结果都是生产者先生产一个数字,消费者再消费。

死锁

死锁是一种场景:当两个或多个进程形成单项等待的环,每个进程都在相互等待对方的资源释放,都无法执行一直持续下去就形成了死锁。

猜你喜欢

转载自blog.csdn.net/Systemmax20/article/details/125576353