1.多线程概念

1.1通俗理解

高速公路收费站(进程)的多个收费匝道(线程);
银行柜台(进程)的多个办事窗口(线程);
浏览器的多窗口,多标签;迅雷的多任务下载等。

1.2进程和线程关系

进程的调度是带资源调度的,而线程的调度是不带资源的。就如同参加接力赛跑一样,对于进程来说,它们就是背着书包(资源)跑,故此运动员(进程)在交接资源时,比较慢。而对于线程来说,它就好比就是轻装上阵,线程间的切换是便捷的。
进程:
一个进行中的程序,比如:一个运行着的QQ程序,同时开启多个QQ程序,那么就是多个进程;
每个进程都有自己独立的资源,比如:每个QQ进程对应的联系人,聊天内容等;
进程是操作系统的资源分配单位,创建并执行一个进程的系统开销是比较大的。
线程:
进程的一个执行路径;
多个线程间共享进程的资源;
多线程(Multithread)指的是在单个进程中同时运行多个不同的线程,执行不同的任务。多线程意味着一个程序的多行语句块并发执行;
线程被称为“轻负荷进程”。
进程和线程关系图解

2.多线程实现

2.1继承Thread类实现

2.1.1启动单个线程

public class TestThread {public static void main(String[] args) {ThreadDemo threadDemo = new ThreadDemo();threadDemo.start();threadDemo.start();threadDemo.start();threadDemo.start();     }
}
class ThreadDemo extends Thread{private int tickets = 5;@Overridepublic void run() {while (tickets>0) {System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数"+tickets);tickets--;}super.run();}
}

结果输出:从输出结果可以看到一个线程类实例化对象的多次start()方法最终都只能够启动一个线程

2.1.2启动多个线程

public class TestThread {public static void main(String[] args) {ThreadDemo threadDemo = new ThreadDemo();new ThreadDemo().start();new ThreadDemo().start();new ThreadDemo().start();new ThreadDemo().start();}
}
class ThreadDemo extends Thread{private  int tickets = 5;@Overridepublic void run() {while (tickets>0) {System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数"+tickets);tickets--;}super.run();}
}

结果输出:每个线程都占用了5张票资源,而不是共同占用5张票

将变量设置为静态实现多个线程资源共享:private static int tickets = 5;
结果输出:达成资源共享,但是还是会出现多次使用,后面的线程同步会解决这个问题

2.2实现Runnable接口实现

2.2.1启动单个线程

public class TestThread {public static void main(String[] args) {ThreadDemo2 threadDemo2 = new ThreadDemo2();new Thread(threadDemo2).start();}
}
class ThreadDemo2 implements Runnable{private int tickets = 5;@Overridepublic void run() {while (tickets>0) {System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数"+tickets);tickets--;}}
}

结果输出:可以实现资源共享

2.2.2启动多个线程

public class TestThread {public static void main(String[] args) {ThreadDemo2 threadDemo2 = new ThreadDemo2();new Thread(threadDemo2).start();new Thread(threadDemo2).start();new Thread(threadDemo2).start();}
}
class ThreadDemo2 implements Runnable{private int tickets = 5;@Overridepublic void run() {while (tickets>0) {System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数"+tickets);tickets--;}}
}

结果输出:每次结果不会完全相同,且也会出现多次占有,后续线程同步可以解决

2.3两种方式区别

(1)实现Runnable的方式可以避免单继承带来的局限性,如上述第一个例子,同一个实例化线程对象只能启动一个线程,即使start多次;
(2)实现Runnable的方式可以实现资源共享,而不会出现多个线程都卖5张票的情况

上述出现一张票同时被占有的情况原因分析:
出现“一票多卖”的现象,这是当tickets=1时,线程0、线程2和线程3都同时看见了,满足条件tickets > 0,当第一个线程就把票卖出去了,tickets理应减1,当它还没有来得及更新,当前的线程的运行时间片就到了,必须推出CPU,让其他线程执行,而其他线程看到的tickets依然是旧状态(tickets=1),所以,依次也把那张已经卖出去的票再次“卖”出去了。事实上,在多线程运行环境中,tickets属于典型的临界资源(Critical resource),run()方法中的方法体就属于临界区(Critical Section)。多个进程中涉及到同一个临界资源的临界区称为相关临界区。

3.线程的状态

3.1图解和状态名词解释


⑴ New(创建态):至今尚未启动的线程处于这种状态。
⑵ Runnable(运行态):正在Java虚拟机中执行的线程处于这种状态。
⑶ Blocked(阻塞态):受阻塞并等待某个监视器锁的线程处于这种状态。
⑷ Waiting(无限等待态):无限期的等待另一个线程来执行某一个特定操作的线程处于这种状态。
⑸ Timed_Waiting(限时等待态):具有指定等待时间的某一等待线程的线程状态。
⑹ Terminated(终止态):已退出的线程处于这种状态。

3.2生命周期

public class ThreadStatus implements Runnable{@Overridepublic void run() {System.out.println("3.线程处于运行状态");Scanner sc = new Scanner(System.in);System.out.println("4.等待IO,线程处于阻塞状态");System.out.println("5.请输入字符。。。");sc.next();//扫描输入的字符sc.close();//IO操作结束后,阻塞状态结束,重新进入就绪状态System.out.println("6.IO结束,结束阻塞状态,重新进入就绪状态,然后进入运行状态");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("7.线程进入死亡状态");}public static void main(String[] args) {Thread thread = new Thread(new ThreadStatus());System.out.println("1.处于创建状态");thread.start();System.out.println("2.处于就绪状态");}
}

结果输出:

3.3start()和run()区别

start():
(1)启动一个新线程,调用了start才能够真正实现多线程;
(2)启动线程后,不需要等待线程的run方法执行完毕,如果主线程(main方法)CPU时间片没用完,则继续执行start之后的语句;
(3)等于主线程兵分两路,一路创建新线程,一路继续执行主线程的语句;
(4)不能被同一个实例化线程对象重复调用,并不会多创建线程,始终是一个
run():
(1)run方法中的是线程体,run结束则该线程结束;
(2)如果没有start直接调用run,那还是主线程直接调用一个普通方法,顺序执行,和多线程没有关系;
(3)可以被重复多次调用

3.4守护线程

3.4.1概念

JVM中线程分为用户线程(普通线程)和守护线程(垃圾回收等服务线程,为用户线程提供服务);
当进程中只剩下守护线程时,进程就会结束,如果有任何用户线程存在,JVM都不会退出;
默认创建的线程都是用户线程,通过调用setDaemon(true)方法可以把用户线程转换为守护线程。

3.4.2使用

public class ThreadDaemon {public static void main(String[] args) {ThreadDemo3 threadDemo3 = new ThreadDemo3();Thread t = new Thread(threadDemo3);t.setDaemon(true);//设置为守护线程,必须在start方法之前t.start();try {System.out.println("我休眠了");Thread.sleep(200);System.out.println("我休眠结束了");} catch (InterruptedException e) {e.printStackTrace();}}
}
class ThreadDemo3 implements Runnable{@Overridepublic void run() {for (int i = 0; true; i++) {System.out.println(Thread.currentThread().getName()+"线程正在运行");}}
}

结果输出:线程内部的run是无线循环,但是因为线程是守护线程,所以在main结束后,整个进程结束。

3.5线程的联合

join()方法可以使两个线程联合,在A线程中执行过程中调用B.join(),A线程将被挂起,等待B执行结束后再继续执行A线程;
join(long millis),join(long millis,int nanos)可以设置等待的时间。

public class ThreadJoin {public static void main(String[] args) {ThreadJoinTest threadJoinTest = new ThreadJoinTest();Thread t = new Thread(threadJoinTest);t.start();for (int i = 0; i <5 ; i++) {if (i == 3) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("main Thread"+ i);}}
}
class ThreadJoinTest implements Runnable{@Overridepublic void run() {for (int i = 0; i <5 ; i++) {System.out.println(Thread.currentThread().getName()+"-----"+i);}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}
}

结果输出:main线程正在执行,遇到t.join()后挂起,优先执行t,必须在t执行结束之后再继续执行main,否则不会出现Thread-0顺序实行完整的5次。

尝试去掉join之后查看输出结果

结果输出:main和t线程在交替执行,所以没有明显的先后顺序,所以可以看出json的作用,使得两个线程能够有先后顺序执行

3.6线程的中断

3.6.1使用场景举例

比如多线程数据库查询,其中一个线程返回了结果,其他的线程就可以中止了

3.6.2中断方法

⑴ Thread.interrupt():来设置中断状态为true,当一个线程运行时,另一个线程可以调用另外一个线程对应的interrupt()方法来中断它。
⑵ Thread.isInterrupted():来获取线程的中断状态。
⑶ Thread.interrupted():这是一个静态方法,用来获取中断状态(),并清除中断状态,其获取的是清除之前的值,也就是说连续两次调用此方法,第二次一定会返回false。

public class ThreadInterrupt {public static void main(String[] args) {ThreadInterruptTest threadInterruptTest = new ThreadInterruptTest();Thread t = new Thread(threadInterruptTest);t.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("在main()方法中-中断其他线程");t.interrupt();System.out.println("在main()方法中-退出");}}
class ThreadInterruptTest implements Runnable{@Overridepublic void run() {try {System.out.println("在run()方法中这个线程-休眠10000ms");Thread.sleep(10000);System.out.println("在run()方法中这个线程-继续执行");} catch (InterruptedException e) {System.out.println("在run()方法中这个线程-中断");return;}System.out.println("在run()方法中这个线程-休眠之后继续执行");System.out.println("在run()方法中这个线程-正常退出");}
}

结果输出:t在休眠时被main中断,走到catch的InterruptedException异常中,然后结束休眠。
调用interrupt()方法并不会使正在执行的线程停止执行,它只对调用wait、join、sleep等方法或由于I/O操作等原因受阻的线程产生影响,使其退出暂停执行的状态。
换句话说,它对正在运行的线程是不起作用的,只有对阻塞的线程有效。

public static void main(String[] args) {ThreadInterruptTest threadInterruptTest = new ThreadInterruptTest();Thread t = Thread.currentThread();System.out.println("1.t线程启动之前的线程中断状态:"+t.isInterrupted());t.interrupt();System.out.println("2.t线程被中断后的中断状态:"+t.isInterrupted());System.out.println("3.t被中断后的中断状态,第二次查询中断状态"+t.isInterrupted());try {Thread.sleep(2000);System.out.println("线程没有被中断");} catch (InterruptedException e) {e.printStackTrace();System.out.println("线程被中断");}System.out.println("4.sleep抛出异常,清除中断标志后,查询中断状态"+t.isInterrupted());}

结果输出:从结果可以看出,sleep抛出异常后,中断状态被清除;程序中的t线程即为main线程

4.同步

4.1问题引出和原因

public class ThreadSynchronization {public static void main(String[] args) {ThreadDemoo threadDemoo = new ThreadDemoo();//启动四个线程new Thread(threadDemoo).start();new Thread(threadDemoo).start();new Thread(threadDemoo).start();new Thread(threadDemoo).start();}
}
class ThreadDemoo implements Runnable {@Overridepublic void run() {int ticket = 5;while(ticket>0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"售卖第"+ticket+"张票");ticket-=1;}}
}

结果输出:会发现多个线程抢占同一张票,一票多卖的情况发生
问题原因:当ticket=5>0,线程1满足while条件,进入循环,然后执行完输出,没有执行到 ticket-=1,此时线程1的CPU运行时间片到了,此时ticket还是5,线程2满足条件进入循环,执行输出和线程1一模一样,从而导致了“一票多卖”的情况。根本原因在于没有对临界资源ticket做限制和控制。

4.2问题的解决

4.2.1同步代码块

结果输出:通过synchronized关键字实现对临界区的原子性代码的同步,实现在同一时刻只能由一个线程可以进入同步代码块内运行,只有当该线程离开同步代码块后,其他线程才能进入同步代码块内运行。

原子性代码:这里所谓的原子性是指,一段代码要么被执行,要么不被执行,不存在执行一部分被中断的情况。也就是说,这段代码的执行像原子一样,不可拆分。这段代码就好比一座独步桥,任何时刻都只能有一个人在桥上行走,即程序中不能有多个线程同时访问临界区,这就是线程的互斥——一种在临界区执行的特殊同步。一般意义上的同步是指,多线程(进程)在代码执行的关键点上,互通消息、相互协作,共同把任务正确地完成。

4.2.2同步方法

结果输出:synchronized修饰的方法作用的也是同一个对象(不是同一个类的不同对象),当某个对象被其中一个线程获取,其他线程就不能使用,直到该线程结束。

4.2.3死锁

public class DeadLock {static String knife = "餐刀";static String fork = "叉子";static class A extends Thread{public void run(){synchronized (knife){System.out.println("甲拿起了"+knife+",等待"+fork);try {Thread.sleep(10);} catch (InterruptedException e) {}synchronized (fork){System.out.println("甲又拿起了"+fork);}}}}static class B extends Thread{public void run(){synchronized (fork){System.out.println("乙拿起了"+fork+",等待"+knife);try {Thread.sleep(10);} catch (InterruptedException e) {}synchronized (knife){System.out.println("乙又拿起了"+knife);}}}}static class Demo extends Thread{public Demo(){this.setDaemon(true);}public void run(){while (true){try {Thread.sleep(100);} catch (InterruptedException e) {}System.out.println("守护线程:程序正在运行中。。。");}}}public static void main(String[] args) {new A().start();new B().start();new Demo().start();}
}

结果输出:
A线程先获取了knife对象的锁,然后去获取fork对象的锁,此时fork对象被B线程获取;
这样A,B线程都无法获取到下一步的对象的锁,造成了循环等待,由守护线程可以得知当前进程一直有线程在运行,从而导致了死锁。
如何预防?
保证AB线程获取资源对象的顺序一致,都是先knife再fork。

5.通信

5.1生产者和消费者

生产者:向数据空间中存放数据(姓名+性别)
消费者:从数据空间中取数据(姓名+性别)
存在的意外:1.生产者存放了姓名,还没来得及存放性别,消费者就来取该姓名的性别,只能取到上一个人的性别;2.生产者存放了若干数据后,消费者才开始取数据,或者生产者没有存放新数据,消费者重复取旧的数据。

5.1.1通信问题一:资源不同步

public class ThreadCommunacation {public static void main(String[] args) {Person q = new Person();new Thread(new Producer(q)).start();new Thread(new Consumer(q)).start();}
}
class Person{String name = "李四";String sex  = "女";
}
class Producer implements Runnable{Person person = null;public Producer(Person p){this.person = p;}@Overridepublic void run() {for (int i = 0; i <10 ; i++) {if (i % 2== 0) {person.name = "张三";try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }person.sex = "男";}else {person.name = "李四";try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }person.sex = "女";}}}
}
class Consumer implements Runnable{Person qerson = null;public Consumer(Person q){this.qerson = q;}@Overridepublic void run() {for (int i = 0; i <10 ; i++) {System.out.println(qerson.name+"----->"+qerson.sex);try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }}}
}

结果输出:张三和李四的姓名和性别错乱,Producer和Consumer操作同一个Person对象,导致出现不同步。

5.1.2通信问题二:数据重复

public class ThreadCommunacation {public static void main(String[] args) {Person q = new Person();new Thread(new Producer(q)).start();new Thread(new Consumer(q)).start();}
}
class Person{String name = "李四";String sex  = "女";public synchronized void set(String name,String sex) {this.name = name;this.sex = sex;}public synchronized void get(){System.out.println(this.name+"----->"+this.sex);}
}
class Producer implements Runnable{Person person = null;public Producer(Person p){this.person = p;}@Overridepublic void run() {for (int i = 0; i <10 ; i++) {if (i % 2== 0) {person.set("张三","男");}else {person.set("李四","女");}}}
}
class Consumer implements Runnable{Person qerson = null;public Consumer(Person q){this.qerson = q;}@Overridepublic void run() {for (int i = 0; i <10 ; i++) {qerson.get();}}
}

结果输出:解决问题一的资源同步问题,但是出现了重复取数据的问题。

5.2通信方法解决同步问题

5.2.1通信方法

wait():通知当前线程进入睡眠状态,直到其他线程进入并调用notify()或notifyAll()为止。在当前线程睡眠之前,该线程会释放所占有的“锁标志”,即其占有的所有synchronized标识的代码块可被其他线程使用。
notify():唤醒在该同步代码块中第1个调用wait()的线程。这类似排队买票,一个人买完之后,后面的人才可以继续买。
notifyAll():唤醒该同步代码块中所有调用wait的所有线程,具有最高优先级的线程首先被唤醒并执行。
wait()、notify()、notifyAll()这3个方法只能在synchronized方法中调用,即无论线程调用的是wait()还是notify()方法,该线程必须先得到该对象的所有权。这样,notify()就只能唤醒同一对象监视器中调用wait()的线程。而使用多个对象监视器,就可以分别有多个wait()、notify()的情况,同组里的wait()只能被同组的notify()唤醒

5.2.2解决通信问题:数据重复

添加记录数据存储空间状态的标识符,标识符true可以取数据,false不可以取数据

class Person{String name = "李四";String sex  = "女";private boolean flag = false;public synchronized void set(String name,String sex) {//如果flag=true,证明是person对象有数据,休眠到当前Producer,后者Consumer线程可以取数据if (flag) {try { wait(); } catch (InterruptedException e) { e.printStackTrace(); }}this.name = name;this.sex = sex;//person对象放入数据之后设置状态为true,flag = true;//唤醒最先到达的线程:Consumer线程消费之后就可以唤醒之前到达的Producer线程notify();}public synchronized void get(){//falg=false时,不能取数据,休眠当前Consumer线程if (!flag) {try {wait(); } catch (InterruptedException e) { e.printStackTrace(); }}System.out.println(this.name+"----->"+this.sex);//取完数据之后,设置为falseflag = false;//唤醒最先到达的线程:第一个Consumer线程过来时被休眠,唤醒第一个休眠的Consumernotify();}
}

结果输出:通过线程间的wait和notify通信,实现数据的准确读取和避免重复读取。

java多线程概念、实现、状态和生命周期、同步、通信相关推荐

