阅读 2699

java 多线程

很多人可能已经很熟悉操作系统中的多任务:就是同一时刻运行多个程序的能力。

多线程程序在较低层次上扩展了多任务的概念:一个程序同时执行多个任务。通常每一个任务称为一个线程,它是线程控制的简称。可以同时运行一个以上线程的程序成为多线程程序。

什么是线程

线程

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

多线程

多线程指在单个程序中可以同时运行多个不同的线程执行不同的任务。

多线程编程的目的,就是“最大限度地利用cpu资源”,当某一线程的处理不需要占用cpu而只和io等资源打交道时,让需要占用Cpu的其他县城有其他机会获得cpu资源。从根本上说,这就是多线程编程的最终目的。

有一个程序实现多个代码同时交替运行就需要产生多个线程。CPU随机地抽出时间。让我们程序一会做这件事,一会做另外的事情。

Java内置支持多线程编程(Multithreaded Programming)。

多线程程序包含两条及以上的并发运行的部分,程序中每个这样的部分都叫做一个线程(Thread)。每个线程都有独立的执行路径,因此多线程是多任务处理的特殊形式。

多任务处理被所有的现代操作系统所支持。然而,多任务处理有两种截然不同的类型:基于进程的基于线程的

  1.基于进程的多任务处理是更熟悉的形式。进程(process)本质上是一个执行的程序。因此基于进程的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。

  比如,基于进程的多任务处理是你在编辑文本的时候可以同时运行Java编译器。

  2.而在基于线程(thread-based)的多任务处理环境中,线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多个任务的功能。

  比如,一个文本编辑器可以在打印的同时格式化文本。

线程和进程的区别

 多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响。

  线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换负担比进程切换的负担要小。多线程程序比多进程程序需要更少的管理费用。

  进程是重量级的任务,需要分配给它们独立的地址空间,进程间通信是昂贵和受限的,进程间的转换也是很需要花费的。而线程是轻量级的选手,它们共享相同的地址空间并且共同分享同一个进程,线程间的通信是便宜的,线程间的转换也是低成本的。

线程的实现

在Java中通过run方法为线程指明要完成的任务,有两种技术来为线程提供run方法:

  1. 继承Thread类并重写它的run方法。之后创建这个子类的对象并调用start()方法。
  2. 通过定义实现Runnable接口的类进而实现run方法。这个类的对象在创建Thread的时候作为参数被传入,然后调用start()方法。

两种方法需要执行线程start()方法为线程分配必须的系统资源,调度线程运行并纸呢个线程的run()方法。

start()方法是启动线程的唯一的方法。start()方法首先为线程的执行准备好系统资源,然后再去调用run()方法。一个线程只能启动一次,再次启动就不合法了。

run()方法中放入了线程的逻辑,即我们要这个线程去做事情。

通常我一般是使用第二种方法来实现的,当一个线程已经继承了另一个类时,只能用第二种方法来构造,即实现Runnable接口。

Thread

package com.java.test;

public class ThreadTest
{
    public static void main(String[] args)
    {
        TreadTest1 thread1 = new TreadTest1();
        TreadTest2 thread2 = new TreadTest2();

        thread1.start();
        thread2.start();
    }

}
class TreadTest1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; ++i)
        {
            System.out.println("Test1 " + i);
        }
    }
}
class TreadTest2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; ++i)
        {
            System.out.println("Test2 " + i);
        }
    }
}
复制代码

当使用第一种方式(继承Thread的方式)来生成线程对象时,我们需要重写run()方法,因为Thread类的run()方法此时什么事情也不做。

Runnable

package com.java.test;

public class ThreadTest
{
    public static void main(String[] args)
    {
//         线程的另一种实现方法,也可以使用匿名的内部类
        Thread threadtest1=new Thread((new ThreadTest1()));
        threadtest1.start();
        Thread threadtest2=new Thread((new ThreadTest2()));
        threadtest2.start();
    }
}

class ThreadTest1 implements Runnable
{

    @Override
    public void run()
    {
        for (int i = 0; i < 100; ++i)
        {
            System.out.println("Hello: " + i);
        }
    }
}

class ThreadTest2 implements Runnable
{

    @Override
    public void run()
    {
        for (int i = 0; i < 100; ++i)
        {
            System.out.println("Welcome: " + i);
        }
    }
}复制代码

  当使用第二种方式(实现Runnable接口的方式)来生成线程对象时,我们需要实现Runnable接口的run()方法,然后使用new Thread(new RunnableClass())来生成线程对象(RunnableClass已经实现了Runnable接口),这时的线程对象的run()方法会调用RunnableClass的run()方法

Runnable源码

package java.lang;
public
interface Runnable {
    public abstract void run();
}
复制代码

Runnable接口中只有run()方法,Thread类也实现了Runnable接口,因此也要实现了接口中的run()方法。

当生成一个线程对象时,如果没有为其指定名字,那么线程对象的名字将使用如下形式:Thread-number,该number是自动增加的数字,并被所有的Thread对象所共享,因为它是一个static的成员变量。

停止线程 

  现在线程的消亡不能通过调用stop()命令,应该让run()方法自然结束。stop()方法是不安全的,已经废弃。

  停止线程推荐的方式:设定一个标志变量,在run()方法中是一个循环,由该标志变量控制循环是继续执行还是跳出;循环跳出,则线程结束。

线程共享资源

多线程编程很常见的情况下是希望多个线程共享资源,通过多个线程同时消费资源来提高效率,但是新手一不小心很容易陷入一个编码误区。

举个小栗子(*╹▽╹*)

下面的代码,希望通过 3 个线程同时执行 i-- ,使得输出i 的值为 0,但3 次输出的结果都为 2。这是因为在 main 方法中创建的三个线程都独自持有一个 i 变量,我们的目的一应该是 3 个线程共享一个 i 变量。

class ThreadTest1 extends Thread {
    private int i = 3;
    @Override
    public void run() {
        i--;
        System.out.println(i);
    }
}

public class ThreadTest {
    public static void main(String[] strings) {
        Thread thread1 = new ThreadTest1();
        thread1.start();
        Thread thread2 = new ThreadTest1();
        thread2.start();
        Thread thread3 = new ThreadTest1();
        thread3.start();
    }
}
输出 // 2 2 2复制代码

应该这么写

public class ThreadTest {
    public static void main(String[] strings) {
        Thread thread1 = new ThreadTest1();
        thread1.start();
        thread1.start();
        thread1.start();    
    }
}
复制代码

多个线程执行同样的代码

在这种情况下,可以使用同一个Runnable对象(看上一篇博客,这是一种创建线程的方式)将需要共享的数据,植入这个Runnable对象里面。例如买票系统,余票是需要共享的,不过在这样做的时候,我想还应该加上synchronized关键字修饰!

多个线程执行的代码不一样

在这种情况下,就两种思路可以实现(这里参考张孝祥老师的观点)

其一:将共享数据封装再另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现对改数据进行的各个操作的互斥和通信。

其二:将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

组合:将共享数据封装再另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。(示例代码所使用的方法),总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较好容易实现它们之间的同步互斥通信

简单粗暴的方式

在程序中,定义一个static变量

线程的生命周期

线程的生命周期其实就是一个线程从创建到消亡的过程。


线程可以有6种状态

New(新创建),Runnable(可运行),Blocked(被阻塞),Waiting(等待),Timed waiting(计时等待) Terminated(被终止)。

要想确定一个线程的当前状态,可调用getState方法。

1.创建状态:

  当用new操作符创建一个新的线程对象时,如 new Thread(t),该线程还没有开始运行,该线程处于创建状态,程序还没有开始运行线程中代码。处于创建状态的线程只是一个空的线程对象,系统不为它分配资源。

2.可运行状态

    一旦调用了start()方法,线程处于runnable状态。一个可运行的线程可能正在运行页可能没有运行,这取决于操作系统给线程提供运行的时间。

    一旦一个线程开始运行,它不必始终保持运行。事实上,运行的线程被中断,目的是为了让其他线程获得运行机会。线程调度的细节依赖于操作系统提供的服务。

    记住,在任何给定时刻,一个可运行的线程可能正在运行页可能没有运行,这就是为什么将这个状态后才能为可运行而不是运行。

3.不可运行状态

不可运行状态包括被阻塞或等待状态,此时线程暂时不活动,不运行任何代码且消耗最少的资源。直到线程调度器重新激活它。

    当一个线程试图获取一个内部的对象锁,而该锁呗其他线程持有,进入阻塞状态。当所有其他线程释放繁琐,允许线程调度器允许线程持有它的时候,该线程将变成非阻塞状态。

    当线程等待另一个线程通知调度器一个条件时,它自己进入等待转态。在调用wait方法和join方法,就会出现这种情况。

    有几个方法啊有一个超时参数,例如,sleep,wait,join。调用他们导致线程进入计时等待状态。这一状态将保持到超时期满或者受到适当通知。

通俗一点: 

当发生下列事件时,处于运行状态的线程会转入到不可运行状态:

  调用了sleep()方法;

  线程调用wait()方法等待特定条件的满足;

  线程输入/输出阻塞。

 返回可运行状态:

  处于睡眠状态的线程在指定的时间过去后;

  如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变;

  如果线程是因为输入输出阻塞,等待输入输出完成。

4.被终止状态

    线程因如下两个原因之一而被终止:

  •     因为run方法正常退出而自然消亡
  •     因为一个没有捕获的异常终止了run方法而消亡

线程的优先级

java线程有优先级的设定,高优先级的线程比地优先级的线程有更高的几率得到执行。

  1. 当前线程没有指定优先级时,所有线程都是普通优先级。
  2. 优先级从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
  3. 优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
  4. 与在线程池中等待运行机会的线程相比,正在运行的线程可能总是拥有更高的优先级。
  5. 由调度程序决定哪一个线程被执行。
  6. t.setPriority()用来设定线程的优先级。
  7. 记住在线程开始方法被调用之前,线程的优先级应该被设定。
  8. 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级Java线程的优先级是一个整数,其取值范围是1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

    public static final int MIN_PRIORITY = 1;
    public static final int NORM_PRIORITY = 5;
    public static final int MAX_PRIORITY = 10;复制代码

其实不然。默认的优先级是父线程的优先级。在init方法里,

Thread parent = currentThread();  
this.priority = parent.getPriority();  复制代码

可以通过setPriority方法(final的,不能被子类重载)更改优先级。优先级不能超过1-10的取值范围,否则抛出IllegalArgumentException。另外如果该线程已经属于一个线程组(ThreadGroup),该线程的优先级不能超过该线程组的优先级。




关注下面的标签,发现更多相似文章
评论