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 使用定时器实现数字时钟功能