多线程总结

进程

  • 进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,
  • 这个过程也是进程本身从产生、发展到最终消亡的过程。多进程操作系统能同时运行多个进程(程序),
  • 由于 CPU 具备分时机制,所以每个进程都能循环获得自己的 CPU 时间片。

线程

  • 多线程是实现并发机制的一种有效手段。
  • 线程是比进程更小的执行单位,线程是在进程的基础之上进行的进一步划分。所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行,
  • 一个进程可能包含了多个同时执行的线程,如图所示。

e8b18f90409b4fcab11ce8ff0f7c1989_image.png

多线程的实现

  • 方式一:继承 Thread 类 、覆写 run();方法
    class 类名称 extends Thread{ // 继承 Thread 类属性… ; // 类中定义属性
    方法… ; // 类中定义方法
    public void run(){// 覆写 Thread 类中的 run() 方法,此方法是线程的主体
    线程主体方法;
    }
    }

  • 方式二:使用 Runnable 实现多线程、覆写 run();方法
    class MyThread implements Runnable { // 定义线程主体类
    private String name; // 定义类中的属性
    public MyThread(String name) { // 定义构造方法
    this.name = name;
    }
    @Override
    public void run(){ // 覆写 run() 方法
    for (int x = 0; x < 200; x++) {
    System.out.println(this.name + "–>" + x);
    }
    }

两种方式的区别:

69e949edf91348cfac12024e0c845c34_image.png

  • 方式三:使用 Callable 接口实现的多线程(取得返回值)

    MyThread mt1 = new MyThread();// 实例化多线程对象
    FutureTask task1 = new FutureTask(mt1) ;
    // FutureTask 是 Runnable 接口子类,所以可以使用 Thread 类的构造来接收 task 对象
    new Thread(task1).start(); // 启动线程

    常用方法 类型 描述
    public FutureTask(Callable callable) 构造 接收 Callable 接口实例
    public FutureTask(Runnable runnable, V result) 构造 接收 Runnable 接口实例,并指定返回结果类型
    public V get() throws InterruptedException, ExecutionException 普通 取得线程操作结果,此方法为 Future 接口定义

线程的状态

创建状态

在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时,它已经有了相应的内存空间和其它资源,但还处于不可运行状态。新建一个线程对象可采用 Thread 类的构造方法来实现,例如:Thread thread=new Thread();

就绪状态

新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。

运行状态

当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run()方法。run() 方法定义了该线程的操作和功能。

堵塞状态

一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入输出操作时,将让出 CPU 并暂时中止自己的执行,进入堵塞状态。在可执行状态下,如果调用 sleep()、suspend()、wait() 等方法,线程都将进入堵塞状态。堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程才可以转入就绪状态。

终止状态

线程调用 stop()方法时或 run() 方法执行结束后,线程即处于终止状态。处于终止状态的线程不具有继续运行的能力


线程的操作方法

常用方法 类型 描述
public Thread(Runnable target, String name) 构造 实例化线程对象,接收 Runnable 接口子类对象,同时设置线程名称
public final void setName(String name) 普通 设置线程名字
public final String getName() 普通 取得线程名字
public static void sleep(long millis) throws InterruptedException 普通 线程休眠、设置的休眠单位是毫秒(需要用 try、catch 语句抛异常)
public final void setPriority(int newPriority) 普通 设置线程优先级
public final int getPriority(); 普通 取得线程优先级

死锁

在 Java 里面如果要想实现线程的同步可以使用 synchronized 关键字。而这个关键字可以通过两种方式使用:

  • 同步代码块,利用 synchronized 包装的代码块,但是需要指定同步对象,一般设置为 this;
  • 同步方法,利用 synchronized 定义的方法。

注:死锁虽然能够保持数据变动的一致性但是执行效率低,消耗时间久。

解决数据错乱问题

  • 数据的错位完全是因为非同步的操作所造成的,所以应该使用同步处理。因为取和设置是两个不同的操作,所以要想进行同步控制,那么就需要将其定义在一个类里面完成。
  • 等待及唤醒机制:
常用方法 类型 描述
public final void wait() throws InterruptedException 普通 线程的等待
public final void notify() 普通 唤醒第一个等待线程
public final void notifyAll() 普通 唤醒全部等待线程

class Message {

private String title ; // 保存信息的标题

private String content ; // 保存信息的内容

public synchronized void set(String title, String content) {

this.title = title;

try {

Thread.sleep(200);

} catch (InterruptedException e) {

e.printStackTrace();

}

this.content = content;

}

**public synchronized void **get() {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(this.title + "–>" + this.content);

}

// setter、getter 略

}

class Producer implements Runnable { // 定义生产者

private Message msg = null ;

public Producer(Message msg) {

this.msg = msg ;

}

@Override

public void run() {

for (int x = 0; x < 50; x++) { // 生产 50 次数据

if (x % 2 == 0) {

this.msg.set(“张三”,“123”) ; // 设置属性

} else {

this.msg.set(“李四”,“456”) ; // 设置属性

}

}

}

}

class Consumer  implements Runnable { // 定义消费者

privateMessage msg = null ;

public Consumer (Message msg) {

this.msg = msg ;

}

@Override

public void run() {

for (int x = 0; x < 50; x++) { // 取走 50 次数据

this.msg.get() ; // 取得属性

}

}

}

public class TestDemo {

public static voidmain(String[] args) throws Exception {

Message msg = new Message() ; // 定义 Message 对象,用于保存和取出数据

new Thread(new Producer(msg)).start() ; // 启动生产者线程

new Thread(new Consumer(msg)).start() ; // 取得消费者线程

}

}