生命周期
jdk里面的状态:
业务逻辑里面的状态:
- 新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值
- 就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
- 运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
- 阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态
线程通信
线程的生产者/消费者模式,生产一个消费一个。
生产者:
public class Producter implements Runnable { private ShareResource shareResource = null; public Producter(ShareResource shareResource) { this.shareResource = shareResource; } public void run() { for (int i = 0; i < 10; i++) { if (i % 2 == 0) { shareResource.push("张三", 3); } else { shareResource.push("李四", 4); } } } }
消费者:
public class Customer implements Runnable { private ShareResource shareResource = null; public Customer(ShareResource shareResource) { this.shareResource = shareResource; } public void run() { for (int i = 0; i < 10; i++) { shareResource.pull(); } } }
测试:
public class Liang { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); new Thread(new Producter(shareResource)).start(); new Thread(new Customer(shareResource)).start(); } }
1.wait和notify方法
wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
这三个方法最终调用的都是jvm级的native方法。
wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。
public class ShareResource { private String name; private Integer age; private boolean isEmpty = true; synchronized public void push(String name, Integer age) { try { while (!isEmpty) { this.wait(); } try { this.name = name; Thread.sleep(10); this.age = age; isEmpty = false; this.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void pull() { try { while (isEmpty) { this.wait(); } Thread.sleep(10); System.out.println(name + " " + age); isEmpty = true; this.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } }
2.Lock和Condition接口
jdk1.5引入了Java.util.concurrent.locks包,并提供了Lock和Condition接口及实现类。
Lock 实现提供了比使用 synchronized方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
Condition 将 Object 监视器方法(wait、notify 和notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待set(wait-set)。其中,Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object监视器方法的使用。
这两个接口将“锁对象”和“监视器对象”分开,这样,一个“锁”就可以关联多个“监视器对象”。也就能实现生产者线程固定唤醒消费者线程,消费者线程固定唤醒生产者线程。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ShareResource { private String name; private Integer age; private boolean isEmpty = true; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void push(String name, Integer age) { lock.lock(); try { while (!isEmpty) { condition.await(); } this.name = name; Thread.sleep(10); this.age = age; isEmpty = false; condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void pull() { lock.lock(); try { while (isEmpty) { condition.await(); } Thread.sleep(10); System.out.println(name + " " + age); isEmpty = true; condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
结果:
张三 3
李四 4
张三 3
李四 4
张三 3
李四 4
张三 3
李四 4
张三 3
李四 4
线程睡眠
sleep方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”(常用于模拟网络延迟),线程睡眠的过程中,如果是在synchronized线程同步内,是持有锁(监视器对象)的,也就是说,线程是关门睡觉的,别的线程进不来。
线程睡眠的原因:线程执行太快,或者需要强制进入下一轮,因为Java规范不保证合理的轮换。
代码
package liang; import java.util.Date; class SleepThread extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { if (i==2){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(new Date().toLocaleString()); } } } public class Main { public static void main(String[] args) throws InterruptedException { System.out.println("--------------------------------------start"); SleepThread t = new SleepThread(); t.start(); System.out.println("--------------------------------------end"); } }
联合线程
一个线程在占用CPU资源期间,可以让其他线程调用join()和本线程联合。线程的join方法表示一个线程等待另一个线程完成后才执行。join方法被调用之后,线程对象处于阻塞状态。有人也把这种方式称为联合线程,就是说把当前线程和当前线程所在的线程联合成一个线程。哪个线程调用了join方法,那么这个线程就要先执行完才会执行其他的线程.
代码:
package liang; class JoinThread extends Thread { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("join---" + i ); } } } public class Main { public static void main(String[] args) throws InterruptedException { System.out.println("--------------------------------------start"); JoinThread joinThread = new JoinThread(); for (int i = 0; i < 50; i++) { System.out.println("main---" + i ); if (i==9){ joinThread.start(); } if (i==39){ joinThread.join(); } } System.out.println("--------------------------------------end"); } }
结果
--------------------------------------start
main---0
main---1
main---2
main---3
main---4
main---5
main---6
main---7
main---8
main---9
main---10
main---11
main---12
main---13
main---14
main---15
main---16
main---17
main---18
main---19
main---20
main---21
main---22
main---23
main---24
main---25
main---26
main---27
main---28
main---29
main---30
main---31
main---32
main---33
main---34
main---35
main---36
main---37
main---38
main---39
join---0
join---1
join---2
join---3
join---4
join---5
join---6
join---7
join---8
join---9
join---10
join---11
join---12
join---13
join---14
join---15
join---16
join---17
join---18
join---19
join---20
join---21
join---22
join---23
join---24
join---25
join---26
join---27
join---28
join---29
join---30
join---31
join---32
join---33
join---34
join---35
join---36
join---37
join---38
join---39
join---40
join---41
join---42
join---43
join---44
join---45
join---46
join---47
join---48
join---49
main---40
main---41
main---42
main---43
main---44
main---45
main---46
main---47
main---48
main---49
--------------------------------------end
守护线程
JVM中存在两种线程:用户线程(User Thread)和守护线程(Daemon Thread)。
所谓的守护线程,是指用户程序在运行的时候后台提供的一种通用服务的线程,比如用于垃圾回收(gc最典型)的
垃圾回收线程。这类线程并不是用户线程不可或缺的部分,只是用于提供服务的"服务线程"。
基于这个特点,当虚拟机中的用户线程全部退出运行时,守护线程没有服务的对象后,JVM也就退出了(任何一个守护线程都是整个JVM中所有非守护线程的保姆);即,当线程中只剩下守护线程时JVM就会退出,反之还有任意一个用户线程在,JVM都不会退出。
注意点:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
代码:
package liang; class DaemonThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Daemon---" + i); } } } public class Main { public static void main(String[] args) throws Exception { System.out.println("--------------------------------------start"); System.out.println(Thread.currentThread().isDaemon());//主线程是否后台线程 for (int i = 0; i < 10; i++) { System.out.println("main---" + i); if (i == 5) { DaemonThread daemonThread = new DaemonThread(); daemonThread.setDaemon(false);//子线程是否前后台线程 守护线程 daemonThread.start(); } } System.out.println("--------------------------------------end"); } }
线程优先级
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。
线程有两种调度模型:
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度(Java使用的): 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
public final int getPriority():返回线程对象的优先级
public final void setPriority(int newPriority):更改线程的优先级。
注意:
线程默认优先级是5。
线程优先级的范围是:1-10。
线程优先级高仅仅表示线程获取的 CPU时间片的几率高(理论上,优先级高的线程比优先级低的线程获得更多的CPU时间)。
IllegalArgumentException:非法参数异常。
抛出的异常表明向方法传递了一个不合法或不正确的参数。就是设置的优先级超出了线程优先级的范围。
代码:
package liang; class HighThread extends Thread { public HighThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "---" + i); } } } class MidThread extends Thread { public MidThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "---" + i); } } } class LowThread extends Thread { public LowThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "---" + i); } } } public class Main { public static void main(String[] args) throws Exception { //优先级是cpu调度机会的次数 System.out.println("--------------------------------------start"); Thread.currentThread().setPriority(2);//设置主线程优先级 System.out.println("主线程优先级:" + Thread.currentThread().getPriority()); HighThread highThread = new HighThread("高优先级"); MidThread midThread = new MidThread("中优先级"); LowThread lowThread = new LowThread("低优先级"); highThread.setPriority(Thread.MIN_PRIORITY);//优先级为1 midThread.setPriority(Thread.NORM_PRIORITY);//优先级为5 lowThread.setPriority(Thread.MAX_PRIORITY);//优先级为10 highThread.start(); midThread.start(); lowThread.start(); System.out.println("--------------------------------------end"); } }
线程礼让
Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果,已经不提倡使用了。
代码
package liang; class YieldThread extends Thread { public YieldThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + "---" + i); if (i == 10) { try { // Thread.sleep(1000); Thread.yield(); } catch (Exception e) { e.printStackTrace(); } } } } } public class Main { public static void main(String[] args) throws Exception { //优先级是cpu调度机会的次数 System.out.println("--------------------------------------start"); YieldThread max = new YieldThread("高优先级"); YieldThread min = new YieldThread("低优先级"); /* max.setPriority(Thread.MIN_PRIORITY);//优先级为1 min.setPriority(Thread.MAX_PRIORITY);//优先级为10*/ min.start(); max.start(); System.out.println("--------------------------------------end"); } }
结果
--------------------------------------start
--------------------------------------end
低优先级---0
低优先级---1
低优先级---2
低优先级---3
低优先级---4
低优先级---5
低优先级---6
低优先级---7
低优先级---8
低优先级---9
低优先级---10
高优先级---0
高优先级---1
高优先级---2
高优先级---3
高优先级---4
高优先级---5
高优先级---6
高优先级---7
高优先级---8
高优先级---9
高优先级---10
低优先级---11
低优先级---12
低优先级---13
低优先级---14
低优先级---15
低优先级---16
低优先级---17
高优先级---11
高优先级---12
高优先级---13
高优先级---14
高优先级---15
高优先级---16
高优先级---17
高优先级---18
高优先级---19
低优先级---18
低优先级---19
定时器和线程组
Timer是一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度所拥有的TimerTasks(自JDK5之后,可以用ScheduledThreadPoolExecutor来替代Timer)。
TimerTask是一个抽象类,它的子类由 Timer 安排为一次执行或重复执行的任务。实际上就是一个拥有run方法的类,需要定时执行的代码放到run方法体内。
ThreadGroup线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。
线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。
允许线程访问有关自己的线程组的信息,但是不允许它访问有关其线程组的父线程组或其他任何线程组的信息。
代码:
import java.util.Date; import java.util.Timer; import java.util.TimerTask; class TimerThread extends TimerTask { public void run() { System.out.println(new Date().toLocaleString()); } } public class Main { public static void main(String[] args) throws Exception { ThreadGroup tg = Thread.currentThread().getThreadGroup(); System.out.println(tg.getName()); System.out.println("--------------------------------------start"); Timer timer = new Timer(); timer.schedule(new TimerThread(), 1000, 1000); System.out.println("--------------------------------------end"); } }
结果:
main
--------------------------------------start
--------------------------------------end
Jul 26, 2017 3:22:18 PM
Jul 26, 2017 3:22:19 PM
Jul 26, 2017 3:22:20 PM
Jul 26, 2017 3:22:21 PM
Jul 26, 2017 3:22:22 PM