[TOC]
多线程Thread入门 1.简单概述 描述:什么是线程?
线程是程序执行的一条路径, 一个进程中可以包含多条线程
多线程并发执行可以提高程序的效率, 可以同时完成多项工作;(简单说利用了空闲时间)
多线程的应用场景:
迅雷开启多条线程一起下载
QQ同时和多个人一起视频
服务器同时处理多个客户端请求
多线程并行和并发的区别?
并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行(需要多核CPU
)
比如:我跟两个网友聊天,左手操作一个电脑跟甲聊同时右手用另一台电脑跟乙聊天这就叫并行。
并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
比如:用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。
注意: 一颗CPU在同一时刻只处理一个任务,只不过执行时间(执行效率高
)太短让我们误认为是同一时刻运行多个后台程序;
Java程序运行原理:
Java命令会启动java虚拟机,之后启动JVM等同于启动了一个应用程序,但实际上是启动了一个进程
。
该进程会自动启动一个 “主线程” 然后主线程去调用某个类的 main 方法
。
JVM的启动是多线程的吗?
JVM启动至少启动了垃圾回收线程
和主线程
所以是多线程的。
基础示例:
[TOC]
多线程Thread入门 1.简单概述 描述:什么是线程?
线程是程序执行的一条路径, 一个进程中可以包含多条线程
多线程并发执行可以提高程序的效率, 可以同时完成多项工作;(简单说利用了空闲时间)
多线程的应用场景:
迅雷开启多条线程一起下载
QQ同时和多个人一起视频
服务器同时处理多个客户端请求
多线程并行和并发的区别?
并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行(需要多核CPU
)
比如:我跟两个网友聊天,左手操作一个电脑跟甲聊同时右手用另一台电脑跟乙聊天这就叫并行。
并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
比如:用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。
注意: 一颗CPU在同一时刻只处理一个任务,只不过执行时间(执行效率高
)太短让我们误认为是同一时刻运行多个后台程序;
Java程序运行原理:
Java命令会启动java虚拟机,之后启动JVM等同于启动了一个应用程序,但实际上是启动了一个进程
。
该进程会自动启动一个 “主线程” 然后主线程去调用某个类的 main 方法
。
JVM的启动是多线程的吗?
JVM启动至少启动了垃圾回收线程
和主线程
所以是多线程的。
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.weiyigeek.Thread;public class Demo1_Thread { public static void main (String[] args) { for (int i = 0 ; i < 1000000 ; i++) new Demo(); for (int i = 0 ; i < 1000 ; i++) System.out.println("Master Thread 执行 " + i); } } class Demo { @Override protected void finalize () throws Throwable { System.out.println(this .getClass() + "类垃圾回收!" ); } }
2.多线程实现 描述:线程是程序中的执行线程,Java虚拟机允许应用程序并发的运行多个执行线程;
每一个线程都有一个优先级,高优先级线程的执行优于低优先级进程;
每一个线程可能会或可能不会被标记为一个守护进程。
在某个线程中运行的代码创建了一个新的Thread对象时,新线程的优先级被设置为创建线程的优先级,当且仅当创建线程是一个守护进程,新线程才是守护线程的。
基础语法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 java.lang.Object java.lang.Thread All Implemented Interfaces: Runnable public class Thread extends Object implements Runnable //构造方法 Thread (Runnable target ) //分配一个新的 Thread 对象。 Thread (String name ) //分配一个新的 Thread 对象。(参数是一个线程的名称)Thread (Runnable target , String name ) //分配一个新的 Thread 对象。 //类方法 void start () //导致该线程开始执行 java 虚拟机自动调用这个线程的run 方法。String getName () //返回此线程的名称。 void setName (String name ) //改变该线程的名称等于参数 name 。static Thread currentThread () //返回当前正在执行的线程对象的引用。(可以直接类.调用)
多线程实现的两种方式:
继承Thread类重写run方法
实现Runable接口重写run方法1 2 3 4 5 6 7 @FunctionalInterface public interface Runnable //一个类实现Runnable 可以运行run 方法,通过自身实例化一个对象并且传入Thread 实例作为目标参数 ;void run ()
实现Runnable的原理:
1,看Thread类的构造函数:传递了Runnable接口的引用
2,通过init()方法:找到传递的target给成员变量的target赋值
3,查看run方法:发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法
实际案例1:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package com.weiyigeek.Thread;public class Demo2_ThreadClass { public static void main (String[] args) { NewThread mt = new NewThread(); mt.start(); for (int i = 0 ; i < 1000 ; i++) System.out.println(new Thread().getName() + " - MasterThread" ); NewRunnable nr = new NewRunnable(); Thread t = new Thread(nr); t.start(); for (int i = 0 ; i < 1000 ; i++) System.out.println(new Thread().getName() + " - MasterThread-NewRunnable" ); } } class NewThread extends Thread { public void run () { for (int i = 0 ; i < 1000 ; i++) System.out.println(this .getName() + " - " + this .getClass()); } } class NewRunnable implements Runnable { @Override public void run () { for (int i = 0 ; i < 1000 ; i++) System.out.println(i + "-" + this .getClass()); } }
两种实现方式的区别总结: 1.查看源码的区别:
a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
2.两种实现方式优缺点:
继承Thread
好处是:可以直接使用Thread类中的方法,代码简单
弊端是:如果已经有了父类,就不能用这种方法(由于JAVA是单继承的特性)
实现Runnable接口
好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的(扩展性比较好)
弊端是:不能直接使用Thread中的方法需要先获取到Thread类线程对象后,才能得到Thread的方法代码复杂
3.在实际开发中根据业务需求来定,一般先用基础的Thread类如果满足不了就采用Runnable接口;
3.线程匿名内部类 描述:匿名内部类实现线程的两种方式更能方便的实现线程执行程序代码并且更加的简介,它也有两种方法
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.weiyigeek.Thread;public class Demo3_AnonmouseThread { public static void main (String[] args) { new Thread() { public void run () { for (int i = 0 ; i < 1000 ; i++) { System.out.println(this .getName() + " - Thread Anonymous Inner Class" ); } } }.start(); new Thread(new Runnable() { @Override public void run () { for (int i = 0 ; i < 1000 ; i++) System.out.println(new Thread().getName() + " - Runnable Anonymous Inner Class" ); } }).start(); } }
执行结果:1 2 3 4 Thread-0 - Thread Anonymous Inner Class Thread-2 - Runnable Anonymous Inner Class ..... Thread-1001 - Runnable Anonymous Inner Class
4.线程类常用方法 1.获取线程名字:通过getName()方法
获取线程对象的名字,我们前面的代码有所接触 2.设置线程名字:通过构造函数
可以传入String类型的名字,还可以通过setName(String)方法
可以设置线程对象的名字 3.获取当前线程:当前 currentThread()
主线程对象也可以获取,可以使用在Runable接口之中获取当前线程对象就能利用线程的方法了;
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.weiyigeek.Thread;public class Demo4_ThreadMethod { public static void main (String[] args) { demo1(); new Thread(new Runnable() { @Override public void run () { Thread t = Thread.currentThread(); t.setName("Slave-01" ); System.out.println(t.getName() + "#Runnable接口实现并获取当前线程对象-并调用其Thread类中方法" ); } }).start(); } public static void demo1 () { new Thread("Master" ) { public void run () { System.out.println( this .getName() + "#线程执行的代码块!" ); } }.start(); new Thread() { public void run () { this .setName("Master-01" ); System.out.println( this .getName() + "#线程执行的代码块!" ); } }.start(); Thread th = new Thread() { public void run () { System.out.println( this .getName() + "#线程执行的代码块!" ); } }; th.setName("Slave" ); th.start(); } }
执行结果:1 2 3 4 Master-01#线程执行的代码块! Slave#线程执行的代码块! Master#线程执行的代码块! Slave-01#Runnable接口实现并获取当前线程对象-并调用其Thread类中方法
5.线程休眠与守护 描述:在windows上一般采用毫秒级别(不支持纳秒值),但是Linux系统比起Windows更加合适处理纳秒级别的休眠sleep; 线程也需要等待和唤醒线程采用正常继续工作;
基础方法:
Thread.sleep(毫秒,纳秒) 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 1000 1000纳秒 = 1000000000
Thread setDaemon(true) 设置一个线程为守护线程,该线程不会单独执行当其他非守护线程都执行结束后,自动退出
,注意他会存在时间缓冲当非守护进程执行完毕后线程守护进程不会立即结束(比如QQ:聊天界面传文件)
基础实例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.weiyigeek.Thread;public class Demo5_SleepThread { public static void main (String[] args) throws InterruptedException { for (int i = 10 ; i > 0 ; i--) { Thread.sleep(1000 ); System.out.println("倒计时" +i+"s" ); } demo1(); } public static void demo1 () { new Thread("Thread1" ) { public void run () { for (int i = 0 ; i < 10 ; i++) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this .getName() + " - " + i); } } }.start(); new Thread("Thread2" ) { public void run () { for (int i = 0 ; i < 10 ; i++) { try { Thread.sleep(1000 ); } catch (Exception e) { e.printStackTrace(); } System.out.println(this .getName() + " - " + i); } } }.start(); } public static void demo2 () { Thread t1 = new Thread("Master" ) { public void run () { for (int i = 0 ; i < 2 ;i++) { try { Thread.sleep(100 ); } catch (Exception e) { e.printStackTrace(); } System.out.println(getName() + " - " + i); } } }; Thread t2 = new Thread("SetDeamon" ) { public void run () { for (int i = 0 ; i < 50 ;i++) { try { Thread.sleep(100 ); } catch (Exception e) { e.printStackTrace(); } System.out.println(getName() + " - " + i); } } }; t2.setDaemon(true ); t1.start(); t2.start(); } }
执行结果1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 倒计时2s 倒计时1s Thread1 - 0 Thread2 - 0 Thread1 - 1 Thread2 - 1 Thread1 - 2 Thread2 - 2 Thread1 - 3 Master - 0 SetDeamon - 0 Master - 1 SetDeamon - 1
6.线程添加与优先级 描述:添加线程给当前执行任务的CPU进行插队执行,我们也可以让程序让出CPU给其他线程执行; 还可以设置线程的优先级使其优先执行;
基础方法:
join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定的毫秒之后继续
Thread.yield() 给调度程序的一个提示,当前线程愿意得到它当前的处理器的使用,调度程序可以自由地忽略这个提示。
setPriority()设置线程的优先级范围(1~10)默认值是5,优先执行的线程;
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 package com.weiyigeek.Thread;public class Demo7_JointThread { public static void main (String[] args) { demo1(); MyThread mt1 = new MyThread("MasterYield" ); MyThread mt2 = new MyThread("SlaveYield" ); } public static void demo1 () { final Thread th1 = new Thread("Slave-Join" ) { public void run () { for (int i = 0 ; i < 20 ; i++) { try { Thread.sleep(20 ); } catch (Exception e) { e.printStackTrace(); } System.out.println(getName() + " - " + i); } } }; Thread th2 = new Thread("Master" ) { public void run () { for (int i = 0 ; i < 20 ; i++) { try { th1.join(30 ); Thread.sleep(20 ); } catch (Exception e) { e.printStackTrace(); } System.out.println(getName() + " - " + i); } } }; th1.setPriority(Thread.MIN_PRIORITY); th2.setPriority(Thread.MAX_PRIORITY); th1.start(); th2.start(); } } class MyThread extends Thread { public MyThread (String name) { super (name); } @Override public void run () { for (int i = 0 ; i <= 30 ; i++) { if (i%2 == 0 ) { Thread.yield(); } System.out.println(getName() + " - " + i); } } }
注意事项:
匿名内部类使用局部变量的时候必须采用final进行修饰的变量才可调用;
CPU在随机的切换正在执行的线程如果要让出线程执行时间需要采用yield()方法,但是实际上达不到效果只是理论上可以;
7.线程同步锁对象 描述:什么情况下需要同步?
当多线程并发有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作这时就需要同步机制.
如果两段代码是同步的那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码
.
实现同步互斥的机制方式:
同步代码块:使用synchronized关键字加上一个锁对象来定义一段代码这就叫同步代码块,多个同步代码块如果使用相同的锁对象那么他们就是同步的;
同步方法:使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 package com.weiyigeek.Thread;public class Demo8_Synchronized { public static void main (String[] args) { final Printer pp = new Printer(); new Thread("Sync-0" ) { public void run () { while (true ){ pp.p4(); } } }.start(); new Thread("Sync-1" ) { public void run () { while (true ){ pp.p41(); } } }.start(); } } class Printer { Syn d = new Syn(); public void p1 () { synchronized (d) { System.out.print("P1" ); System.out.print("Method" ); System.out.println("" ); } } public void p2 () { synchronized (d) { System.out.print("P2" ); System.out.print("Method" ); System.out.println("" ); } } public synchronized void p3 () { System.out.print("W" ); System.out.print("e" ); System.out.print("i" ); System.out.println(); } public void p31 () { synchronized (this ) { System.out.print("G" ); System.out.print("e" ); System.out.print("e" ); System.out.print("k" ); System.out.println(); } } public static synchronized void p4 () { System.out.print("W" ); System.out.print("e" ); System.out.print("i" ); System.out.print("\r\n" ); } public static void p41 () { synchronized (Printer.class) { System.out.print("G" ); System.out.print("e" ); System.out.print("e" ); System.out.print("k" ); System.out.print("\r\n" ); } } } class Syn { }
注意事项:
同步锁对象不能采用匿名对象,因为匿名对象不是同一个对象;
非静态的同步方法的锁对象是什么? 答:非静态同步函数的锁是this
静态的同步方法的锁对象是什么? 答:静态的同步函数的锁是字节码对象,原因由于静态方法优先于对象存在;
8.线程安全 描述:多线程并发操作同一数据时, 就有可能出现线程安全问题,所以使用同步技术可以解决这种问题, 把操作数据的代码进行同步
, 不要多个线程一起操作; 如果需要所有的对象的都共享一个数据,让一个类中变量编程一个静态变量
;否则每个线程对象都将执行run中代码并且类中变量是独立且不影响得;
死锁:多线程同步的时候, 如果同步代码嵌套使用相同锁就有可能出现死锁
,所以尽量不要嵌套使用1 2 3 4 5 6 7 synchronized (){ ... synchronized (){ ... } }
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.weiyigeek.Thread;public class Demo9_SyncTest { public static void main (String[] args) { NewTickets nr = new NewTickets(); new Thread(nr).start(); new Thread(nr).start(); new Thread(nr).start(); new Thread(nr).start(); } } class Tickets extends Thread { private static int ticket = 30 ; private static Object obj = new Object(); public void run () { while (true ) { synchronized (Tickets.class) { if (ticket <= 0 ) break ; try { Thread.sleep(10 ); } catch (Exception e) { e.printStackTrace(); } System.out.println( getName()+ " | 卖出第" + ticket-- + "张票" ); } } } } class NewTickets implements Runnable { private static int ticket = 30 ; private static Object obj = new Object(); @Override public void run () { while (true ) { synchronized (obj) { if (ticket <= 0 ) break ; try { Thread.sleep(10 ); } catch (Exception e) { e.printStackTrace(); } System.out.println( Thread.currentThread().getName()+ " | 卖出第" + ticket-- + "张票" ); } } } }
执行结果:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
示例2:死锁代码(哲学家就餐问题-在操作系统同步互斥中学习过)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private static String s1 = "筷子左" ;private static String s2 = "筷子右" ;public static void main (String[] args) { new Thread() { public void run () { while (true ) { synchronized (s1) { System.out.println(getName() + "...拿到" + s1 + "等待" + s2); synchronized (s2) { System.out.println(getName() + "...拿到" + s2 + "开吃" ); } } } } }.start(); new Thread() { public void run () { while (true ) { synchronized (s2) { System.out.println(getName() + "...拿到" + s2 + "等待" + s1); synchronized (s1) { System.out.println(getName() + "...拿到" + s1 + "开吃" ); } } } } }.start(); }
学习多线程以前的线程安全的类问题,如何判断线程是安全的? 答:通过ctrl+shift+t进行查找下面类是否使用了同步锁sychronized来修饰方法即(后面ctrl+o搜索具体的方法)看源码Vector,StringBuffer,Hashtable,
,可判断线程是不是安全的;1 2 3 4 5 6 public synchronized void addElement (E obj) { modCount++; ensureCapacityHelper(elementCount + 1 ); elementData[elementCount++] = obj; }
学习总结:
Vector是线程安全的,ArrayList是线程不安全的
StringBuffer是线程安全的,StringBuilder是线程不安全的
Hashtable是线程安全的,HashMap是线程不安全的
Collections.synchroinzed(xxx) 支持线程不安全的列表集合变成线程安全的;1 2 3 4 5 6 7 8 static <T> Collection<T> synchronizedCollection (Collection<T> c) static <T> List<T> synchronizedList (List<T> list) static <K,V> Map<K,V> synchronizedMap (Map<K,V> m) static <K,V> NavigableMap<K,V> synchronizedNavigableMap (NavigableMap<K,V> m) static <T> NavigableSet<T> synchronizedNavigableSet (NavigableSet<T> s) static <T> Set<T> synchronizedSet (Set<T> s) static <K,V> SortedMap<K,V> synchronizedSortedMap (SortedMap<K,V> m) static <T> SortedSet<T> synchronizedSortedSet (SortedSet<T> s)
多线程进阶 单例设计模式 单例设计模式思想:保证类在内存中只有一个对象,方便让大家指向同一个对象。
如何保证类在内存中只有一个对象呢?
(1)控制类的创建,不让其他类来创建本类的对象:private构造方法;
(2)在本类中定义一个本类的对象:Singleton s;
(3)提供公共的访问方式本类对象: public static Singleton getInstance(){return s};
(4)单例写法有三种,在基础示例中进行体现;
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package com.weiyigeek.Thread;public class Demo11_Singleton { public static void main (String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); if (s1 == s2) { s1.print(); } Singleton1 sl1 = Singleton1.getInstance(); Singleton1 sl2 = Singleton1.getInstance(); if (sl1 == sl2) { sl1.print(); } Singleton2 slt1 = Singleton2.s; Singleton2 slt2 = Singleton2.s; System.out.println(slt1 == slt2); } } class Singleton { private Singleton () {} private static Singleton s = new Singleton(); public static Singleton getInstance () { return s; } public static void print () { System.out.println("方法1" ); } } class Singleton1 { private Singleton1 () {}; private static Singleton1 s; public static Singleton1 getInstance () { if (s == null ) s = new Singleton1(); return s; } public static void print () { System.out.println("方式2" ); } } class Singleton2 { private Singleton2 () {}; public static final Singleton2 s = new Singleton2(); }
Runtime类 描述:Runtime类是一个单例类并且允许应用程序与环境中运行应用程序接口的一个实例,以后在写命令执行的shell的时候非常有用;
1 2 3 4 5 6 7 8 9 java.lang.Object java.lang.Runtime public class Runtime extends Object //类方法 static Runtime getRuntime () //返回与当前应用程序相关的java 运行时对象。 Process exec (String command ) //在一个单独的进程中执行指定的字符串命令。
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.weiyigeek.Thread;import java.io.IOException;public class Demo10_Runtime { public static void main (String[] args) throws IOException, InterruptedException { Runtime r = Runtime.getRuntime(); System.out.println("正在执行关机命令!" ); r.exec("shutdown -s -t 300" ); Thread.sleep(5000 ); r.exec("shutdown -a" ); System.out.println("已经取消关机!" ); } }
执行结果:
注意事项 :1.饿汉式与懒汉式之间的区别:
饿汉式:不管怎么样运行时候先建立一个对象,在实际开发中使用因为在多线程编程中它不会创建多个对象,它以空间换取时间;
懒汉式:在调用静态方法的时候需要进行判断然后创建对象,但是在开发中不建议使用,因为在多线程开发时候会出现问题导致创建多个对象,它以时间换空间;
Timer定时器 描述:简单的说定时器就是指定时间执行指定的某一任务;任务可能被安排指定时间为一次性执行,或定期重复执行。
语法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 java.lang.Object java.util.Timer public class Timer extends Object //类方法 void cancel () //终止此计时器,丢弃任何当前计划的任务。 int purge () //从这个计时器的任务队列中移除所有已取消的任务。 void schedule (TimerTask task , Date time ) //在指定的时间计划执行指定的任务。 void schedule (TimerTask task , Date firstTime , long period ) //计划重复固定延迟执行指定的任务,开始在指定的时间。 //由它的子类实现由定时器一次性或重复执行的任务。 (具体的要执行的任务-它是抽象类) public abstract class TimerTask extends Object implements Runnable
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.weiyigeek.Thread;import java.util.Date;import java.util.Timer;import java.util.TimerTask;public class Demo_TimerTask { public static void main (String[] args) throws InterruptedException { Timer t = new Timer(); t.schedule(new NewTask(), new Date(2019 -1900 ,9 ,19 ,11 ,21 ,30 ),3000 ); while (true ){ Thread.sleep(1000 ); System.out.println(new Date()); } } } class NewTask extends TimerTask { @Override public void run () { System.out.println("正在执行任务!" ); } }
执行结果:1 2 3 4 5 6 7 8 9 10 Sat Oct 19 11 :21 :29 CST 2019 正在执行任务! Sat Oct 19 11 :21 :30 CST 2019 Sat Oct 19 11 :21 :31 CST 2019 Sat Oct 19 11 :21 :32 CST 2019 正在执行任务! Sat Oct 19 11 :21 :33 CST 2019 Sat Oct 19 11 :21 :34 CST 2019 Sat Oct 19 11 :21 :35 CST 2019 正在执行任务!
进程间通信 1.什么时候需要通信?
多个线程并发执行时, 在默认情况下CPU是随机切换线程的
如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
2.怎么通信?
如果希望线程等待, 就调用wait()
如果希望唤醒等待的线程, 就调用notify();
这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
语法方法:1 2 3 4 5 6 7 8 9 10 11 12 java.lang.object void wait () void wait (long timeout) void wait (long timeout, int nanos) nofify () ; 方法是随机唤醒一个线程notifyAll()方法是唤醒所有线程
多个线程通信的问题(三个或三个以上间的线程通信):
JDK5之前无法唤醒指定的一个线程(而是随机唤醒):多个线程之间通信,需要使用notifyAll()通知所有线程,用while来反复判断条件;
简单的说假如有三个保安,第一个保安夜班结束了但它不知道剩下的两个保安谁值班,他就将两个保安都唤醒,其中一个保安说今天该我上班,而剩下的那个还是继续回到床上睡觉; (所以对线程程序来说他不知道谁满足条件她就把所有的线程都唤醒)
在JDK1.5之后有更改的解决方案互斥锁(后面描述)
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 package com.weiyigeek.Thread;public class Demo12_Waitnotify { public static void main (String[] args) { final Waitnofity wn = new Waitnofity(); new Thread() { public void run () { while (true ) { try { wn.print1(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run () { while (true ){ try { wn.print2(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run () { while (true ) { try { wn.print3(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } } class Waitnofity { private int flag = 1 ; public void print1 () throws InterruptedException { synchronized (this ) { while (flag != 1 ){ this .wait(); } System.out.print(Thread.currentThread().getName() + " - Print1()" ); System.out.println(" " ); flag=2 ; this .notifyAll(); } } public void print2 () throws InterruptedException { synchronized (this ) { while (flag != 2 ){ this .wait(); } System.out.print(Thread.currentThread().getName() + " - Print2()" ); System.out.println(" " ); flag=3 ; this .notifyAll(); } } public void print3 () throws InterruptedException { synchronized (this ) { while (flag != 3 ){ this .wait(); } System.out.print(Thread.currentThread().getName() + " - Print3()" ); System.out.println(" " ); flag=1 ; this .notifyAll(); } } } Thread-0 - Print1() Thread-1 - Print2() Thread-2 - Print3() Thread-0 - Print1() Thread-1 - Print2() Thread-2 - Print3() Thread-0 - Print1()
学习总结: 1,在同步代码块中用哪个对象锁就用哪个对象调用wait方法
2,为什么wait方法和notify方法定义在Object这类中?
答:因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中
3,sleep方法和wait方法的区别? 区别1:
sleep方法必须传入参数,参数就是时间时间到了自动醒来,
wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待不传入参数就是直接等待
区别2:
sleep方法在同步函数或同步代码块
中,不释放锁 - 睡着了也抱着锁睡(他不能被唤醒)
wait方法在同步函数或者同步代码块中释放锁 - 如果不释放锁,线程产生等待CPU也会一直在该段程序中耗着不能执行其他任务
;
互斥锁 描述:学过操作系统或者信号和通信的人应该了解过互斥锁(不多讲自己百度在信号同步那一章节的), 它是JDK1.5的新特性;
1.同步: 使用ReentrantLock类的lock() 获取锁和unlock()释放锁方法进行同步
2.通信
使用ReentrantLock类的newCondition()方法可以获取Condition对象
需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了
基础语法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class ReentrantLock extends Object implements Lock , Serializable //常用方法: void lock () void unlock () Condition newCondition () void await () void signal () void signalAll ()
基础实例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 package com.weiyigeek.Thread;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;public class Demo13_ReentranLock { public static void main (String[] args) { final Reentran rt = new Reentran(); new Thread() { @Override public void run () { while (true ) { try { rt.p1(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { @Override public void run () { while (true ) { try { rt.p2(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { @Override public void run () { while (true ){ try { rt.p3(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } } class Reentran { private ReentrantLock r = new ReentrantLock(); private Condition c1 = r.newCondition(); private Condition c2 = r.newCondition(); private Condition c3 = r.newCondition(); private int flag = 1 ; public void p1 () throws InterruptedException { r.lock(); if (flag != 1 ) c1.await(); System.out.println("Wei" ); flag = 2 ; c2.signal(); r.unlock(); } public void p2 () throws InterruptedException { r.lock(); if (flag != 2 ) c2.await(); System.out.println("Geek" ); flag = 3 ; c3.signal(); r.unlock(); } public void p3 () throws InterruptedException { r.lock(); if (flag != 3 ) c3.await(); System.out.println("Hacker" ); flag = 1 ; c1.signal(); r.unlock(); } }
线程组 描述:为什么要存在组? 答:那是为了方便管理和维护,同样JAVA中线程中也有自己的组;
线程组概述: Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
。默认情况下所有的线程都属于主线程组
。
基础语法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ThreadGroup extends Object implements Thread .UncaughtExceptionHandler //构造方法 ThreadGroup (String name ) //构建一个新的线程组,并给其赋值名字 Thread (ThreadGroup ?group , Runnable ?target , String ?name ) //设置整组的优先级或者守护线程//常用方法: ThreadGroup getThreadGroup () String getName () ThreadGroup getParent () String toString ()
线程的生命周期:
新建:创建线程;
就绪:线程对象已经启动了,但是还没回去到CPU执行权;
运行:获取到了CPU的执行权;
阻塞:没有CPU的执行权回到就绪;
死亡:代码运行完毕线程消亡;
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.weiyigeek.Thread;public class Demo13_ThreadGroup { public static void main (String[] args) { System.out.println("默认线程组: " +Thread.currentThread().getThreadGroup().getName()); Mrunnable mr = new Mrunnable(); Thread t1 = new Thread(mr,"One" ); Thread t2 = new Thread(mr,"Two" ); ThreadGroup tg1 = t1.getThreadGroup(); ThreadGroup tg2 = t2.getThreadGroup(); System.out.println("+ 默认都是主线程组:" + tg1.getName()); System.out.println("+ 默认都是主线程组:" + tg2.getName()); ThreadGroup tg = new ThreadGroup("NewThreadGroup" ); Thread t3 = new Thread(tg,mr,"Group" ); ThreadGroup tg3 = t3.getThreadGroup(); System.out.println("+ 设置的线程组:" + tg3.getName()); tg.setDaemon(true ); } } class Mrunnable implements Runnable { @Override public void run () { for (int i = 0 ; i < 1000 ; i++) { System.out.println(Thread.currentThread().getName() + "...." + i); } } }
执行结果:1 2 3 4 默认线程组: main + 默认都是主线程组:main + 默认都是主线程组:main + 设置的线程组:NewThreadGroup
线程池 概述:程序启动一个新线程成本是比较高的(经过五种状态),因为它涉及到要与操作系统进行交互;而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前我们必须手动实现自己的线程池
从JDK5开始Java内置支持线程池
内置线程池的使用JDK5新增了一个Executors工厂类来产生线程池
,有如下几个方法:1 2 3 4 5 6 public class Executors extends Object * public static ExecutorService newFixedThreadPool (int nThreads ) //线程池中可存放线程的数量使用固定数量的线程操作了共享无界队列。 * public static ExecutorService newSingleThreadExecutor () //创建一个执行器,使用一个单一的工作线程操作关闭一个无限的队列。
这些方法的返回值是ExecutorService对象,该对象表示一个线程池可以执行Runnable对象
或者Callable对象代表的线程。 它提供了如下方法:1 2 3 4 5 6 7 8 public interface ExecutorService extends Executor //常用方法: void shutdown () <T> Future<T> submit (Callable<T> task) Future<?> submit (Runnable task) <T> Future<T> submit (Runnable task, T result)
基础示例:多线程程序实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package com.weiyigeek.Thread;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class Demo14_Executor { public static void main (String[] args) throws InterruptedException, ExecutionException { ExecutorService pool = Executors.newFixedThreadPool(2 ); pool.submit(new Mrunnable()); pool.submit(new Mrunnable()); pool.shutdown(); ExecutorService pool1 = Executors.newFixedThreadPool(2 ); Future<Integer> f1 = pool1.submit(new MyCallable(100 )); Future<Integer> f2 = pool1.submit(new MyCallable(50 )); System.out.println("前100之和:" + f1.get()); System.out.println("前50之和:" + f2.get()); pool1.shutdown(); } } class MyCallable implements Callable <Integer > { private int num; public MyCallable (int num) { this .num = num; } @Override public Integer call () throws Exception { int sum = 0 ; while (num >= 0 ){ sum += num--; } return sum; } }
总结: 多线程程序实现的方式3的好处和弊端好处:
弊端: