Java多线程入门

一 进程和多线程简介

1.1 进程和线程

  • 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
  • 线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

二 几个重要的概念

2.1 同步和异步

同步和异步通常用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。
异步比较经典的实现方式是:消息队列

2.2 临界区

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。在并行程序中,临界区资源是保护的对象。

2.3 高并发

高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。
高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

三 使用多线程常见的三种方式

3.1 继承Thread类

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        System.out.println("Mythread");
    }
}

Test

public class Test {
    public static void main(String[] args) {
        MyThread mythread = new MyThread();
        mythread.start();
        System.out.println("运行结束");
    }
}

运行结果

运行结束
Mythread

从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。

3.2 实现Runnable接口

推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。

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

Test.java

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

运行结果

运行结束
MyRunnable

3.3 使用线程池

使用线程池的方式也是最推荐的一种方式,以后讲解。

四 实例变量和线程安全

线程类中的实例变量针对其他线程可以有共享和不共享之分。下面通过两个简单的例子来说明!

4.1 不共享数据的情况

public class DonotShareData extends Thread {
    private int count = 5;
    public DonotShareData(String name) {
        super();
        this.setName(name);
    }
    @Override
    public void run() {
        super.run();
        while (count > 0) {
            count--;
            System.out.println("由 " + DonotShareData.currentThread().getName() + " 计算,count=" + count);
        }
    }

    public static void main(String[] args) {
        DonotShareData a = new DonotShareData("A");
        DonotShareData b = new DonotShareData("B");
        DonotShareData c = new DonotShareData("C");
        a.start();
        b.start();
        c.start();
    }
}

运行结果

由 C 计算,count=4
由 A 计算,count=4
由 A 计算,count=3
由 A 计算,count=2
由 A 计算,count=1
由 B 计算,count=4
由 B 计算,count=3
由 B 计算,count=2
由 B 计算,count=1
由 B 计算,count=0
由 A 计算,count=0
由 C 计算,count=3
由 C 计算,count=2
由 C 计算,count=1
由 C 计算,count=0

可以看出每个线程都有一个属于自己的实例变量count,它们之间互不影响。我们再来看看另外一种情况。

4.2 共享数据的情况

public class SharedVariableThread extends Thread{
    private int count = 5;
    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由 " + SharedVariableThread.currentThread().getName() + " 计算,count=" + count);
    }
    public static void main(String[] args) {
        SharedVariableThread mythread = new SharedVariableThread();
        // 下列线程都是通过mythread对象创建的
        Thread a = new Thread(mythread, "A");
        Thread b = new Thread(mythread, "B");
        Thread c = new Thread(mythread, "C");
        Thread d = new Thread(mythread, "D");
        Thread e = new Thread(mythread, "E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

运行结果

由 C 计算,count=3
由 B 计算,count=3
由 A 计算,count=2
由 D 计算,count=1
由 E 计算,count=0

可以看出这里已经出现了错误,我们想要的是依次递减的结果。为什么呢??
因为在大多数jvm中,count–的操作分为如下三步:

  1. 取得原有的count值
  2. 计算count - 1
  3. 对count进行赋值
    所以多个线程同时访问时出现问题就是难以避免的了。
    那么有没有什么解决办法呢?
    答案是:当然有,而且很简单。给大家提供两种解决办法:一种是利用 synchronized 关键字(保证任意时刻只能有一个线程执行该方法),一种是利用 AtomicInteger 类(JUC 中的 Atomic 原子类)。大家如果之前没有接触 Java 多线程的话,可能对这两个概念不太熟悉,不过不要担心我后面会一一向你介绍到!这里不能用 volatile 关键字,因为 volatile 关键字不能保证复合操作的原子性。

五 一些常用方法

5.1 currentThread()

返回对当前正在执行的线程对象的引用。

5.2 getID()

返回此线程的标识符

5.3 getName()

返回此线程的名称

5.4 getPriority()

返回此线程的优先级

5.5 isAlive()

测试这个线程是否还处于活动状态。
什么是活动状态呢?
活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。

5.6 sleep(long millis)

使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

5.7 interrupt()

中断这个线程。

5.8 interrupt() 和isInterrupted()

interrupt():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能
isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但不清除状态标志

5.9 setName(String name)

将此线程的名称更改为等于参数 name 。

5.10 isDaemon()

测试这个线程是否是守护线程。

5.11 setDaemon(boolean on)

将此线程标记为 daemon线程或用户线程。

5.12 join()

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是 主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了
join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

5.13 yield()

yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。

5.14 setPriority(int newPriority)

更改此线程的优先级

六 如何停止一个线程呢?

stop(),suspend(),resume()(仅用于与suspend()一起使用)这些方法已被弃用,所以我这里不予讲解。

6.1 使用interrupt()方法

我们上面提到了interrupt()方法,先来试一下interrupt()方法能不能停止线程。

public class InterruptThread extends Thread {
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 5000000; i++) {
            System.out.println("i=" + (i + 1));
        }
    }
    public static void main(String[] args) {
        try {
            InterruptThread thread = new InterruptThread();
            thread.start();
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
    }
}

运行上诉代码你会发现,线程并不会终止
针对上面代码的一个改进:
interrupted()方法判断线程是否停止,如果是停止状态则break。

public class InterruptThread2 extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 500000; i++) {
            if (this.interrupted()) {
                System.out.println("已经是停止状态了!我要退出了!");
                break;
            }
            System.out.println("i=" + (i + 1));
        }
        System.out.println("看到这句话说明线程并未终止------");
    }
    public static void main(String[] args) {
        try {
            InterruptThread2 thread = new InterruptThread2();
            thread.start();
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
    }
}

