线程的创建 继承Thread类
编写程序,开启一个线程,该线程每隔1秒。 在控制台输出“喵喵, 我是小猫咪”
对上题改进:当输出8 次喵喵,我是小猫咪,结束该线程
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 package com.threaduse;public class Thread01 { public static void main (String[] args) { Cat cat = new Cat (); cat.start(); System.out.println(Thread.currentThread().getName()); for (int i = 0 ; i < 8 ; i++) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主线程执行" ); } } } class Cat extends Thread { @Override public void run () { int time = 0 ; while (true ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("喵喵, 我是小猫咪" + (++time) + " " + Thread.currentThread().getName()); if (time == 8 ) { break ; } } } }
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 main 主线程执行 喵喵, 我是小猫咪1 Thread-0 主线程执行 喵喵, 我是小猫咪2 Thread-0 主线程执行 喵喵, 我是小猫咪3 Thread-0 喵喵, 我是小猫咪4 Thread-0 主线程执行 主线程执行 喵喵, 我是小猫咪5 Thread-0 主线程执行 喵喵, 我是小猫咪6 Thread-0 主线程执行 喵喵, 我是小猫咪7 Thread-0 主线程执行 喵喵, 我是小猫咪8 Thread-0 Process finished with exit code 0
cat.start()
方法调用了ublic synchronized void start()
方法,其中的start0()
方法是本地方法由JVM调用。
真正实现多线程的是start0()
。
start()
调用start0()
方法后,该线程不一定会立刻执行,只是将线程变成了可运行状态。什么时候执行,取决于CPU什么时候调度。
实现Runnable接口 java是单继承的,所以某个类在继承了其他父类的情况下需要实现多线程可以继承接口。
编写程序,该程序每隔一秒输出“hi”,输出10次自动退出。使用Runnable接口实现。
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 package com.threaduse;public class Thread02 { public static void main (String[] args) { Dog dog = new Dog (); Thread thread = new Thread (dog); thread.start(); } } class Dog implements Runnable { @Override public void run () { int count = 0 ; while (true ){ try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hi " +(++count)+"" +Thread.currentThread().getName()); if (count==10 ){ break ; } } } }
这种写法的启动方式和第一种不一样,Thread用了代理模式。
1 2 3 Dog dog = new Dog (); Thread thread = new Thread (dog); thread.start();
用代码模拟一下这种模式,ThreadProxy
模拟简化版的Thread
就是dog
通过Thread
代理启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class ThreadProxy implements Runnable { private Runnable target = null ; public ThreadProxy (Runnable target) { this .target = target; } public void start () { start0(); } public void start0 () { run(); } @Override public void run () { if (target != null ) { target.run(); } } }
多个子线程案例 编写一个程序创建两个线程,一个线程每隔1秒输出”hello,world” 10次退出。一个线程每隔1秒输出”hi” ,输出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 package com.threaduse;public class Thread03 { public static void main (String[] args) { T1 t1 = new T1 (); Thread thread1 = new Thread (t1); T2 t2 = new T2 (); Thread thread2 = new Thread (t2); thread1.start(); thread2.start(); } } class T1 implements Runnable { @Override public void run () { int count = 0 ; while (true ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello world" ); if (++count == 10 ) { break ; } } } } class T2 implements Runnable { @Override public void run () { int count = 0 ; while (true ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hi" ); if (++count == 5 ) { break ; } } } }
通知线程退出 可以在主线程中修改循环条件控制子线程
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 package com.exit_;public class ThreadExit_ { public static void main (String[] args) { T t = new T (); t.start(); try { Thread.sleep(5 *1000 ); } catch (InterruptedException e) { e.printStackTrace(); } t.setLoop(false ); } } class T extends Thread { private boolean loop = true ; @Override public void run () { int count = 0 ; while (loop) { System.out.println(++count); } } public void setLoop (boolean loop) { this .loop = loop; } }
线程常用方法
序号
方法描述
1
public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2
public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3
public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4
public final void setPriority(int priority) 更改线程的优先级。
5
public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6
public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7
public void interrupt() 中断线程。
8
public final boolean isAlive() 测试线程是否处于活动状态。
线程中断 演示interrupt()
函数,可以中断线程休眠。捕获异常后,执行catch内语句。
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.method;public class ThreadMethod01 { public static void main (String[] args) { T t = new T (); t.setName("小明" ); t.setPriority(Thread.MIN_PRIORITY); t.start(); for (int i=0 ;i<5 ;i++){ try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hi " +i); } t.interrupt(); } } class T extends Thread { @Override public void run () { while (true ) { for (int i = 0 ; i < 100 ; i++) { System.out.println(Thread.currentThread().getName() + "吃包子" + i); } try { System.out.println(Thread.currentThread().getName() + "休眠中" ); Thread.sleep(20000 ); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "被interrupt了" ); } } } }
线程插队 yield()
:线程的礼让。让出CPU给其他线程,但礼让时间不确定,所以不一定礼让成功。
join()
:线程的插队。插队的线程一旦插队成功,就先执行完插入的线程的所有任务。
案例: main线程创建一个子线程,每隔1秒吃一个包子,吃10次,主线程隔1秒吃一个包子 ,吃10次。
要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续。
用join()
实现
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.method;public class ThreadMethod02 { public static void main (String[] args) throws InterruptedException { T2 t2 = new T2 (); t2.start(); for (int i = 1 ; i <= 10 ; i++) { Thread.sleep(1000 ); System.out.println("主线程吃了" + i + "个包子" ); if (i == 5 ) { System.out.println("主线程让子线程先吃" ); t2.join(); System.out.println("子线程吃完😋" ); } } } } class T2 extends Thread { @Override public void run () { for (int i = 1 ; i <= 10 ; i++) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程吃了" + i + "个包子" ); } } }
输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 主线程吃了1 个包子 子线程吃了1 个包子 主线程吃了2 个包子 子线程吃了2 个包子 子线程吃了3 个包子 主线程吃了3 个包子 主线程吃了4 个包子 子线程吃了4 个包子 子线程吃了5 个包子 主线程吃了5 个包子 主线程让子线程先吃 子线程吃了6 个包子 子线程吃了7 个包子 子线程吃了8 个包子 子线程吃了9 个包子 子线程吃了10 个包子 子线程吃完😋 主线程吃了6 个包子 主线程吃了7 个包子 主线程吃了8 个包子 主线程吃了9 个包子 主线程吃了10 个包子 Process finished with exit code 0
用yield
1 2 3 4 5 6 if (i == 5 ) { System.out.println("主线程让子线程先吃" ); Thread.yield (); System.out.println("子线程吃完😋" ); }
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 主线程吃了1 个包子 子线程吃了1 个包子 主线程吃了2 个包子 子线程吃了2 个包子 主线程吃了3 个包子 子线程吃了3 个包子 主线程吃了4 个包子 子线程吃了4 个包子 主线程吃了5 个包子 主线程让子线程先吃 子线程吃了5 个包子 子线程吃完😋 主线程吃了6 个包子 子线程吃了6 个包子 子线程吃了7 个包子 主线程吃了7 个包子 子线程吃了8 个包子 主线程吃了8 个包子 主线程吃了9 个包子 子线程吃了9 个包子 主线程吃了10 个包子 子线程吃了10 个包子 Process finished with exit code 0
不一定会成功
守护线程 一般为工作线程(用户线程)服务,当所有的用户线程结束,守护线程自动结束
垃圾回收机制是常见的守护线程
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 package com.method;public class ThreadMethod04 { public static void main (String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread (); myDaemonThread.setDaemon(true ); myDaemonThread.start(); for (int i = 0 ; i < 10 ; i++) { System.out.println("主线程" ); Thread.sleep(1000 ); } } } class MyDaemonThread extends Thread { @Override public void run () { for (; ; ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程" ); } } }
这段代码实现了主线程退出后,子线程自动退出
myDaemonThread.setDaemon(true);
,将线程变成守护进程
线程的生命周期 java.lang.Thread.State
枚举类中定义了六种线程的状态,可以调用线程Thread中的getState()
方法获取当前线程的状态 。
线程状态
解释
NEW
尚未启动的线程状态,即线程创建,还未调用start方法
RUNNABLE
就绪状态 (调用start,等待调度)+正在运行
BLOCKED
等待监视器锁 时,陷入阻塞状态
WAITING
等待状态的线程正在等待 另一线程执行特定的操作(如notify)
TIMED_WAITING
具有指定等待时间 的等待状态
TERMINATED
线程完成执行,终止状态
线程同步 在多线程编程中,一些敏感数据不允许被多个线程同时访问。采用同步访问技术,保证数据在任何时刻,最多只有一个线程访问,保证数据的完整性。
使用synchronized
关键字上锁。
售票系统 模拟三个售票窗口,售票100。分别使用继承Thread和实现Runnable方法,并分析有什么问题。
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.syn;public class SellTicket { public static void main (String[] args) { Ticket ticket = new Ticket (); new Thread (ticket).start(); new Thread (ticket).start(); new Thread (ticket).start(); } } class Ticket implements Runnable { private static int ticketNum = 100 ; private boolean loop = true ; public void sell () { if (ticketNum <= 0 ) { System.out.println("售票结束" ); loop = false ; return ; } try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售出一张,当前票数" + +(--ticketNum)); } @Override public void run () { while (loop) { sell(); } } }
1 2 3 4 5 6 7 8 9 Thread-0售出一张,当前票数3 Thread-1售出一张,当前票数1 Thread-0售出一张,当前票数1 Thread-2售出一张,当前票数2 Thread-2售出一张,当前票数-1 Thread-0售出一张,当前票数0 售票结束 Thread-1售出一张,当前票数0 售票结束
不加synchronized
关键字会出现多卖和重复卖的情况。给sell()
方法加上synchronized
关键字就可以解决。
改进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public synchronized void sell () { } public void sell () { synchronized (this ){ ... } }
运行结果,问题解决。
1 2 3 4 5 6 7 Thread-2 售出一张,当前票数3 Thread-2 售出一张,当前票数2 Thread-2 售出一张,当前票数1 Thread-2 售出一张,当前票数0 售票结束 售票结束 售票结束
1 2 3 4 5 6 7 8 9 10 11 1. public synchronized static void m1 () {} 锁是加在方法所在的类上的2. 如果在静态方法中实现一个同步代码块:class A implenments Runnable{ public synchronized static void m () { synchronized (A.class){ System.out.println("m" ); } } }
线程死锁 死锁是多个线程由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。如果无外力作用,永远无法推进下去。
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 package com.syn;public class DeadLock { public static void main (String[] args) { DeadLockDemo A = new DeadLockDemo (true ); A.setName("A 线程" ); DeadLockDemo B = new DeadLockDemo (false ); B.setName("B 线程" ); A.start(); B.start(); } } class DeadLockDemo extends Thread { static Object o1 = new Object (); static Object o2 = new Object (); boolean flag; public DeadLockDemo (boolean flag) { this .flag = flag; } @Override public void run () { if (flag) { synchronized (o1) { System.out.println(Thread.currentThread().getName() + " 进入 1" ); synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入 2" ); } } } else { synchronized (o2) { System.out.println(Thread.currentThread().getName() + " 进入 3" ); synchronized (o1) { System.out.println(Thread.currentThread().getName() + " 进入 4" ); } } } } }
这段代码模拟了一个死锁,两个线程只能得到一个锁,程序会一直卡住
下面操作会释放锁:
当前线程的同步方法、同步代码块执行结束
当前线程在同步代码块、同步方法中遇到break、return
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
当前线程在同步代码块、同步方法中执行了对象的wait()
方法,当前线程暂停并释放锁
下面操作不会释放锁:
当前线程同步代码块、同步方法中调用Thread.sleep(),Thread.yield()
方法暂停线程不会释放锁
线程执行同步代码块时,其他 线程调用了该线程的suspend()
方法将改线程挂起不会释放锁
应尽量避免使用suspend()
和resume()
来kong’zhi