【Java学习笔记】多线程
作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/
创建:
任何继承了Thread的类都可以创立单独的线程
public class ThreadDemo1 extends Thread {
private String name;
private int count;
private int delay;
public static void main(String arg[]) {
ThreadDemo1 one = new ThreadDemo1("one",1000,2);
one.start();//发起一个线程
ThreadDemo1 two = new ThreadDemo1("two",2000,5);
two.start();//发起第二个线程
}
ThreadDemo1(String nameString,int countStart,int seconds) {
name = nameString;
count = countStart;
delay = seconds * 1000;
}
public void run() { //线程运行时执行的代码
while(true) {
try {
System.out.println(name + ": " + count++);
Thread.sleep(delay);
} catch(InterruptedException e) {
return;
}
}
}
}
发一个弹球的例程,在BounceBallCanvas类继承了Canvas,那么就必须重新创建一个类BounceBallThread去继承Thread
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Color;
import java.awt.Canvas;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
public class ThreadDemo2 extends JFrame {
public static void main(String arg[]) {
new ThreadDemo2();
}
public ThreadDemo2() {
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e)
{ System.exit(0); } } );
Container container = getContentPane();
BounceBallCanvas canvas = new BounceBallCanvas();
container.add(canvas);
pack();
BounceBallThread bbt = new BounceBallThread(canvas);
bbt.start();
setLocation(250,100);
setVisible(true);
}
}
class BounceBallThread extends Thread {
BounceBallCanvas canvas;
BounceBallThread(BounceBallCanvas bbc) {
canvas = bbc;
}
public void run() {
while(true) {
try {
sleep(33);
canvas.repaint();
} catch(Exception e) {
System.out.println(e.getStackTrace());
}
}
}
}
class BounceBallCanvas extends Canvas {
private double y;
private double x;
private final static int diameter = 10;
private final static double gravity = 0.3;
private double velocity;
private Rectangle rect;
private int width;
private int height;
private Image image = null;
private boolean running;
private Thread looper;
private int bounceCount;
BounceBallCanvas() {
rect = null;
setSize(100,300);
running = false;
height = 0;
width = 0;
}
public void update(Graphics g) {
rect = getBounds(rect);
if((height != rect.height) || (rect.width != width)) {
height = rect.height;
width = rect.width;
velocity = 0.0;
bounceCount = 0;
x = width / 2;
y = 0;
image = createImage(rect.width,rect.height);
}
if(velocity > 0.0) {
if(((int)(y) + diameter) > rect.height) {
velocity = -velocity;
velocity *= 0.95;
y = (double)(rect.height - diameter);
if(++bounceCount > 20) {
y = 0;
velocity = 0.0;
bounceCount = 0;
}
}
}
velocity += gravity;
y += velocity;
paint(image.getGraphics());
g.drawImage(image,0,0,this);
}
public void paint(Graphics g) {
if(rect == null)
return;
g.setColor(Color.white);
g.fillRect(0,0,rect.width,rect.height);
g.setColor(Color.black);
g.fillOval((int)x,(int)y,diameter,diameter);
}
}
在run()方法中可以使用Thread类的静态方法返回当前线程的引用,然后进行获取线程的名字等操作
Thread thread = Thread.currentThread();静态方法,
String name = thread.getName();
具体的请见Thread类。
值得注意的是
1.线程运行中对成员变量的操作和主线程对其的操作,操作的对象是同一个。而两个独立的线程则分别拥有数据空间。
2.若覆盖了start()方法,则要先调用super.start()方法,否则还是运行在主线程中运行run()方法。例如:
package org.bupt.test;
public class Machine extends Thread{
private int a=0;
private static int count =0;
public void start() {
super.start();//确保线程创建
System.out.println(currentThread().getName()+":第"+(++count)+"个线程启动"); //start()方法被覆盖后,就是由主线程执行这句话。
}
public void run() {
for (a = 0; a < 50; a++) {
System.out.println(currentThread().getName()+":"+a);
try {
sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Machine machine1=new Machine();
Machine machine2=new Machine();
machine1.start();
machine2.start();
}
}
3.一个线程只能被启动一次。
任何实现了Runnable接口的类都可以创建线程。这是为了弥补JAVA中单继承的特性。
同样的例子我们使用对Runnable接口的实现进行重写,三部曲:创建一个实现Runnable接口的类的实例,创建和其联系的线程,启动线程。
public class RunnableDemo1 implements Runnable {
String name;
int count;
int delay;
public static void main(String arg[]) {
RunnableDemo1 one = new RunnableDemo1("one",1000,2);
//Thread类的定义是Thread(Runnable runnable),而且Thread类本身也实现了Runnable接口
Thread threadOne = new Thread(one);//注意要用Thread类创建一个线程类
threadOne.start();
RunnableDemo1 two = new RunnableDemo1("two",2000,5);
Thread threadTwo = new Thread(two);
threadTwo.start();
}
RunnableDemo1(String nameString,int countStart,int seconds) {
name = nameString;
count = countStart;
delay = seconds * 1000;
}
public void run() {
while(true) {
try {
System.out.println(name + ": " + count++);
Thread.sleep(delay);
} catch(InterruptedException e) {
return;
}
}
}
}
弹球动画:
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Color;
import java.awt.Canvas;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
public class RunnableDemo2 extends JFrame {
public static void main(String arg[]) {
new RunnableDemo2();
}
public RunnableDemo2() {
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e)
{ System.exit(0); } } );
Container container = getContentPane();
BounceBallCanvas canvas = new BounceBallCanvas();
container.add(canvas);
pack();
canvas.start();
setLocation(250,100);
setVisible(true);
}
}
class BounceBallCanvas extends Canvas implements Runnable {
private double y;
private double x;
private final static int diameter = 10;
private final static double gravity = 0.3;
private double velocity;
private Rectangle rect;
private int width;
private int height;
private Image image = null;
private boolean running;
private Thread looper;
private int bounceCount;
BounceBallCanvas() {
rect = null;
setSize(100,300);
running = false;
height = 0;
width = 0;
}
public void start() {
if(!running) {
running = true;
looper = new Thread(this);
looper.start();
}
}
public void stop() {
running = false;
}
public void run() {
try {
while(running) {
Thread.sleep(33);
repaint();
}
} catch(Exception e) {
System.out.println(e.getStackTrace());
}
}
public void update(Graphics g) {
rect = getBounds(rect);
if((height != rect.height) || (rect.width != width)) {
height = rect.height;
width = rect.width;
velocity = 0.0;
bounceCount = 0;
x = width / 2;
y = 0;
image = createImage(rect.width,rect.height);
}
if(velocity > 0.0) {
if(((int)(y) + diameter) > rect.height) {
velocity = -velocity;
velocity *= 0.95;
y = (double)(rect.height - diameter);
if(++bounceCount > 20) {
y = 0;
velocity = 0.0;
bounceCount = 0;
}
}
}
velocity += gravity;
y += velocity;
paint(image.getGraphics());
g.drawImage(image,0,0,this);
}
public void paint(Graphics g) {
if(rect == null)
return;
g.setColor(Color.white);
g.fillRect(0,0,rect.width,rect.height);
g.setColor(Color.black);
g.fillOval((int)x,(int)y,diameter,diameter);
}
}
后台线程:
概念类似于Linux的后台守护进程,JVM的垃圾回收线程就是典型的后台线程,它负责回收其他线程不再使用的内存。只有前台的所有线程结束后后台线程才会结束。main线程默认为前台线程,而由前台线程创建的线程都是前台线程(后台创建的则是后台线程)。
Thread类的setDaemon()方法设置后台线程(线程启动之前),isDaemon()方法判断后台线程。
下边的实例程序我们在主线程中一直将a加1,start方法中创建了一个匿名线程类,它的实例为后台实例,每隔10ms把a重置为0
package org.bupt.test;
public class Machine extends Thread{
private int a;
private static int count;
public void start(){
super.start();
Thread daemon = new Thread(){//创建匿名类的实例
public void run() {
while (true) {//尽管是无限循环,但是在主线程结束时也会结束。
reset();
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void reset() {
a=0;
}
};
daemon.setDaemon(true);
daemon.start();
}
public void run() {
while (true) {
System.out.println(currentThread().getName()+":"+a++);
if (count++==100) {
break;
}
}
}
public static void main(String[] args) throws InterruptedException {
Machine machine=new Machine();
machine.setName("m1");
machine.start();
}
}
优先级:
1.调节各个线程的优先级
Thread类中的setPriority(int)和getPriority()方法可以设置和获取线程的优先级。优先级用整数表示。取值范围为1-10.Thread有三个静态变量:
MAX_PRIORITY :取值为10,最高。
MIN_PRIORITY:取值为1,最低。
NORM_PRIORITY:取值为5,默认。
主线程默认的优先级为5,若A线程创建了B线程,则B和A有同样的优先级。值得注意的是,在实际的OS中,JVM的优先级没有得到很好的映射,若对程序的可移植性有高的要求的话,那么应该确保在设置线程优先级时只使用上述三个静态变量。
2.线程睡眠:
使用sleep()方法,线程会转为阻塞态。休眠结束后转为就绪态。若中途打断(用interrupt()方法),则抛出异常InterruptedException。
3.线程让步:
使用yield()方法,若此时有相同优先级(或更高)的其他线程处于就绪态,则将当前运行的线程放到可运行池中(就绪态)并使其他线程运行。若没有相同优先级的线程则什么都不做。和sleep不同的地方是,sleep方法不考虑进程的优先级,而且sleep在休眠的时候转入阻塞态,具有更好的移植性。yield实际的作用是在测试中人为的提高程序的并发性能,以帮助发现一些隐藏的错误。
4.等待其他线程结束:
一个线程可以使用另一个线程的join()方法,当前运行的线程会转到阻塞态,直到另一个线程运行结束,它才会恢复运行。join可以传入超时的时间参数,若超过这个时间则线程也会恢复运行。
package org.bupt.test;
public class Machine extends Thread{
public void run() {
for (int a = 0; a < 50; a++) {
System.out.println(currentThread().getName()+":"+a);
}
}
public static void main(String[] args) throws InterruptedException {
Machine machine=new Machine();
machine.setName("m1");
machine.start();
System.out.println("main:join machine");
machine.join();
System.out.println("main:end");
}
}
运行结果:
main:join machine
m1:0
m1:1
m1:2
m1:3
m1:4
m1:5
m1:6
m1:7
m1:8
m1:9
m1:10
m1:11
m1:12
m1:13
m1:14
m1:15
m1:16
m1:17
m1:18
m1:19
m1:20
m1:21
m1:22
m1:23
m1:24
m1:25
m1:26
m1:27
m1:28
m1:29
m1:30
m1:31
m1:32
m1:33
m1:34
m1:35
m1:36
m1:37
m1:38
m1:39
m1:40
m1:41
m1:42
m1:43
m1:44
m1:45
m1:46
m1:47
m1:48
m1:49
main:end
实例:
Timer类可以定时执行一些任务,而TimerTask类则表示了定时器执行的任务,它是一个抽象类,实现了Runnable接口。
package org.bupt.test;
import java.util.Timer;
import java.util.TimerTask;
public class Machine extends Thread{
private int a;
private static int count;
public void start(){
super.start();
Timer timer = new Timer(true);//设置一个定时器,并且将其关联的线程设为后台线程。
TimerTask timerTask = new TimerTask(){//设置一个定时器任务的匿名类继承TimerTask类,其中的run 方法表示定时器需要定时完成的任务
public void run() {
while (true) {
reset();
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
timer.schedule(timerTask, 100, 100);//用来设置定时器需要定时执行的任务,第二个参数表示延迟执行时间,即设置后多长时间开始生效,第三个参数表示每次执行任务的间隔时间。
}
private void reset() {
a=0;
}
public void run() {
while (true) {
System.out.println(currentThread().getName()+":"+a++);
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count++==100) {
break;
}
yield();
}
}
public static void main(String[] args) throws InterruptedException {
Machine machine=new Machine();
machine.setName("m1");
machine.start();
}
}
注意:
同一个定时器对象可以执行多个定时任务,间隔时间可以设置为不同。
线程互斥:
该程序创建了五个Client对Server端进行读取,每次读取后都再写入一个10以内的随机数
package org.bupt.test;
class server {
int store ;
public int read() {
return store ;
}
public synchronized void update(int n) {
System.out.println("begin update") ;
store = n ;
System.out.println("ending update") ;
}
}
class client extends Thread {
static server d ;
public client(server d) {
this.d = d ;
}
private int random(int n) {
return (int) Math.round(n*Math.random() - 0.5) ;//Math 类的round ()方法的运算结果是一个<=(参数值+0.5)的最大整数。
}
public void run() {
do { System.out.println(d.read()) ;
d.update(random(10)) ; } while (true) ;
}
}
class server1 {
static server d = new server() ;
static client c[] = new client[5] ;
public static void main(String [] args) {
for(int i=0; i<5; i++) {
c[i] = new client(d) ;
c[i].start() ;
} ;
}
}
其中 synchronized {} 就是Critical Section,一个线程进入这个函数则另一个线程就不能同时再进入了。甚至你可以 synchronized 一个类,这意味着在这个类中的所有方法都是Critical Section。
下边我们介绍Monitor,用来进行互斥。
这个东西的别名是Intrinsic Lock、Monitor Lock,其实都是一个东西的不同名字而已。每一个对象都有一个Monitor,如果有其它的线程获取了这个对象的monitor,当前的线程就要一直等待,直到获得monitor的线程放弃monitor,当前的线程才有机会获得monitor。
懂得了以上,我们就不难理解同步块了:
Object lock = new Object();
... ...
Synchronized(lock){
/*Block of locked code*/
}
当然对象也可以使用自己的Monitor进行上锁
Synchronized(this){
/*Block of locked code*/
}
我们再看synchronized static method,防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
注意在继承关系上,synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法
同步:
public class JoinDemo extends Object {
public static Thread createThread(String name, long napTime) {
final long sleepTime = napTime;
Runnable r = new Runnable() {
public void run() {
try {
print("in run() - entering");
Thread.sleep(sleepTime);
} catch ( InterruptedException x ) {
print("interrupted!");
} finally {
print("in run() - leaving");
}
}
};
Thread t = new Thread(r, name);
t.start();
return t;
}
private static void print(String msg) {
String name = Thread.currentThread().getName();//将打印的信息前边加上由哪个线程打印的出处信息。
System.out.println(name + ": " + msg);
}
public static void main(String[] args) {
Thread[] t = new Thread[3];
/*创建了三个线程A、B、C,休眠时间分别为2s,1s,3s*/
t[0] = createThread("thread A", 2000);
t[1] = createThread("thread B", 1000);
t[2] = createThread("thread C", 3000);
for ( int i = 0; i < t.length; i++ ) {
try {
String idxStr = "thread[" + i + "]";
String name = "[" + t[i].getName() + "]";
print(idxStr + ".isAlive()=" +
t[i].isAlive() + " " + name);
print("about to do: " + idxStr +
".join() " + name);
long start = System.currentTimeMillis();
t[i].join(); // wait for the thread to die
long stop = System.currentTimeMillis();
print(idxStr + ".join() - took " +
( stop - start ) + " ms " + name);
} catch ( InterruptedException x ) {
print("interrupted waiting on #" + i);
}
}
}
}
运行结果:
main: thread[0].isAlive()=true [thread A]//主线程判断线程A的状态
main: about to do: thread[0].join() [thread A]//主线程准备与 线程A同步
thread A: in run() - entering//三个线程依次启动
thread B: in run() - entering
thread C: in run() - entering
thread B: in run() - leaving//由于持续时间,B和A先结束
thread A: in run() - leaving
main: thread[0].join() - took 2003 ms [thread A]//主线程等待到了线程A,花费了2003ms
main: thread[1].isAlive()=false [thread B]//主线程判断线程B的状态
main: about to do: thread[1].join() [thread B]//主线程准备与 线程A同步
main: thread[1].join() - took 0 ms [thread B]//由于在同步前线层B已经结束了,所以当主线程等到B的时候其实是没有花费任何时间的。
main: thread[2].isAlive()=true [thread C]//主线程判断线程C的状态
main: about to do: thread[2].join() [thread C]//主线程准备与 线程C同步
thread C: in run() - leaving//线程C结束
main: thread[2].join() - took 1001 ms [thread C]//主线程等待到了线程C,花费了1001ms
相关注意事项:
1.关于线程安全类:不可变类总是线程安全的,而可变类的线程安全往往以降低并发性能为代价,注意只对可能导致资源竞争的代码进行synchronized,并且在需要提供单线程和多线程两个环境下通过继承和覆盖而在内部建立采用同步机制的内部类。
2.以下情况持有锁的线程会释放锁:
执行完同步代码块
执行同步代码块中,遇到异常导致线程终止。
执行同步代码块中,执行了锁对象所属的wait()方法。
而以下情况线程是不会释放锁的:
执行同步代码块中,执行了Thread.sleep()方法,当前线程线程放弃CPU,开始睡眠,但是不会释放锁。
执行同步代码块中,执行了Thread.yield()方法,当前线程线程放弃CPU,将执行权给线程优先级大于等于其的线程,但是不会释放锁。
执行同步代码块中,其他线程执行了当前线程对象的suspend()方法(该方法已经被废弃),当前线程线程放弃CPU,但是不会释放锁。
3.关于死锁:
JVM不监测也不试图避免死锁,因此要自己保证不出现死锁状态。一个通用的经验法则是当几个线程都要访问共享资源A、B和C时,保证使每个线程按照同样的顺序去访问它们。