运行结果

i=442112
i=442113
i=442114
i=442115
i=442116
i=442117
i=442118
i=442119
i=442120
已经是停止状态了!我要退出了!
看到这句话说明线程并未终止------

for循环虽然停止执行了,但是for循环下面的语句还是会执行,说明线程并未被停止。

6.2 使用return停止线程

public class ReturnThread extends Thread {
    @Override
    public void run() {
        super.run();
        while (true) {
            if (this.isInterrupted()) {
                System.out.println("停止了!");
                return;
            }
            System.out.println("timer=" + System.currentTimeMillis());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReturnThread t = new ReturnThread();
        t.start();
        Thread.sleep(2000);
        t.interrupt();
    }
}

运行结果

timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
timer=1578901541538
停止了!

当然还有其他停止线程的方法,后面再做介绍。

七 线程的优先级

每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低
优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。

线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。

线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。

Thread类中包含的成员变量代表了线程的某些优先级。如Thread.MIN_PRIORITY(常数1),Thread.NORM_PRIORITY(常数5),
Thread.MAX_PRIORITY(常数10)。其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1) 到Thread.MAX_PRIORITY(常数10) 之间,在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)。

线程优先级具有继承特性测试代码:

public class PriorityThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("PriorityThread1 run priority=" + this.getPriority());
        PriorityThread2 thread2 = new PriorityThread2();
        thread2.start();
    }
}
public class PriorityThread2  extends Thread{
    @Override
    public void run() {
        System.out.println("PriorityThread2 run priority=" + this.getPriority());
    }
}
public class PriorityThreadTest {
    public static void main(String[] args) {
        System.out.println("main thread begin priority="
                + Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(6);
        System.out.println("main thread end   priority="
                + Thread.currentThread().getPriority());
        PriorityThread1 thread1 = new PriorityThread1();
        thread1.start();
    }
}

运行结果

main thread begin priority=5
main thread end   priority=6
PriorityThread1 run priority=6
PriorityThread2 run priority=6

八 Java多线程分类

8.1 多线程分类

用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

守护线程:运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 “佣人”。
1. 特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作
2. 应用:数据库连接池中的检测线程,JVM虚拟机启动后的检测线程
3. 最常见的守护线程:垃圾回收线程

8.2 如何设置守护线程?

可以通过调用Thead类的setDaemon(true)方法设置当前的线程为守护线程

注意事项:

1.  setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
2. 在守护线程中产生的新线程也是守护线程
3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
public class DaemonThread extends Thread{
    private int i = 0;
    @Override
    public void run() {
        try {
            while (true) {
                i++;
                System.out.println("i=" + (i));
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
public class DaemonThreadTest {
    public static void main(String[] args) {
        try {
            DaemonThread thread = new DaemonThread();
            thread.setDaemon(true);
            thread.start();
            Thread.sleep(5000);
            System.out.println("我离开thread对象也不再打印了,也就是停止了!");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果:

i=42
i=43
i=44
i=45
i=46
i=47
i=48
i=49
i=50
我离开thread对象也不再打印了,也就是停止了!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值