多线程的常用操作方法(附牛客练习题)

1.线程命名与取得
若没有设置线程名字,则会自动分配一个线程名字
注:如果直接调用线程对象的run()方法,则run()方法里不能直接通过getName()方法来获得当前执行线程的名字,而是需要使用Thread.currentThread()方法先获得当前线程,再调用线程对象的getName()方法来获得线程的名字。
代码实现:

public class ThreadMethod extends Thread  {
    @Override
    public void run() {
        //this:  ThreadMethod类的对象, getName()获取当前线程的名称
        System.out.println(this.getName() + " thread(extends)");
    }
}
public static void main(String[] args) {
        Thread thread1 = new ThreadMethod();
        thread1.setName("打印输出线程");
        thread1.start();
        for (int i = 0; i < 10; i++) {
            Runnable runnable1 = new RunnableMethod();
            Thread thread2 = new Thread(runnable1, "线程-" + i);
            thread2.setName("业务线程-" + i);
            thread2.start();
            //主程序(程序入口)线程main
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName());
        }
    }

主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的
每当使用了java命令去解释程序的时候,都表示启动了一个新的JVM进程。而主方法只是这个进程上的一个线程而已
线程死亡:
线程会以如下三种方式结束,结束后就处于死亡状态
(1)run()或call()方法执行完成,线程正常结束
(2)线程抛出一个未捕获的Exception或Error
(3)直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁
2.线程休眠:sleep()方法
线程休眠,等到了预计时间之后再恢复执行(又到就绪状态)
sleep()方法有两种重载形式:
(1)static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
(2)static void sleep(long millis,int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。

对于单核CPU:真正意义上是串行执行
对于多核CPU:同一时间真正意义上的并行执行
Thread.sleep:
(1)线程暂停执行,进入阻塞状态
(2)交出CPU
(3)不释放占用的资源锁
(4)休眠时间到了回到就绪状态
代码实现:

public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("Start: " + LocalDateTime.now());
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("End: " + LocalDateTime.now());
        }, "Thread-Sleep").start();
    }

3.线程让步:yield()方法
(1)线程暂停执行,进入就绪状态
(2)交出CPU,但不确定具体时间
(3)不释放占用的资源锁
(4)相同优先级的线程拥有交出CPU的权限
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
sleep()方法声明抛出了 异常,所以调用sleep()方法时要么捕获该异常,要么显示声明抛出该异常;而yield()方法则没有声明抛出任何异常
具体代码实现:

public static void main3(String[] args) {
        Runnable runnable = () -> {
            System.out.println("Start: " + LocalDateTime.now());
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Thread.yield();
            }
            System.out.println("End: " + LocalDateTime.now());
        };
        //单核CPU
        new Thread(runnable, "Thread-Sleep-1").start();//sleep
        new Thread(runnable, "Thread-Sleep-2").start();
        new Thread(runnable, "Thread-Sleep-3").start();
    }

4.join()方法
在线程A中调用线程B的join方法,线程A暂停执行,直到线程B执行完,线程A才继续执行。
join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。
代码实现:

public static void main(String[] args) {
        Thread thread = new Thread(new RunnableJoin(), "Thread-A");
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "main中的代码");

    }
 class RunnableJoin implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 执行开始时间 :" + LocalDateTime.now());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 大量代码");
            System.out.println(Thread.currentThread().getName() + " 执行结束时间 :" + LocalDateTime.now());

        }
        }

5.线程停止
线程停止的三种方式:
(1) 设置标记位,可以是线程正常退出。
(2) 使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
(3) 使用Thread类中的一个interrupt() 可以中断线程
代码实现:


    class RunnableStop implements Runnable {

        private boolean flag = true;

        @Override
        public void run() {
            int i = 0;
            while (flag) {
                try {
                 //这里阻塞之后,线程被调用了interrupte()方法,
                // 清除中断标志,就会抛出一个异常
// java.lang.InterruptedException
                    Thread.sleep(1000);//阻塞
                    //非阻塞
                    //此处有代码
                    boolean status = Thread.currentThread().isInterrupted();
                    if (status) {
                        System.out.println("非阻塞状态 " + status);
                        break;
                    }
                    i++;
                    System.out.println(Thread.currentThread().getName() + " 运行了 " + i + " 次");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //阻塞状态
                    boolean status = Thread.currentThread().isInterrupted();
                    //false 中断标志位清除
                    System.out.println("阻塞状态 " + status);
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName() + " 终于停了");
        }

        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    }

 public static void main6(String[] args) {
        RunnableStop runnableStop = new RunnableStop();
        Thread thread = new Thread(runnableStop, "Thread-Stop-1");
//thread.stop();
        thread.interrupt();
        System.out.println(Thread.currentThread().getName() + "代码执行完了");
    }

interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程
interrupt()方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程的当前状
态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,还会有如下情况之一的操作:
如果是wait、sleep以及join三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException;
如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理;
通过触发中断,则由开发者决定如何处理线程业务

线程的优先级:
设置优先级:
public final void setPriority(int newPriority)
取得优先级:
public final int getPriority()
对于优先级设置的内容可以通过Thread类的几个常量来决定

  1. 最高优先级:public final static int MAX_PRIORITY = 10;
  2. 中等优先级:public final static int NORM_PRIORITY = 5;
  3. 最低优先级:public final static int MIN_PRIORITY = 1;
    主线程(main) 默认优先级是5
    线程优先级具有继承性,如果线程A中创建线程B,则B的默认优先级继承线程A的优先级
    具体的实现代码:
public static void main(String[] args) {
        Thread thread1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "优先级" + Thread.currentThread().getPriority()), "Thread-1");
        thread1.setPriority(10);
        thread1.start();
        System.out.println(Thread.currentThread().getPriority());
        //main默认优先级是5
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "优先级" + Thread.currentThread().getPriority());
                Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName() + "优先级" + Thread.currentThread().getPriority());
                    }
                }, "Thread-Parent-A-Name-is-B").start();
            }
        }, "Thread-Parent-main-Name-is-A").start();

    }
}

牛客题:
代码如下所示:

import java.util.ArrayList;
import java.util.List;

public class NameList {
    private List names=new ArrayList();
    public synchronized void add(String name){
        names.add(name);
    }
    public synchronized void printAll(){
        for(int i=0;i<names.size();i++){
            System.out.println(names.get(i)+"");
        }
    }

    public static void main(String[] args) {
        final  NameList s1=new NameList();
        for(int i=0;i<2;i++){
            new Thread(){
                public  void run(){
                    s1.add("A");
                    s1.add("B");
                    s1.add("C");
                    s1.printAll();
                }
            }.start();
        }
    }
}

分析;
线程1占领add锁,执行ABC后释放锁
线程2占领add锁,执行ABC后释放锁
期间线程1占领printAll锁输出,所以线程1输出3到6个元素
线程1执行完printAll方法后释放锁,线程二执行printAll方法并占领锁,此时add移执行完,所以第二次输出的字符个数等于6
所以执行结果:ABCABCABC
也可能是:AABBCCAABBCC ,ABCAABCABC等
因为添加字符顺序是不确定的,但是确定第一个添加的字符肯定是A.

猜你喜欢

转载自blog.csdn.net/weixin_42373873/article/details/91065604