Java多线程基础

多线程基础

多线程是什么

在了解多线程之前 我们先来了解一下进程的含义:

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。----来自百度百科

我们可以打开自己电脑的任务管理器,查看 “进程” 窗口的数据
在这里插入图片描述
这里每一个正在运行的程序都可以被理解为“进程”。

了解了进程是什么,现在就可以解释线程了。
线程可以理解为每个进程中独立的子任务。就像我们使用音乐软件听歌一样,系统在执行播放任务的同时,还有下载音频文件线程、搜索曲目线程等等,这些线程在我们看来,就是在同步进行的。
但是又有一个新的问题出现了,使用多线程有什么好处呢?
在我们学习工作中,使用操作系统时,可以一边播放音乐,一边编辑文档,同时还可以在后台下载资料,正因为处理器在这些线程之间快速地切换,使得我们感觉他们就是在同步执行的,这就是多线程为我们带来的便利。
更直观的表达就是,待运行的任务有3个,执行时间分别是5秒,20秒,1秒。单任务的特点是同步执行,在单任务执行环境下执行时,回先执行任务一,5秒后执行任务二,再过20秒再执行任务三;而多任务运行时,CPU就会在三个任务间高速切换,使任务3不必等待25才能执行,系统效率大大提升,与单任务对应,使用多线程也就是使用了异步。

使用多线程

Java中实现多线程编程的方法共有两种:

  1. 继承Thread类(实现了Runnable接口)
  2. 实现Runnable接口
继承Thread类

Thread类有一个 run() 方法。使用继承Thread类的方式使用多线程编程,就 需要重写 这个方法,这个方法的内容就是该线程要完成的 任务

public class MyThread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                int time = (int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("run=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面的类就继承了Thread类,现在通过调用MyThread对象的 start() 方法就可以 启动线程

public class Test {
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.setName("myThread");
//            b01_thread.run();
            thread.start();
            for (int i = 0; i < 10; i++) {
                int time = (int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("main=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
在这里插入图片描述
这里需要注意一下,我们启动线程是调用了Thread对象的 start() ,而不是直接调用我们刚刚实现的 run() 方法,这是为什么呢?
原来Thread类中的start()方法会通知“线程管理器”此线程已经准备就绪,可以调用run()方法开始运行了,这个过程就相当于对系统下达了一个通知,让系统安排时间来执行run()方法内的代码,也就启动了线程,具有 异步执行 的效果。
而调用run()方法的结果就截然不同,他不会通知“线程管理器”,处理器也不会专门另行安排时间空间执行该方法,而是由main()方法调用run()方法执行代码,是一个 同步执行 过程。

另外我们还需要注意一件事:
线程对象执行start()方法的顺序并非线程执行的顺序,线程真正的执行顺序是随机的:

public class MyThread extends Thread {
    private int i;

    public MyThread(int i) {
        super();
        this.i = i;
    }

    @Override
    public void run() {
        System.out.println(i);
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread t1 = new MyThread(1);
        MyThread t2 = new MyThread(2);
        MyThread t3 = new MyThread(3);
        MyThread t4 = new MyThread(4);
        MyThread t5 = new MyThread(5);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

执行结果:
在这里插入图片描述

实现Runnanle接口

当我们的类已经使用了宝贵的唯一继承机会,我们就不得不使用这个方法来实现多线程了。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

我们的Runnable实现类MyRunnable已经写好了,但是如何执行呢?
我们需要看一下Thread类的构造方法:
在这里插入图片描述我们发现Thread的构造方法中,有2个可以传入一个Runnable接口,那就说明我们可以通过Thread构造方法将我们的MyRunnable类传递进去,然后通过新的Thread实例来启动线程。

public class Test {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable, "b02_runnable");
        thread.start();
        System.out.println("运行结束");
    }
}

在这里插入图片描述


线程安全

我们先来了解一下,什么叫做“非线程安全”这个术语。
非线程安全也叫作线不程安全,是指多个线程对同一个对象中同一个属性进行操作时,出现值不同步的情况,进一步影响程序的下一步进行的现象。

这里用两个事例来说明非线程安全问题:

  1. 共享变量时引起的异常
  2. 自增/减与System.out.println()方法引起的异常
共享变量时引起的异常

在试验中,我们不难发现,使用new Thread()得到的多个对象之间的属性是独有的,互不干涉,但如果我们需要多个线程都可以修改这个值时,我们就需要使用 new Thread(Runnable runnable)new Thread(Runnable runnable, String name) 两个构造方法来创建线程对象。

public class MyThread extends Thread{
    private int count = 5;
//    存在安全隐患
    @Override
    public void run() {
        super.run();
        count--;
        System.out.println(this.currentThread().getName() + "进行计算:" + count);
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        Thread t1 = new Thread(thread, "t1");
        Thread t2 = new Thread(thread, "t2");
        Thread t3 = new Thread(thread, "t3");
        Thread t4 = new Thread(thread, "t4");
        Thread t5 = new Thread(thread, "t5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

在这里插入图片描述

我们发现在修改count的时候,产生了异常情况,这是因为我们的改值的执行过程中有多个步骤,但是多个线程同时访问这个值,自然会出现非线程安全问题。

我们这时候要引入一个关键词 synchronized .通过这个关键字,我们就可以对被修饰的方法或对象加上一道“”,加上锁的这部分代码被称作“互斥区”或“临界区”。一个线程只有拿到这把锁才可以执行被修饰的代码,而其他没有拿到这个锁的线程就会不断尝试去争抢这把锁,直到抢到为止。因此我们把MyThread类改成下面这样,就可以将对count的操作进行同步操作。

public class MyThread extends Thread{
    private int count = 5;
//    存在安全隐患
    @Override
    synchronized public void run() {
        super.run();
        count--;
        System.out.println(this.currentThread().getName() + "进行计算:" + count);
    }
}

在这里插入图片描述

自增/减与System.out.println()方法引起的异常

这里讲述一下变量自增自减与System.out.println()方法共用时可能会引起的一种情况。

public class MyThread extends Thread {
    private int i = 1;
    @Override
    public void run() {
        System.out.println("i=" + (i++) + "\nthread -- " + Thread.currentThread().getName());
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        Thread t1 = new Thread(thread);
        Thread t2 = new Thread(thread);
        Thread t3 = new Thread(thread);
        t1.start();
        t2.start();
        t3.start();
    }
}

在博主启动了无数次程序之后,终于出现了罕见的非线程安全的问题。
在这里插入图片描述
这是因为自增操作是在System.out.println()方法执行前执行的。因此虽然System.out.println()是同步的,但是仍然会发生非线程安全问题。所以应该继续使用同步方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值