Java并发编程技术知识点梳理(第二篇)线程的基本操作

目录

 

进程和线程

线程的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资源,那么可以在适当的时候调用此方法,给予其他重要线程更多的工作机会

发布了20 篇原创文章 · 获赞 14 · 访问量 8790

猜你喜欢

转载自blog.csdn.net/yemuxiaweiliang/article/details/104514093