欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程的基本操作

程序员文章站 2022-05-04 18:17:43
...

目录

 

进程和线程

线程的6种状态及状态转换

6种状态

状态转换关系图

新建线程

终止线程

线程中断(重要)

等待(wait)和通知(notify)

挂起(suspend)和继续执行(resume)线程

等待线程结束(join)和谦让(yield)


进程和线程

进程:是系统进行资源分配的基本单位。

线程:是程序执行,资源调度的最小单位。

关系:线程也叫轻量级进程,进程是线程的容器

线程的6种状态及状态转换

6种状态

本文特指Java语言中的线程状态及其转换,和操作系统级别的(进程)线程状态及其转换有所区别

先看一下java中Thread类的源码定义的6种状态,读源码很重要,读源码的注释更重要。

public enum State {
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,
        TERMINATED;
    }

 

  1. 新建(New):创建后尚未启动的线程处于这种状态。
  2. 运行(Runnable):Runnable包括了操作系统线程状态中的Running(执行态)和Ready(就绪态)。处于此状态的线程有可能正在执行,也可能等待CPU为它分配执行时间。
  3. 无限期等待(Waiting):处于这种状态的线程不会被分配CPU执行时间,他们要等待被其他线程显式唤醒。举例见上面源码注释
  4. 限期等待(Timed Waiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。举例见上面源码注释。
  5. 阻塞(Blocked):阻塞状态和等待状态的区别在于:阻塞状态在等待获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而等待状态则是在等待一段时间,或者唤醒动态的发生。在线程等待进入同步区域的时候,线程将进入阻塞状态
  6. 结束(Terminated):已经终止线程的线程状态,线程已经结束运行。

状态转换关系图

线程的基本操作

新建线程

使用new关键字创建一个线程对象,并且调用其start()方法即可。线程类Thread,有一个run()方法。

start()方法就会新建一个线程并让此线程执行run()方法。

package test8;

public class Main {
    public static void main(String[] args) {
        Thread t1= new Thread(){
            @Override
            public void run(){
                System.out.println("Hello");
            }
        };
        t1.start();
    }
}

一个普通类要成 线程类的几种办法(自定义线程)

  • 1.普通类继承(extends)Thread类,重写run()方法。考虑到Java是单继承的,继承本身是一种宝贵的资源,所以此方法不常用。
  • 2.普通类实现(implements)Runnable接口,重写run()方法。该方案较为常用。
package test8;

public class MyThread implements Runnable {
    @Override
    public void run(){
        System.out.println("我是一个自定义的线程类创建的对象");
    }
}
package test8;

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread());
        thread.start();
    }
}

 线程的基本操作

  • 3.一个普通类实现Callable<T>接口,并重写call()方法。注意:在使用此线程类的时候需要结合FutureTask实现使用,用于接收运算结果
package test9;

import java.util.concurrent.Callable;

