Java语言——多线程

一. 多线程

笔直向前,说到做到。

1.1 Thread类

构造方法:

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

常用方法:

  • public String getName() :获取当前线程名称。
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run() :此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
package com.itheima.demo;
class Mythread extends Thread{
    private  int num=6;
    public Mythread(String name)
    {
        super(name);
    }

    @Override
    public void run(){
        while (true) {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
            }
            if (num == 0) {
                break;
            }
        }
    }
}
public class HelloWorld {
    public static void main(String[] args) {
        Mythread me = new Mythread("雷金鹏");
        me.start();
        for (int i=0;i<6;i++) {
            System.out.println(i+"个馍了");
        }
        
    }
}

结果:
0个馍了
1个馍了
2个馍了
3个馍了
4个馍了
5个馍了
雷金鹏吃了第6个馍
雷金鹏吃了第5个馍
雷金鹏吃了第4个馍
雷金鹏吃了第3个馍
雷金鹏吃了第2个馍
雷金鹏吃了第1个馍

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行

1.2 Runnable接口

采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。 步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
  3. 调用线程对象的start()方法来启动线程。
package com.itheima.demo;
class Mythread implements Runnable{
    private int num=6;
    @Override
    public void run(){
        while (true) {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
            }
            if (num == 0) {
                break;
            }
        }
    }
}
public class HelloWorld {
    public static void main(String[] args) {
        Mythread me = new Mythread();
        Thread my1 = new Thread(me,"马新航");
        my1.start();
    }
}

结果:
马新航吃了第6个馍
马新航吃了第5个馍
马新航吃了第4个馍
马新航吃了第3个馍
马新航吃了第2个馍
马新航吃了第1个馍

1.3 Thread和Runnable的区别

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进 程。

1.4 匿名内部类方式实现线程的创建

public class HelloWorld {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            private int num=6;
            @Override
            public void run() {
                while (true) {
                    if (num > 0) {
                        System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
                    }
                    if (num == 0) {
                        break;
                    }
                }
            }
        },"范程").start();
    }
}

结果:
范程吃了第6个馍
范程吃了第5个馍
范程吃了第4个馍
范程吃了第3个馍
范程吃了第2个馍
范程吃了第1个馍

二. 线程安全

2.1线程安全

package com.itheima.demo;
class Mythread implements Runnable{
    private int num=6;
    @Override
    public void run(){
        while (true) {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
            }
            if (num==0){
                break;
            }
        }
    }
}
public class HelloWorld {
    public static void main(String[] args) {
        //创建线程任务对象
        Mythread ticket = new Mythread();
        //创建三个对象
        Thread t1 = new Thread(ticket, "吕布");
        Thread t2 = new Thread(ticket, "高宠");
        Thread t3 = new Thread(ticket, "李存孝");
        //同时吃馍
        t1.start();
        t2.start();
        t3.start();
    }
}

结果:
吕布吃了第6个馍
李存孝吃了第5个馍
高宠吃了第4个馍
李存孝吃了第2个馍
吕布吃了第3个馍
高宠吃了第1个馍

这儿当次数少时是这样的,但当num值大的时候,就可能会出现
李存孝吃了第31个馍 吕布吃了第31个馍吕布吃了第-1个馍
的结果

在本代码中是因为当线程1执行到if时,此时CPU又随机选择到线程2,这样此时线程1处于堵塞状态,当线程2执行完后再进行刚才线程1的记录,这样数据就会重复执行。

这种问题,几个窗口(线程)数值不同步了,这种问题称为线程不安全。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。

2.2 线程同步

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复与不存在问题,Java中提供了同步机制 (synchronized)来解决。

有三种方式完成同步操作:

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。
package com.itheima.demo;
class Mythread implements Runnable{
    private int num=100;
    Object s = new Object();
    @Override
    public void run(){
        while (true) {
            synchronized (s) {
                if (num > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto‐generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
                }
                if (num == 0) {
                    break;
                }
            }
        }
    }
}
public class HelloWorld {
    public static void main(String[] args) {
        //创建线程任务对象
        Mythread ticket = new Mythread();
        //创建三个对象
        Thread t1 = new Thread(ticket, "吕布");
        Thread t2 = new Thread(ticket, "高宠");
        Thread t3 = new Thread(ticket, "李存孝");
        //同时吃馍
        t1.start();
        t2.start();
        t3.start();
    }
}

2.3 Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象

  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁。
package com.itheima.demo;

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

class Mythread implements Runnable{
    private int num=100;
    Lock s = new ReentrantLock();
    @Override
    public void run(){
        while (true) {
            s.lock();
            if (num > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "吃了第" + num-- + "个馍");
            }
            if (num == 0) {
                break;
            }
            s.unlock();
        }
    }
}

三. 线程状态

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限 等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时 等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait。
Teminated(被 终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

在这里插入图片描述

发布了57 篇原创文章 · 获赞 96 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44840572/article/details/103544626
今日推荐