百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

一文搞懂Java多线程 java多线程实战指南

yuyutoo 2024-10-12 01:04 5 浏览 0 评论

Java多线程的运行机制

Java多线程的创建

Java多线程的创建有三种方式:

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

2.通过Runnable接口创建线程

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

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

run()
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;
    }
}

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

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

相关推荐

Mysql和Oracle实现序列自增(oracle创建序列的sql)

Mysql和Oracle实现序列自增/*ORACLE设置自增序列oracle本身不支持如mysql的AUTO_INCREMENT自增方式,我们可以用序列加触发器的形式实现,假如有一个表T_WORKM...

关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)

概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...

MySQL CREATE TABLE 简单设计模板交流

推荐用MySQL8.0(2018/4/19发布,开发者说同比5.7快2倍)或同类型以上版本....

mysql学习9:创建数据库(mysql5.5创建数据库)

前言:我也是在学习过程中,不对的地方请谅解showdatabases;#查看数据库表createdatabasename...

MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别

执行"CREATETABLE新表ASSELECT*FROM原表;"后,新表与原表的字段一致,但主键、索引不会复制到新表,会把原表的表记录复制到新表。...

Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出

在街上看到的PandaDunk的超载可能让一些球鞋迷们望而却步,但Dunk的浪潮仍然强劲,看不到尽头。我们看到的很多版本都是为女性和儿童制作的,这种新配色为后者引入了一种令人耳目一新的新选择,而...

美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍

多功能雷达AN/SPY-1的特性和技术能力,该雷达已经在美国海军服役了30多年,其修改-AN/SPY-1A、AN/SPY-1B(V)、AN/SPY-1D、AN/SPY-1D(V),以及雷神...

汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)

全面分析汽车音响使用或安装技术常识一:主机是大多数人最熟习的音响器材,有关主机的各种性能及规格,也是耳熟能详的事,以下是一些在使用或安装时,比较需要注意的事项:LOUDNESS:几年前的主机,此按...

【推荐】ProAc Response系列扬声器逐个看

有考牌(公认好声音)扬声器之称ProAcTablette小音箱,相信不少音响发烧友都曾经,或者现在依然持有,正当大家逐渐掌握Tablette的摆位设定与器材配搭之后,下一步就会考虑升级至表现更全...

#本站首晒# 漂洋过海来看你 — BLACK&amp;DECKER 百得 BDH2000L无绳吸尘器 开箱

作者:初吻给了烟sco混迹张大妈时日不短了,手没少剁。家里有了汪星人,吸尘器使用频率相当高,偶尔零星打扫用卧式的实在麻烦(汪星人:你这分明是找借口,我掉毛是满屋子都有,铲屎君都是用卧式满屋子吸的,你...

专题|一个品牌一件产品(英国篇)之Quested(罗杰之声)

Quested(罗杰之声)代表产品:Q212FS品牌介绍Quested(罗杰之声)是录音监听领域的传奇品牌,由英国录音师RogerQuested于1985年创立。在成立Quested之前,Roger...

常用半导体中英对照表(建议收藏)(半导体英文术语)

作为一个源自国外的技术,半导体产业涉及许多英文术语。加之从业者很多都有海外经历或习惯于用英文表达相关技术和工艺节点,这就导致许多英文术语翻译成中文后,仍有不少人照应不上或不知如何翻译。为此,我们整理了...

Fyne Audio F502SP 2.5音路低音反射式落地音箱评测

FyneAudio的F500系列,有新成员了!不过,新成员不是新的款式,却是根据原有款式提出特别版。特别版产品在原有型号后标注了SP字样,意思是SpecialProduction。Fyne一共推出...

有哪些免费的内存数据库(In-Memory Database)

以下是一些常见的免费的内存数据库:1.Redis:Redis是一个开源的内存数据库,它支持多种数据结构,如字符串、哈希表、列表、集合和有序集合。Redis提供了快速的读写操作,并且支持持久化数据到磁...

RazorSQL Mac版(SQL数据库查询工具)

RazorSQLMac特别版是一款看似简单实则功能非常出色的SQL数据库查询、编辑、浏览和管理工具。RazorSQLformac特别版可以帮你管理多个数据库,支持主流的30多种数据库,包括Ca...

取消回复欢迎 发表评论: