1.什么是线程
线程是一个程序内部的顺序控制流。
线程和进程:
每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大。
线程: 轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
多进程: 在操作系统中能同时运行多个任务(程序)
多线程:在同一应用程序中有多个顺序流同时执行
2.线程的概念模型
1. 虚拟的CPU,由java.lang.Thread类封装和虚拟;
2. CPU所执行的代码,传递给Thread类对象;
3. CPU所处理的数据,传递给Thread类对象。
Java的线程是通过java.lang.Thread类来实现的。
每个线程都是通过某个特定Thread对象所对应的,方法run( )来完成其操作的,方法run( )称为线程体。
3.创建线程的两种方式的比较
一. 使用Runnable接口创建线程:
可以将CPU(Tread 类)、代码和数据(Runnable接口 run 方法)分开,形成清晰的模型;线程体run()方法所在的类还可以从其他类继承一些有用的属性或方法;
并有利于保持程序风格的一致性。
public class TestTread1{ public static void main(String args[]) { Runner1 r = new Runner1(); Thread t = new Thread(r); t.start(); } } class Runner1 implements Runnable { public void run() { for(int i=0; i<30; i++) { System.out.println("No. " + i); } } }
二. 直接继承Thread类创建线程:
Thread子类无法再从其他类继承,编写简单,run()方法的当前对象就是线程对象,可直接操纵。
public class TestTread2{ public static void main(String args[]){ Thread t = new Runner3(); t.start(); } } class Runner3 extends Thread { public void run() { for(int i=0; i<30; i++) { System.out.println("No. " + i); } } }
4.多线程
Java 中 引 入 线 程 机 制 的 目 的 在 于 实 现 多 线 程 (Multi-Thread),多线程之间可以共享代码和数据。
public class TestTread3{ public static void main(String args[]) { Runner2 r = new Runner2(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); } } class Runner2 implements Runnable { public void run() { for(int i=0; i<10; i++) { String s = Thread.currentThread().getName(); System.out.println(s + ": " + i); } } }
结果:
Thread-0: 0 Thread-0: 1 Thread-0: 2 Thread-0: 3 Thread-0: 4 Thread-0: 5 Thread-0: 6 Thread-0: 7 Thread-0: 8 Thread-0: 9 Thread-1: 0 Thread-1: 1 Thread-1: 2 Thread-1: 3 Thread-1: 4 Thread-1: 5 Thread-1: 6 Thread-1: 7 Thread-1: 8 Thread-1: 9
5.后台线程
相关基本概念:
后台处理(Background Processing):让某些程序的运行让步给其他的优先级高的程序
后台线程(Background Thread / Daemon Thread):以后台的方式(往往是服务线程)运行的线程,例如定时器线程
用户线程(User Thread):完成用户指定的任务的线程
主线程(Main Thread):它就是一种主线程
子线程(Sub Thread):在线程中创建的线程就是子线程
Thread类提供的相关方法:
public final boolean isDaemon():判断某个线程是不是一个后台线程
public final void setDaemon(Boolean on):设置某个线程为一个后台线程
举例:
package v512.chap16; public class TestDaemonThread { public static void main(String args[]){ Thread t1 = new MyRunner(10); t1.setName("用户线程t1"); t1.start(); Thread t2 = new MyRunner(50); t2.setDaemon(true); t2.setName("后台线程t2"); t2.start(); for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName() + ": " + i); } System.out.println("主线程结束!"); } } class MyRunner extends Thread { private int n; public MyRunner(int n){ this.n = n; } public void run() { for(int i=0; i<n; i++) { System.out.println(this.getName() + ": " + i); } System.out.println(this.getName() + "结束!"); } }
结果:
用户线程t1: 0 用户线程t1: 1 用户线程t1: 2 用户线程t1: 3 用户线程t1: 4 用户线程t1: 5 用户线程t1: 6 用户线程t1: 7 用户线程t1: 8 用户线程t1: 9 用户线程t1结束! main: 0 main: 1 main: 2 main: 3 main: 4 main: 5 main: 6 main: 7 main: 8 main: 9 主线程结束! 后台线程t2: 0 后台线程t2: 1 后台线程t2: 2 后台线程t2: 3 后台线程t2: 4 后台线程t2: 5 后台线程t2: 6 后台线程t2: 7 后台线程t2: 8 后台线程t2: 9 后台线程t2: 10 后台线程t2: 11 后台线程t2: 12 后台线程t2: 13 后台线程t2: 14 后台线程t2: 15 后台线程t2: 16 后台线程t2: 17 后台线程t2: 18 后台线程t2: 19 后台线程t2: 20 后台线程t2: 21 后台线程t2: 22 后台线程t2: 23 后台线程t2: 24 后台线程t2: 25 后台线程t2: 26 后台线程t2: 27 后台线程t2: 28 后台线程t2: 29 后台线程t2: 30 后台线程t2: 31 后台线程t2: 32 后台线程t2: 33 后台线程t2: 34 后台线程t2: 35 后台线程t2: 36 后台线程t2: 37 后台线程t2: 38 后台线程t2: 39 后台线程t2: 40 后台线程t2: 41 后台线程t2: 42 后台线程t2: 43 后台线程t2: 44 后台线程t2: 45 后台线程t2: 46 后台线程t2: 47 后台线程t2: 48 后台线程t2: 49 后台线程t2结束!
6.GUI线程
GUI程序运行过程中系统会自动创建若干GUI线程
常见GUI线程:
AWT-Windows线程:(windows 系统中)后台线程
AWT-EventQueue-n线程
AWT-Shutdown线程:负责关闭窗体中的抽象窗口工具
DestroyJavaVM线程:不是GUI线程,它是当main线程结束之后系统自动创建的,用于等到所有的线程都死掉了之后销毁jvm
实例:
package v512.chap16; import java.awt.*; import java.awt.event.*; public class TestGUIThread { public static void main(String args[]) throws Exception{ Frame f = new Frame(); Button b = new Button("Press Me!"); MyMonitor mm = new MyMonitor(); b.addActionListener(mm); f.addWindowListener(mm); f.add(b,"Center"); f.setSize(100,60); f.setVisible(true); MyThreadViewer.view(); } } class MyMonitor extends WindowAdapter implements ActionListener{ public void actionPerformed(ActionEvent e){ MyThreadViewer.view(); } } class MyThreadViewer{ public static void view(){ Thread current = Thread.currentThread(); System.out.println("当前线程名称: " + current.getName()); int total = Thread.activeCount(); System.out.println("活动线程总数: " + total + "个"); Thread[] threads = new Thread[total]; current.enumerate(threads); for(Thread t : threads){ String role = t.isDaemon()?"后台线程 ":"用户线程 "; System.out.println(" -" + role + t.getName()); } System.out.println("----------------------------------"); } }
运行之后,在点击一下 button
输出结果:
当前线程名称: main 活动线程总数: 4个 -用户线程 main -用户线程 AWT-Shutdown -后台线程 AWT-Windows -用户线程 AWT-EventQueue-0 ---------------------------------- 当前线程名称: AWT-EventQueue-0 活动线程总数: 4个 -用户线程 AWT-Shutdown -后台线程 AWT-Windows -用户线程 AWT-EventQueue-0 -用户线程 DestroyJavaVM ----------------------------------
7.线程的生命周期
新建状态
就绪状态:此时不一定马上就开始运行
运行状态:运行状态的线程可能会发生问题(赚到了阻塞状态)
阻塞状态
终止状态
8.线程的优先级
线程的优先级用数字来表示,范围从1到10。主线程的缺省优先级是5,子线程的优先级默认与其父线程相同。
注意:程序中不能依赖线程的优先级来让线程按照我们的要求来执行!
Thread类提供的相关方法:
public final int getPriority();
public final void setPriority(int newPriority);
相关静态整型常量:
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
用法举例:
package v512.chap16; public class TestPriority { public static void main(String args[]){ System.out.println("线程名\t优先级"); Thread current = Thread.currentThread(); System.out.print(current.getName() + "\t"); System.out.println(current.getPriority()); Thread t1 = new Runner(); Thread t2 = new Runner(); Thread t3 = new Runner(); t1.setName("First"); t2.setName("Second"); t3.setName("Third"); t2.setPriority(Thread.MAX_PRIORITY); t3.setPriority(8); t1.start(); t2.start(); t3.start(); } } class Runner extends Thread { public void run() { System.out.print(this.getName() + "\t"); System.out.println(this.getPriority()); } }
输出结果:
线程名 优先级 main 5 First 5 Second 10 Third 8
9.线程的串行化
在多线程程序中,如果在一个线程运行的过程中要用到另一个线程的运行结果,则可进行线程的串型化处理。
Thread类提供的相关方法:
public final void join()
public final void join(long millis) //等待毫秒数
public final void join(long millis,int nanos)
测试 主线程必须要等到子线程运行完成之后才可以运行:
package v512.chap16; public class TestJoin { public static void main(String args[]){ MyRunner2 r = new MyRunner2(); Thread t = new Thread(r); t.start(); try{ t.join(); }catch(InterruptedException e){ e.printStackTrace(); } for(int i=0;i<20;i++){ System.out.println("主线程:" + i); } } } class MyRunner2 implements Runnable { public void run() { for(int i=0;i<20;i++) { System.out.println("SubThread: " + i); } } }
输出结果:
SubThread: 0 SubThread: 1 SubThread: 2 SubThread: 3 SubThread: 4 SubThread: 5 SubThread: 6 SubThread: 7 SubThread: 8 SubThread: 9 SubThread: 10 SubThread: 11 SubThread: 12 SubThread: 13 SubThread: 14 SubThread: 15 SubThread: 16 SubThread: 17 SubThread: 18 SubThread: 19 主线程:0 主线程:1 主线程:2 主线程:3 主线程:4 主线程:5 主线程:6 主线程:7 主线程:8 主线程:9 主线程:10 主线程:11 主线程:12 主线程:13 主线程:14 主线程:15 主线程:16 主线程:17 主线程:18 主线程:19
10.线程休眠
线程休眠——暂停执行当前运行中的线程,使之进入阻塞状态,待经过指定的“延迟时间”后再醒来并转入到就绪状态。
Thread类提供的相关方法:
public static void sleep(long millis)
public static void sleep(long millis, int nanos)
用法举例: 数字时钟
package v512.chap16; import java.util.*; import javax.swing.*; public class DigitalClock{ public static void main(String[] args){ JFrame jf = new JFrame("Clock"); JLabel clock = new JLabel("Clock"); clock.setHorizontalAlignment(JLabel.CENTER); jf.add(clock,"Center"); jf.setSize(140,80); jf.setLocation(500,300); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.setVisible(true); while(true){ clock.setText(getTime()); try{ Thread.sleep(1000); //this.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } } } public static String getTime(){ Calendar c = new GregorianCalendar(); String time = c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DATE) + " " ; int h = c.get(Calendar.HOUR_OF_DAY); int m = c.get(Calendar.MINUTE); int s = c.get(Calendar.SECOND); String ph = h<10 ? "0":""; String pm = m<10 ? "0":""; String ps = s<10 ? "0":""; time += ph + h + ":" + pm + m + ":" + ps + s; return time; } }
显示效果:
11.线程让步
线程让步——让运行中的线程主动放弃当前获得的 CPU 处理机会,但不是使该线程阻塞,而是使之转入就绪状态。
Thread类提供的相关方法:
public static void yield()
用法举例:
package v512.chap16; import java.util.Date; public class TestYield{ public static void main(String[] args){ Thread t1 = new MyThread(false); Thread t2 = new MyThread(true); Thread t3 = new MyThread(false); t1.start(); t2.start(); t3.start(); } } class MyThread extends Thread{ private boolean flag; public MyThread(boolean flag){ this.flag = flag; } public void setFlag(boolean flag){ this.flag = flag; } public void run(){ long start = new Date().getTime(); for(int i=0;i<500;i++){ if(flag) Thread.yield(); System.out.print(this.getName() + ": " + i + "\t"); } long end = new Date().getTime(); System.out.println("\n" + this.getName() + "执行时间: " + (end - start) + "毫秒"); } }
输出结果太多了,不便贴出,可以查看一下执行时间
Thread-0执行时间: 67毫秒
Thread-2执行时间: 84毫秒
Thread-1执行时间: 148毫秒
12.线程的挂起和恢复
线程挂起——暂时停止当前运行中的线程,使之转入阻塞状态,并且不会自动恢复运行。
线程恢复——使得一个已挂起的线程恢复运行。
Thread类提供的相关方法:
public final void suspend()
public final void resume()
用法举例:
package v512.chap16; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Date; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JButton; public class TestSuspend{ public static void main(String[] args){ JFrame jf = new JFrame("Timer"); JButton pause = new JButton("Pause"); JLabel clock = new JLabel("Timer"); clock.setBackground(Color.GREEN); clock.setOpaque(true); clock.setHorizontalAlignment(JLabel.CENTER); jf.add(clock,"Center"); jf.add(pause,"North"); jf.setSize(140,80); jf.setLocation(500,300); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.setVisible(true); MyThread3 mt = new MyThread3(clock,10000); mt.start(); MyListener ml = new MyListener(clock,mt); pause.addActionListener(ml); } } class MyThread3 extends Thread{ private JLabel clock; private long time; private long end; public MyThread3(JLabel clock,long time){ this.clock = clock; this.time = time; } public void init(){ long start = new Date().getTime(); end = start + time; } public void run(){ this.init(); while(true){ long now = new Date().getTime(); time = end - now; if(time > 0){ String s = this.convert(time); clock.setText(s); }else{ break; } try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } } clock.setText("时间到!"); clock.setBackground(Color.RED); } public String convert(long time){ long h = time / 3600000; long m = (time % 3600000) / 60000; long s = (time % 60000) / 1000; long ms = (time % 1000) / 10; String ph = h<10 ? "0":""; String pm = m<10 ? "0":""; String ps = s<10 ? "0":""; String pms = ms<10 ? "0":""; String txt = ph + h + ":" + pm + m + ":" + ps + s + "." + pms + ms; return txt; } } class MyListener implements ActionListener{ private JLabel clock; private MyThread3 mt; private boolean running= true; public MyListener(JLabel clock,MyThread3 mt){ this.clock = clock; this.mt = mt; } public void actionPerformed(ActionEvent e){ if(!mt.isAlive()) return; JButton jb = (JButton)(e.getSource()); if(running){ jb.setText("Replay"); clock.setBackground(Color.YELLOW); mt.suspend(); }else{ jb.setText("Pause"); clock.setBackground(Color.green); mt.init(); mt.resume(); } running = !running; } }
测试结果:
13.终止线程
一种特别优雅的方式终止线程(设置标志 flag)
实例:
public class Test { public static void main(String args[]){ Runner r = new Runner(); Thread t = new Thread(r); t.start(); for(int i=0;i<10;i++){ try{ Thread.sleep(5); System.out.println("\nin thread main i=" + i); }catch(InterruptedException e){ e.printStackTrace(); } } System.out.println("Thread main is over"); r.shutDown(); } }
public class Runner implements Runnable { private boolean flag=true; public void run() { int i = 0; while (flag == true) { System.out.print(" " + i++); } } public void shutDown() { flag = false; } }
main线程结束之后,子线程也就结束了(flag == false)
14.线程控制的基本方法
15. 临界资源问题
临界资源问题实例:
两个线程A和B在同时操纵Stack类的同一个实例(栈),A向栈里push一个数据,B则要从堆栈中pop一个数据。
栈的模拟 public class Stack{ int idx=0; char[ ] data = new char[6]; public void push(char c){ data[idx] = c; idx++; } public char pop(){ idx--; return data[idx]; } }
可能出现的问题
(4 中 c 应该是不存在的)
16. 互斥锁 和 死锁
在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
synchronized用法
①用于方法声明中,标明整个方法为同步方法:这个时候调用这个方法的对象是处于被当前的线程锁定的状态
public synchronized void push(char c){ data[idx] = c; idx++; }
②用于修饰语句快,标明整个语句块为同步块:这个时候 synchronized 中的对象是被当前的线程锁定了的
public char pop(){ //其它代码 synchronized(this){ idx--; return data[idx]; } //其它代码 }
并发运行的多个线程间彼此等待、都无法运行的状态称为线程死锁。
线程死锁实例一:
package v512.chap16; public class TestDeadLock{ public static void main(String args[]){ StringBuffer sb = new StringBuffer("ABCD"); MyThread4 t = new MyThread4(sb); t.start(); synchronized(sb){ try{ t.join(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(sb); } System.out.println("Main thread is over!"); } } class MyThread4 extends Thread{ private StringBuffer sb; public MyThread4(StringBuffer sb){ this.sb = sb; } public void run(){ synchronized(sb){ sb.reverse(); } System.out.println("Sub thread is over!"); } }
控制台什么都没有输出来!
分析:在main方法的同步块中串行插入了子线程t,并锁定了sb对象,然而子线程的run方法中也有一个同步块,它要锁定的对象正是sb对象,
然而sb对象已经被主线程给锁定了,子线程只能等待,另一方面,主线程又必须要等到子线程结束了之后才可以执行,两者就这么一直等待着,
陷入了死锁的状态!
如果删除掉main中的同步,就可以看到结果:
Sub thread is over! DCBA Main thread is over!
实例二:
package v512.chap16; public class TestDeadLock2{ public static void main(String args[]){ char[] a = {'A','B','C'}; char[] b = {'D','E','F'}; MyThread5 t1 = new MyThread5(a,b); MyThread5 t2 = new MyThread5(b,a); t1.start(); t2.start(); } } class MyThread5 extends Thread{ private char[] source; private char[] dest; public MyThread5(char[] source,char[] dest){ this.source = source; this.dest = dest; } public void run(){ synchronized(source){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(dest){ System.arraycopy(source,0,dest,0,source.length); System.out.println(dest); } } } }
假设线程t1先执行run,然后将字符数组a锁定了,接着t1休眠了,线程t2开始执行,并将数组b锁定了,之后线程t2进入休眠,
然后线程t1重新执行,这时候发现数组b已经被线程t2给锁定了,只能等待,休眠之后,线程t2同样发现数组a被线程t1给锁定了,
两者相互之间等待着,就陷入了死锁状态!
17.线程的同步通信
为避免死锁,在线程进入阻塞状态时应尽量释放其锁定的资源,以为其他的线程提供运行的机会。
相关方法:
public final void wait() :会导致当前的线程进入阻塞状态并尽量释放其锁定的资源
public final void notify():将某个线程重新恢复过来,但不是马上就进入到就绪状态,它可能还要等,等到它要使用的对象解除了锁定才行
public final void notifyAll()
生产者 和 消费者 问题:
同步的栈:
package v512.chap16; public class SyncStack { // 支持多线程同步操作的堆栈的实现 private int index = 0; private char[] data = new char[6]; public synchronized void push(char c) { while (index == data.length) { try { this.wait(); } catch (InterruptedException e) { } } this.notify(); data[index] = c; index++; System.out.println("produced:" + c); } public synchronized char pop() { while (index == 0) { try { this.wait(); } catch (InterruptedException e) { } } this.notify(); index--; System.out.println("consumed:" + data[index]); return data[index]; } }
测试类:包括了生产者和消费者
package v512.chap16; public class SyncTest{ public static void main(String args[]){ SyncStack stack = new SyncStack(); Runnable p=new Producer(stack); Runnable c = new Consumer(stack); Thread t1 = new Thread(p); Thread t2 = new Thread(c); t1.start(); t2.start(); } } class Producer implements Runnable{ SyncStack stack; public Producer(SyncStack s){ stack = s; } public void run(){ for(int i=0; i<20; i++){ char c =(char)(Math.random()*26+'A'); stack.push(c); try{ Thread.sleep((int)(Math.random()*300)); }catch(InterruptedException e){ } } } } class Consumer implements Runnable{ SyncStack stack; public Consumer(SyncStack s){ stack = s; } public void run(){ for(int i=0;i<20;i++){ char c = stack.pop(); try{ Thread.sleep((int)(Math.random()*500)); }catch(InterruptedException e){ } } } }
输出结果:
produced:M consumed:M produced:N consumed:N produced:P produced:A consumed:A consumed:P produced:Z consumed:Z produced:F produced:J produced:X produced:Q consumed:Q produced:N produced:W produced:K consumed:K produced:I consumed:I produced:A consumed:A produced:Z consumed:Z produced:M consumed:M produced:X consumed:X produced:X consumed:X produced:P consumed:P produced:V consumed:V consumed:W consumed:N consumed:X consumed:J consumed:F
18.多线程编程专题 (还未仔细看)
线程间数据传输
例16-16 使用管道流实现线程间数据传输
类的同步性与线程安全
例16-17 验证同步类的线程安全性
定时器
例16-18 使用定时器实现数字时钟功能