Java 线程基础

雪月书韵茶香 昨夜西风凋碧树,独上高楼,望尽天涯路 本文由博客端 https://www.xysycx.cn 主动推送

线程基础内容

程序、进程与线程

进程与线程的关系

线程的创建与启动

创建线程的方式 1

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"---------"+i);
        }
    
    }
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();
        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+"=========="+i);
        }
    }
}

注意:Thread 类中的 run 方法是存储线程要运行的代码,主线程要运行的代码存放在 main 方法中

start 方法是开启线程并执行该线程的 run 方法

继承 Thread 类方式的缺点:

如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类

如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类
如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类

创建线程的方式 2(重点&&常用)

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"---------------"+i);
        }

    }

    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();
        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+"===================="+i);
        }

    }
}

使用 Runnable 接口实现多线程优点:

可以实现继承。实现 Runnale 接口的方式要通用一些

可以实现继承。实现 Runnale 接口的方式要通用一些
可以实现继承。实现 Runnale 接口的方式要通用一些

线程操作的相关方法

截图录屏_选择区域_20201119154243

线程的代理设计模式

代理模式主要使用了 Java 的多态,干活的是被代理类,代理类主要是接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?同根就成,大家知根知底,你能做啥,我能做啥都清楚的很,同一个接口呗。

Thread 与 Runnable 的子类都实现了 Runnable 接口,之后将 Runnable 的子类 MyThread 的子类 MyThread 放到 Thread 类之中,测试类调用是 Thread 类中的 start 方法去启动多线程,实际上具体的执行者是 Runnable 的子类 MyThread 中的 run 方法中的代码

线程代理模式简图

真实角色:MyThread

代理角色:Thread

实现共同接口:Runnable

线程的生命周期

线程状态

注意:在多线程时候,可以实现唤醒和等待的过程,但是唤醒和等待操作对应的类不是 thread,而是我们设置的共享对象或者共享变量(Object 类中的方法)

线程基本状态转换图

线程同步

线程同步是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系

线程同步定义

(此处参照计算机操作系统一书中进程同步的概念,我把进程替换成了线程)

线程同步的主要任务是对多个相关线程在执行次序上进行协调,是并发执行的诸进程之间能按照一定的规则(或时序)共享资源,并能很好的合作,从而使程序的执行具有可再现性。

线程同步是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系。

线程同步其实实现的就是线程排队,防止线程同步访问共享资源造成冲突,变量需要同步,但是常量不需要,因为常量存放于方法区。

只要这些线程的代码访问同一份可变的共享资源,这些进程之间就需要同步。

线程同步的必要性

如果没有线程同步操作,将会产生非常严重的后果。

举个生活中常见的例子:

小红和小绿是一对夫妻,小红每个月会给小绿的银行账户中存入 1000 元钱作为小绿的生活费,有一天小红误操作存了 2000 进去,此时小绿正在查看账户余额忽然发现比平时钱多了一倍,喜出望外非常感动立马准备取钱出来准备去吃顿自助餐。与此同时小红也发现自己的操作失误,准备取出多出来的 1000 元钱。注意:此处小红和小绿同时取钱操作,是纳秒级别的并发操作。而由于银行系统没有进行线程同步操作。此时会发生什么?

小绿成功的取出 2000 块钱,小红成功的取出多转的 1000 块钱。

明明卡里只有两千块钱,小红和小绿却取出了总金额 3000 元。这么干下去,银行早倒闭了。

而此时银行的程序员小六立马发现了这个漏洞,开始考虑解决方案,都说程序员个个都智商绝顶(没有冒犯的意思),很快想出了一个聪明的办法来解决这个八阿哥,既然是由于并发产生的问题,那么我让它不并发不就好了。

当超过一个人同时进行取款操作时候,对这个账户余额上一把锁,同一时间(瞬时)只能让一个人进行操作,其他人排队等待。当第一人操作完成之后释放锁,然后第二个人才能开始操作。

当小红和小绿发现这个财富密码之后,就又开始了薅资本主义羊毛的骚操作,这次还是同时取款,小红和小绿发现这次操作时候自动取款机的程序好像变慢了一点(显示器上显示》》》》操作中请等待》》》》),这次是小绿先取出钱,然后小红的操作界面显示余额不足。此次小红和小绿的薅羊毛行动失败了。

果然 排队 这个方法非常有效,再也没发生过这种事情,解决这个 bug 的代价只是让程序看起来慢了一丢丢而已,这对银行来说成本几乎可以忽略不计,于是银行的程序员小六很快就升职加薪并且找到了同在一个银行上班的小红做自己的女朋友。

线程同步的实现(初级)

此处仅仅是线程基础内容,不会引出太多内容,不然这一个点挖出来的东西我也写不完(我还没学会呢)

/**
 * @Description TODO   同步方法
 * @Author Fedeline
 * @Date 11/19/20 12:22 PM
 */
public class TicketRunnable3 implements Runnable {
    private int ticket = 10;
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sail();
        }
    }
    public static void main(String[] args) {
        TicketRunnable3 ticketRunnable = new TicketRunnable3();
        Thread t1 = new Thread(ticketRunnable,"A");
        Thread t2 = new Thread(ticketRunnable,"B");
        Thread t3 = new Thread(ticketRunnable,"C");
        Thread t4 = new Thread(ticketRunnable,"D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
    public synchronized void sail(){
        if (ticket>0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
        }
    }
}
/**
 * @Description TODO 同步代码块
 * @Author Fedeline
 * @Date 11/19/20 12:22 PM
 */
public class TicketRunnable2 implements Runnable {
    private int ticket = 10;
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this){
                if (ticket>0){
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");

                }
            }


        }

    }

    public static void main(String[] args) {
        TicketRunnable2 ticketRunnable = new TicketRunnable2();
        Thread t1 = new Thread(ticketRunnable,"A");
        Thread t2 = new Thread(ticketRunnable,"B");
        Thread t3 = new Thread(ticketRunnable,"C");
        Thread t4 = new Thread(ticketRunnable,"D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

线程同步小结

死锁

死锁的起因

死锁的起因,通常是源于多个线程对资源的整多,不仅对不可抢占资源进行争夺时会引起死锁,而且对可消耗资源的进行争夺时,也会引起死锁。

在一组线程发生死锁的情况下,这组死锁进程中的每一个进程 ,都在等待另一个死锁进程所占用有的资源。或者说每个线程所等待的事件是该组中其它线程释放所占有的资源。但是由于所有这些进程已都无法运行,因此它们谁也不能释放资源,致使没有任何一个进程可被唤醒。这样这组进程只能无限期等待下去。

死锁定义

同样参照计算机操作系统一书中的定义

如果每一组线程中的每个线程都在等待仅由该组线程中的其他线程才能引发的事件,那么该组进程是死锁的(DeadLock)

产生死锁的必要条件

以下四个比必要条件必须同时具备才会形成死锁

四个必要条件只要有一个被破坏就可以预防死锁

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    2830 引用 • 8051 回帖 • 741 关注

赞助商 我要投放

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...