线程基础

    xiaoxiao2023-10-15  173

    文章目录

    一,概念运行程序会创建一个进程但OS调度的最小单元是线程(轻量级进程)普通的程序包含的线程:二,启动线程和退出线程1,创建线程的方法2,启动线程3、线程中断interrupt方法中断线程的两种写法try/catch块中包含while循环 处理不可中断的阻塞 三、线程的状态四、常用方法深入理解1、run()2、start()3、sleep()4、yield()5、wait()6、notify()7、notiyfAll()

    一,概念运行程序会创建一个进程但OS调度的最小单元是线程(轻量级进程)普通的程序包含的线程:

    监听Ctrl-Break // 监听中断信号附加监听器// 获取内存转储,线程转储信号调度程序// 将信号分给jvm的线程终结者// 调用对象的终结者方法参考处理程序// 清除参考主// **程序的主入口

    可以用以下代码打印出以上线程:

    public class ShowMainThread { public static void main(String[] args) { //java虚拟机的线程管理接口 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); //获取线程信息的方法 ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false); for(ThreadInfo threadInfo:threadInfos){ System.out.println(threadInfo.getThreadId()+":"+threadInfo.getThreadName()); } } }

    从上面可知:即使我们运行一个什么都不做的主方法,也会有以上6个线程,另外关于垃圾回收的GC线程,如果程序一直不用启动垃圾回收机制,那么GC线程是不会启动的。

    为什么要用多线程 1.充分利用多处理核心; 2.更快的响应时间(用户订单的场景,发送邮件等部分可由其他线程执行)

    二,启动线程和退出线程

    1,创建线程的方法

    方法一,将类声明为Thread的子类。该子类应重写Thread类的运行方法。接下来可以分配并启动该子类的实例。

    class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime } }

    然后,下列代码会创建并启动一个线程: PrimeThread p = new PrimeThread(143); p.start();

    方法二,声明实现Runnable接口的类。该类然后实现run方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。

    class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } }

    然后,下列代码会创建并启动一个线程:

    PrimeRun p = new PrimeRun(143); new Thread(p).start();

    每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。

    2,启动线程

    调用线程的start()方法即可启动线程。

    public class HowStartThread { private static class TestThread extends Thread{ @Override public void run() { System.out.println("TestThread is runing"); } } private static class TestRunable implements Runnable{ @Override public void run() { System.out.println("TestRunable is runing"); } } public static void main(String[] args) { Thread t1 = new TestThread(); Thread t2 = new Thread(new TestRunable()); t1.start(); t2.start(); System.out.println("TestRunable is runing"); } }

    线程完成:

    run()方法执行完成;抛出一个未处理的异常导致线程的提前结束

    3、线程中断

    interrupt方法

    不安全的取消: 单独使用一个取消标志位.Stop(),suspend(),resume()是过期的api,很大的副作用,容易导致死锁或者数据不一致。

    使用线程的中断 : interrupt() 中断线程,本质是将线程内部的中断标志位设为true,其他线程向需要中断的线程打个招呼。是否真正进行中断由线程自己决定。 isInterrupted() 线程检查自己的中断标志位。 静态方法Thread.interrupted() 将中断标志位复位为false。

    如果我们不使用线程内部的中断标志位,而是自己起一个变量用于标志中断,那么遇到wait()、sleep()等方法时,我们的标志位就不起作用了,这是不靠谱的做法,如:

    public class FlagCancel { private static class TestRunable implements Runnable{ private volatile boolean on = true; private long i =0; @Override public void run() { while(on){ System.out.println(i++); /*阻塞方法,on不起作用 如wait,sleep,阻塞队列中的方法(put,take) 要说明此时调用cancel,程序感受不到on的变化,Thread.currentThread().interrupt();能让线程提前结束wait,sleep */ try { synchronized (this){ wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("TestRunable is runing :"+i); } public void cancel(){ System.out.println("Ready stop thread......"); on = false; } } public static void main(String[] args) throws InterruptedException { TestRunable testRunable = new TestRunable(); Thread t = new Thread(testRunable); t.start(); Thread.sleep(10); testRunable.cancel(); } }

    运行结果:

    0 Ready stop thread......

    线程一直停留在wait()方法,没有终止。

    由上面的中断机制可知Java里是没有抢占式任务,只有协作式任务。当线程处于阻塞(如调用了java的sleep,wait等等方法时)的时候,是不会理会我们自己设置的取消标志位的,但是这些阻塞方法都会检查线程的中断标志位。一旦查到中断标志位是中断状态时,立刻中断线程,并对sleep,wait等方法有效。

    下面介绍两种中断线程的写法

    中断线程的两种写法

    先上代码:

    package com.dongnaoedu.interrupt; /** * 调用阻塞方法时,如何中断线程 */ public class BlockInterrupt { private static Object o = new Object(); /*while循环中包含try/catch块*/ private static class WhileTryWhenBlock extends Thread { private volatile boolean on = true; private long i =0; @Override public void run() { System.out.println("当前执行线程id:"+Thread.currentThread().getId()); while (on && !Thread.currentThread().isInterrupted()) { System.out.println("i="+i++); try { //抛出中断异常的阻塞方法,抛出异常后,中断标志位会改成false //可以理解为这些方法会隐含调用Thread.interrputed()方法 synchronized (o){ o.wait(); } } catch (InterruptedException e) { System.out.println("当前执行线程的中断标志位:" +Thread.currentThread().getId() +":"+Thread.currentThread().isInterrupted()); Thread.currentThread().interrupt();//重新设置一下 System.out.println("被中断的线程_"+getId() +":"+isInterrupted()); //do my work } //清理工作,准备结束线程 } } public void cancel() { //on = false; interrupt(); System.out.println("本方法所在线程实例:"+getId()); System.out.println("执行本方法的线程:"+Thread.currentThread().getId()); //Thread.currentThread().interrupt();这会把调用方的线程中断了 } } /*try/catch块中包含while循环*/ private static class TryWhileWhenBlock extends Thread { private volatile boolean on = true; private long i =0; @Override public void run() { try { while (on) { System.out.println(i++); //抛出中断异常的阻塞方法,抛出异常后,中断标志位改成false synchronized (o){ o.wait(); } } } catch (InterruptedException e) { System.out.println("当前执行线程的中断标志位:" +Thread.currentThread().getId() +":"+Thread.currentThread().isInterrupted()); }finally { //清理工作结束线程 } } public void cancel() { on = false; interrupt(); } } public static void main(String[] args) throws InterruptedException { // WhileTryWhenBlock whileTryWhenBlock = new WhileTryWhenBlock(); // whileTryWhenBlock.start(); // Thread.sleep(100); // whileTryWhenBlock.cancel(); TryWhileWhenBlock tryWhileWhenBlock = new TryWhileWhenBlock(); tryWhileWhenBlock.start(); Thread.sleep(100); tryWhileWhenBlock.cancel(); } }

    try/catch块中包含while循环

    在TryWhileWhenBlock的cancel方法中设置线程标志变量 on = false; 并调用了interrupt()方法,将线程设置为中断 ,设置中断之后,会停止wait方法并抛出InterruptedException异常,并且抛出异常后,中断标志位会改成false,抛出异常已经退出while循环,程序结束。 运行结果如下: 把main方法改成如下:

    public static void main(String[] args) throws InterruptedException { WhileTryWhenBlock whileTryWhenBlock = new WhileTryWhenBlock(); whileTryWhenBlock.start(); Thread.sleep(100); whileTryWhenBlock.cancel(); // TryWhileWhenBlock tryWhileWhenBlock = new TryWhileWhenBlock(); // tryWhileWhenBlock.start(); // Thread.sleep(100); // tryWhileWhenBlock.cancel(); }

    在WhileTryWhenBlock的cancel方法中调用了interrupt()方法,将线程的中断标志位设为true,设置中断之后,会停止wait方法并抛出InterruptedException异常,并且抛出异常后,中断标志位会改成false,我们在异常处理的代码块中调用Thread.currentThread().interrupt(); 重新设置中断位为true,重新回到while条件,不满足条件,结束循环。 运行结果如下:

    处理不可中断的阻塞

    IO通信 inputstream read/write等阻塞方法,不会理会中断,而关闭底层的套接字socket.close()会抛出socketException NIO: selector.select()会阻塞,调用selector的wakeup和close方法会抛出ClosedSelectorException

    如何让我们的代码既可以响应普通的中断,又可以关闭底层的套接字呢?

    覆盖线程的interrupt方法,在处理套接字异常时,再用super.interrupt()自行中断线程。 示例代码:

    public class OverrideInterrupt extends Thread { private final Socket socket; private final InputStream in; public OverrideInterrupt(Socket socket, InputStream in) { this.socket = socket; this.in = in; } @Override public void interrupt() { try { //关闭底层的套接字 socket.close(); } catch (IOException e) { e.printStackTrace(); //..... }finally { //同时中断线程 super.interrupt(); } } }

    三、线程的状态

    新建(NEW): 线程被创建,但是没有调用start方法。 可运行(RUNNABLE): 可运行线程的线程状态,由cpu决定是不是正在运行,有人把正在运行的线程再细分为运行状态(running)。 被阻塞(BLOCKING): 受阻塞并且正在等待监视器锁的某一线程的线程状态。 等待(WAITING ): 某一等待线程的线程状态。 计时等待(TIMED_WAITING): 具有指定等待时间的某一等待线程的线程状态。 被终止(TERMINATED ): 已终止线程的线程状态。

    四、常用方法深入理解

    1、run()

    run就是一个普通的方法,跟其他类的实例方法没有任何区别,只是线程启动后会调用到该方法。

    2、start()

    启动线程调用的方法,然后Java 虚拟机调用该线程的 run 方法。

    结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。

    3、sleep()

    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。

    sleep时期不会释放锁,如果把sleep()放在synchronized内的话,那么锁住对象之后,在sleep期间其他线程无法获得该对象锁,所以我们在用sleep时,要把sleep放在同步代码块的外面。 看个示例代码:

    package com.dongnaoedu; /** * 动脑学院-Mark老师 * 创建日期:2017/11/26 * 创建时间: 22:00 * sleep方法是否会释放锁 */ public class SleepTest { //锁 private Object lock = new Object(); public static void main(String[] args) { SleepTest sleepTest = new SleepTest(); Thread threadA = sleepTest.new ThreadSleep(); threadA.setName("ThreadSleep"); Thread threadB = sleepTest.new ThreadNotSleep(); threadB.setName("ThreadNotSleep"); threadA.start(); try { Thread.sleep(1000); System.out.println(" RunTest slept!"); } catch (InterruptedException e) { e.printStackTrace(); } threadB.start(); } private class ThreadSleep extends Thread{ @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName+" will take the lock"); try { //拿到锁以后,休眠 synchronized(lock) { System.out.println(threadName+" taking the lock"); Thread.sleep(5000); System.out.println("Finish the work: "+threadName); } } catch (InterruptedException e) { //e.printStackTrace(); } } } private class ThreadNotSleep extends Thread{ @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName+" will take the lock time="+System.currentTimeMillis()); //拿到锁以后不休眠 synchronized(lock) { System.out.println(threadName+" taking the lock time="+System.currentTimeMillis()); System.out.println("Finish the work: "+threadName); } } } }

    输出结果:

    ThreadSleep will take the lock ThreadSleep taking the lock RunTest slept! ThreadNotSleep will take the lock time=1558875732864 Finish the work: ThreadSleep ThreadNotSleep taking the lock time=1558875736864 Finish the work: ThreadNotSleep

    其中输出ThreadNotSleep will take the lock time=1558875732864 之后停了4秒,再输出下面的内容。这是因为ThreadSleep线程里面把sleep方法放到同步代码块里面了,sleep期间其他线程不能获得同一对象锁,如果把sleep方法放到同步代码块外面,那么对其他线程的影响就会小得多。

    4、yield()

    当前线程出让cpu占有权,当前线程变成了可运行状态,下一时刻仍然可能被cpu选中,不会释放锁。

    5、wait()

    调用以前,当前线程必须要持有锁,调用wait方法后,当前线程会处于等待状态,加入对象锁的等待集合中,同时,线程会释放当前持有的锁,其他线程可以在这时获取该锁,其他线程获取到该锁后,wait的线程会进入blocked状态,直到被notify() 方法或 notifyAll() 方法唤醒。

    注意1: 虽然会wait自动解锁,但是对顺序有要求, 如果在notify被调用之后,才开始wait方法 的调用,线程会永远处于WAITING状态。 注意2: 这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出 IllegalMonitorStateException异常。

    6、notify()

    唤醒在此对象监视器上等待的单个线程,如果改对象有多个线程在等待,那么唤醒哪一个完全由cpu决定(谨慎使用)

    7、notiyfAll()

    唤醒在此对象监视器上等待的所有线程。(推荐使用)

    下面是关于wait和notifyAll的实例:

    package com.dongnaoedu.waitnotify; /** * wait/notify/notifyAll的演示 */ public class User { public static final String CITY = "NewYork"; private int age; private String city; public User(int age, String city) { this.age = age; this.city = city; } public User() { } //修改用户的城市后,发出通知 public synchronized void changeCity(){ this.city="London"; notifyAll(); } //修改用户的年龄后,发出通知 public synchronized void changeAge(){ this.age = 31; notifyAll(); } //等待用户的年龄变化的方法,接收到通知,检查发现用户年龄大于30时,进行业务工作,否则一直等待 //阻塞方法 public synchronized void waitAge(){ while(this.age<=30){ try { wait(); System.out.println("wait age ["+Thread.currentThread().getId() +"] is notified!"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("the age is "+this.age);//业务工作 } //等待用户的城市变化的方法,接收到通知,检查发现用户城市不是NewYork时,进行业务工作,否则一直等待 //阻塞方法 public synchronized void waitCity(){ while(this.city.equals(CITY)){ try { wait(); System.out.println("wait city ["+Thread.currentThread().getId() +"] is notified!"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("the city is "+this.city);//业务工作 } } package com.dongnaoedu.waitnotify; public class TestUser { private static User user = new User(30,User.CITY); private static class CheckAge extends Thread{ @Override public void run() { user.waitAge(); } } private static class CheckCity extends Thread{ @Override public void run() { user.waitCity(); } } public static void main(String[] args) throws InterruptedException { for(int i=0;i<3;i++){ //启动三个等待用户年龄变化的线程 new CheckAge().start(); } for(int i = 0; 我<3; i ++){ //启动三个等待用户城市变化的线程 new CheckCity()。start(); } 了了Thread.sleep(1000); user.changeCity(); //变动用户的城市 }}

    运行结果

    wait city [19] is notified! the city is London wait city [18] is notified! the city is London wait city [17] is notified! the city is London wait age [16] is notified! wait age [15] is notified! wait age [14] is notified!

    注意:

    1、wait、notify、notifyAll只能在synchronized关键字中使用,且调用wait、notify、notifyAll的对象与锁对象相同,否则会抛出IllegalMonitorStateException异常。

    2、wait() 方法调用后,会破坏原子性。

    补充: 调用完notify或者notifyAll后,线程并不会马上从阻塞中退出,抢到锁后才会继续执行。

    package com.dongnao.concurrent.period5; /* 调用完notify后,线程并不会马上从阻塞中退出, 抢到锁后才会继续执行 */ import java.util.concurrent.locks.LockSupport; public class Demo6_WaitNotifyTest { private static final long SECOND = 1000 * 1000 * 1000L; public static void main(String args[]) throws InterruptedException { Thread th = new Thread(){ @Override public void run() { synchronized (Demo6_WaitNotifyTest.class){ try { System.out.println("线程1 进入等待。。。"); Demo6_WaitNotifyTest.class.wait(); System.out.println("主线程释放锁后,才拿到锁,结束等待"); } catch (InterruptedException e) { e.printStackTrace(); } } } }; th.start(); new Thread(){ @Override public void run() { LockSupport.parkNanos(1000 * 1000 * 1000); synchronized (Demo6_WaitNotifyTest.class){ System.out.println("如果我先打印,那么notify的线程没有特殊逻辑。。。"); } } }.start(); new Thread(){ @Override public void run() { LockSupport.parkNanos(1000 * 1000 * 1000 * 2); synchronized (Demo6_WaitNotifyTest.class){ System.out.println("线程3 拿到锁。。。"); Demo6_WaitNotifyTest.class.notify(); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); while (true){ System.out.println("th.state:" + th.getState()); Thread.sleep(200L); } } }

    输出结果(相同的输出结果只保留两条,多于的用。。。表示):

    线程1 进入等待。。。 th.state:WAITING th.state:WAITING 。。。 如果我先打印,那么notify的线程没有特殊逻辑。。。 th.state:WAITING th.state:WAITING 。。。 线程3 拿到锁。。。 th.state:BLOCKED th.state:BLOCKED 。。。 主线程释放锁后,才拿到锁,结束等待 th.state:TERMINATED th.state:TERMINATED 。。。

    可以看到:线程3拿到Demo6_WaitNotifyTest.class锁之后调用了notify,然后sleep了10000毫秒。调用notify之后线程1没有马上从阻塞中退出,而是要等线程3 sleep结束释放了Demo6_WaitNotifyTest.class锁,然后线程1重新获取Demo6_WaitNotifyTest.class锁,这才退出阻塞,运行下面的代码。

    最新回复(0)