  1. Java多线程——线程的优先级和生命周期

    Java多线程--线程的优先级和生命周期 摘要:本文主要介绍了线程的优先级以及线程有哪些生命周期. 部分内容来自以下博客: https://www.cnblogs.com/sunddenly/p/41 ...

  2. JAVA多线程:线程创建过程以及生命周期总结

    1)如果所有的前台线程死亡,那么后台线程也会自动死亡. 2)一旦线程A调用了线程B的join()方法,那么线程B将会启动执行.此时,线程A会等待线程B执行完成后再继续执行. 应用场景: 可以将大任务分 ...

  3. 【笔记】【一文搞定】java - 多线程:内存模型、生命周期、方法/关键词、并发问题、线程池、案例

    参考: Java内存模型(JMM)详解 - https://zhuanlan.zhihu.com/p/518758482 线程安全性详解(原子性.可见性.有序性) - https://blog.csd ...

  4. 线程知识点(一)—— 程序、进程、线程之间的区别与联系、Java的线程状态和生命周期

    1 程序.进程.线程之间的区别与联系 三者之间的形象化理解: * 程序:代码实现了功能,就是程序,是静态的: * 进程:执行中的程序就是进程,是动态的: * 线程:进程内的一个执行单元,也是进程内的可 ...

  5. 一张图弄懂java线程的状态和生命周期