public class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception{
        return "我是自定义线程产生的一个对象";
    }
}
package test9;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) {
        //执行Callable方式,需要FutureTask实现,用于接收运算结果
        FutureTask<String> futureTask = new FutureTask<>(new MyThread());
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
  •  4.创建线程池的方式创建线程,在这里不多做赘述,可以参考我的其他关于线程池的文章。

终止线程

一般来说,线程执行完毕就会结束,无须手工关闭。但是我们在某些条件下需要手动控制线程结束,并不会让线程执行完毕自动结束。

Thread类提供了stop()方法。但是该方法已经被废弃,原因是因为该方法在结束线程时,会直接终止线程,并立即释放此线程所持有的锁。而锁的作用是保证数据的一致性。可想而知,该方法会引起一些数据不一致的问题

线程的基本操作

示例:

package test10;

/**
 * 用户类
 */
public class User {
    private int id;
    private String name;

    public User() {
        id = 0;
        name = "0";
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
    //...省略get set方法
   
}
package test10;

public class WriterThread implements Runnable {
    private User user;

    public WriterThread(User user) {
        this.user = user;
    }

    @Override
    public void run(){
        while (true){
            synchronized (user){
                int v = (int)(System.currentTimeMillis()/1000);
                user.setId(v);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                user.setName(String.valueOf(v));
            }
            Thread.yield();
        }
    }
}

 

package test10;

public class ReaderThread implements Runnable {
    private User user;
    public ReaderThread(User user) {
        this.user = user;
    }

    @Override
    public void run() {
        while (true){
            synchronized (user){
                if(user.getId() != Integer.parseInt(user.getName())){
                    System.out.println(user.toString());
                }
            }
            Thread.yield();
        }
    }
}
package test10;

public class Main {
    public static void main(String[] args) throws InterruptedException{
        User user = new User();
        ReaderThread readerThread = new ReaderThread(user);
        Thread threadRead = new Thread(readerThread);
        threadRead.start();
        while (true){
            WriterThread writeThread = new WriterThread(user);
            Thread threadWrite = new Thread(writeThread);
            threadWrite.start();
            Thread.sleep(150);
            threadWrite.stop();
        }
    }
}

线程的基本操作 

在此种情况下,如何终止线程呢,一般采用标记变量的方式。

线程的基本操作

线程的基本操作 

线程的基本操作 

线程中断(重要)

线程中断是一种重要的线程协作机制,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程退出。至于目标线程接到通知后如何处理,则完全由目标线程自行决定。

public void interrupt();//中断线程
public boolean isInterrupted();//判断是否被中断
public static boolean interrupted();//判断是否被中断,并清除当前的中断状态

interrupt()方法通知目标线程中断,也就是设置中断标志位,中断标志位表示当前线程已被中断了。

isInterrupted()方法判断当前线程是否被 中断(通过检查中断标志位)

interrupted()方法也可用来判断当前线程的中断状态,但同时会清除中断标志位 

package test11;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run(){
                while(true){
                    if(Thread.currentThread().isInterrupted()){
                        System.out.println("当前线程被中断了");
                        break;
                    }
                    Thread.yield();
                }
            }
        };
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

此例和终止线程的标记变量的方式比较类似,但是此功能更为强大,如果在循环体中, 出现了类似于wait()方法或者sleep()方法这样的操作,则只能通过中断来识别了。

等待(wait)和通知(notify)

为了支持多线程之间的协作,jdk提供了两个非常重要的和线程相关的方法:等待wait()方法和通知notify()方法,这两个方法不在Thread类中,而是在Object类中。

public final void wait() throws InterruptedException;
public final native void notify();

工作过程: 如果一个线程调用了object.wait()方法,那么它就会进入object对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。当object.notify()方法被调用时,它就会从这个等待队列中随机选择一个线程,并将其唤醒。需要注意,这个选择是不公平的。

注意:Object.wait()方法并不能随便调用,必须包含在对应的synchronized语句中。

wait()方法和notify()方法的工作流程细节

线程的基本操作

package test12;

public class ReadThread implements Runnable {
    private Object object;

    public ReadThread(Object object) {
        this.object = object;
    }

    @Override
    public void run() {
        synchronized (object){
            System.out.println("读线程开启");
            try{
                System.out.println(System.currentTimeMillis()+"读线程 start!");
                object.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis()+"读线程 end!");
        }
    }
}
package test12;

public class WriteThread implements Runnable {
    private Object object;

    public WriteThread(Object object) {
        this.object = object;
    }
    @Override
    public void run() {
        synchronized (object){
            System.out.println(System.currentTimeMillis()+"写线程启动!");
            object.notify();
            System.out.println(System.currentTimeMillis()+"写线程 end!");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
package test12;

public class Main {
    public static void main(String[] args) {
        Object object = new Object();
        Thread read = new Thread(new ReadThread(object));
        Thread write = new Thread(new WriteThread(object));
        read.start();
        write.start();
    }
}

线程的基本操作 

 wait和sleep的关系和区别:都可以让线程等待若干时间,除了wait()方法可以被唤醒外,另外一个主要区别就是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。

挂起(suspend)和继续执行(resume)线程

线程的基本操作

线程的基本操作 

不推荐使用的原因是因为挂起线程suspend()方法在导致线程暂停的同时,并不会释放任何锁资源。直到对应的线程进行了resume()方法操作,被挂起的线程才能继续。

替代方案:使用wait()和notify()以及标记变量。

package test13;

public class WriteThread implements Runnable {
    private Object object;
    volatile boolean suspendThread = false;

    public void setSuspendThread(boolean suspendThread) {
        this.suspendThread = suspendThread;
    }
    public void resumeThread(){
        this.suspendThread = false;
        synchronized (this){
            notify();
        }
    }

    public WriteThread(Object object) {
        this.object = object;
    }
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            synchronized (this){
                while (suspendThread){
                    try{
                        wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
            synchronized (object) {
                System.out.println("写线程在写内容");
                try {
                    Thread.sleep(400);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Thread.yield();
        }
    }
}
package test13;

public class ReadThread implements Runnable {
    private Object object;

    public ReadThread(Object object) {
        this.object = object;
    }

    @Override
    public void run() {
        for (int i=0;i<10;i++) {
            synchronized (object) {
                System.out.println("读线程在读内容");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            Thread.yield();
        }
    }
}
package test13;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        WriteThread writeThread = new WriteThread(object);
        Thread write = new Thread(writeThread);
        ReadThread readThread = new ReadThread(object);
        Thread read = new Thread(readThread);
        write.start();
        read.start();
        Thread.sleep(1000);
        writeThread.setSuspendThread(true);
        System.out.println(System.currentTimeMillis()/1000+"写线程挂起两秒");
        Thread.sleep(2000);
        System.out.println(System.currentTimeMillis()/1000+"继续执行写线程");
        writeThread.resumeThread();

    }
}

 线程的基本操作

 

等待线程结束(join)和谦让(yield)

join使用的场景:一个线程的输入可能非常依赖另一个线程或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。

yield使用的场景:它会让当前线程让出CPU。如果一个线程不那么重要,或者优先级特别低,而且又担心它会占用太多的CPU资源,那么可以在适当的时候调用此方法,给予其他重要线程更多的工作机会

相关标签: 并发编程