    转载自 一张图弄懂java线程的状态和生命周期 上图是一个线程的生命周期状态流转图,很清楚的描绘了一个线程从创建到终止的过程. 这些状态的枚举值都定义在java.lang.Thread.State下 ...

  6. [转]Java 对象锁-synchronized()与线程的状态与生命周期

    线程的状态与生命周期 Java 对象锁-synchronized() ? 1 2 3 4 synchronized(someObject){ //对象锁 } 对象锁的使用说明: 1.对象锁的返还. 当 ...

  7. (二)Java线程与系统线程,生命周期

    本专栏多线程目录: (一)线程是什么 (二)Java线程与系统线程和生命周期 (三)Java线程创建方式 (四)为什么要使用线程池 (五)四种线程池底层详解 (六)ThreadPoolExecutor ...

  8. k8s的Pod状态和生命周期管理

    Pod状态和生命周期管理 一.什么是Pod? 二.Pod中如何管理多个容器? 三.使用Pod 四.Pod的持久性和终止 五.Pause容器 六.init容器 七.Pod的生命周期 (1)Pod pha ...

  9. Hibernate→HQL、query.list()返回数据类型、查询相关语句、分页、原生SQL、@注解、持久化对象状态及生命周期、一多关系、继承映射关系、逆向工程

    HQL Query实例与表 session通用工具类 Query对象 from 类→List<类>接收 映射类 仅查询商品 查询商品及所在商家 别名 返回数据类型定义 Iterator接收 ...

  10. 电商中订单的状态有哪几种,请依次说明各个状态的生命周期

    当用户点击"一键购买"或者是从购物车里点击 "去结算" ,会跳转到 "核实订单信息"  页面,当全部核实以后点击"提交订单按钮&q ...

最新文章

  1. 白盒测试的3中主要方法(cont.)
  2. @RestController和@Controller注解的区别
  3. JDBC、分层(分包)
  4. 安装SQL2005 29506错误码的解决方案
  5. linux时间同修改,linux 系统时间修改同步
  6. 面试官:要不讲讲 Cookie、Session、Token、JWT之间的区别?
  7. matlab绘图白边设定
  8. 商城系统学习总结(1)——订单与库存在高并发场景下案例解析
  9. 如何维持手机电池寿命_手机如何正确充电,电池寿命长。
  10. 推荐系统 | 信息过载的大数据时代,大数据推荐系统如何搭建,趋势何方
  11. PHP导出数据库数据字典脚本
  12. LINUX内核的进程调度策略
  13. Atitit velocity 模板引擎使用法 目录 1.1. 1.4 Context 1 1.1.1. 1.4.1 Context 基本概念 1 1.2. .3不同模式下使用velocity 1
  14. Macbook M1电脑安装svn及使用
  15. 超表面透镜相位matlab,基于超透镜的小F数大景深镜头的设计方法及应用与流程...
  16. markdown使用文档(Typora 快捷键)
  17. Docker服务以及容器如何设置自动启动
  18. hihoCoder 1096 Divided Product 微软苏州校招笔试 12月27日
  19. 为什么用交叉线而不是直通线连接相同的设备
  20. FL Studio2020中文版下载安装激活教程及优缺点介绍

热门文章

  1. 【春招实习】贝壳金服电话一面
  2. Android指南针陀螺仪开发
  3. 根据State筛选数据表格
  4. [NOI2016] 优秀的拆分 题解
  5. MySQL 数据库 day-03
  6. jav皮卡_前5名:12个模因,皮卡第,AMP打开还是关闭? 和更多
  7. C++ 输入输出(cin cout)加速/效率优化
  8. 荣耀magic v参数配置
  9. 一篇最通俗易懂的https
  10. Win10自带的SSH服务 scp功能传输文件(linux)