每个Java程序都有一个默认的主线程。Java程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法后就启动一个线程,这个线程就称作"主线程",该线程负责执行main方法。在main方法中再创建的线程就是其他线程。
如果main方法中没有创建其他线程,那么当main方法返回时JVM就会结束Java应用程序。但如果main方法中创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法返回(主线程结束)JVM也不会结束,要一直等到该程序所有线程全部结束才结束Java程序(另外一种情况是:程序中调用了Runtime类的exit方法,并且安全管理器允许退出操作发生。这时JVM也会结束该程序)。
线程的常用方法:
start():线程调用该方法将启动线程,从新建态进入就绪队列,一旦享用CPU资源就可以脱离创建它的线程,独立开始自己的生命周期。
run():Thread类的run()方法与Runnable接口中的run()方法功能和作用相同,都用来定义线程对象被调度后所进行的操作,都是系统自动调用而用户不得引用的方法。run()方法执行完毕,线程就成死亡状态,即线程释放了分配给它的内存(死亡态线程不能再调用start()方法)。在线程没有结束run()方法前,不能让线程再调用start()方法,否则将发生IllegalThreadStateException异常。
sleep(int millsecond):有时,优先级高的线程需要优先级低的线程做一些工作来配合它,此时为让优先级高的线程让出CPU资源,使得优先级低的线程有机会运行,可以使用sleep(int millsecond)方法。线程在休眠时被打断,JVM就抛出InterruptedException异常。因此,必须在try-catch语句块中调用sleep方法。
isAlive():当线程调用start()方法并占有CPU资源后该线程的run()方法开始运行,在run()方法没有结束之前调用isAlive()返回true,当线程处于新建态或死亡态时调用isAlive()返回false。
注意:一个已经运行的线程在没有进入死亡态时,不要再给它分配实体,由于线程只能引用最后分配的实体,先前的实体就成为了"垃圾",并且不能被垃圾回收机制收集。
currentThread():是Thread类的类方法,可以用类名调用,返回当前正在使用CPU资源的线程。
interrupt():当线程调用sleep()方法处于休眠状态,一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法"吵醒"自己,即导致线程发生IllegalThreadStateException异常,从而结束休眠,重新排队等待CPU资源。
GUI线程:JVM在运行包含图形界面应用程序时,会自动启动更多线程,其中有两个重要的线程:AWT-EventQueue和AWT-Windows。AWT-EventQueue线程负责处理GUI事件,AWT-Windows线程负责将窗体或组件绘制到桌面。
线程同步:(用synchronized修饰某个方法,该方法修改需要同步的变量;或用volatile修饰基本变量)
当两个或多个线程同时访问一个变量,并且一个线程需要修改这个变量时,应对这样的问题进行处理,否则可能发生混乱。
要处理线程同步,可以把修改数据的方法用关键字synchronized修饰。一个方法使用synchronized修饰,当一个线程A使用这个方法时,其他线程想使用该方法时就必须等待,直到线程A使用完该方法。所谓同步就是多个线程都需要使用一个synchronized修饰的方法。
volatile比同步简单,只适合于控制对基本变量(整数、布尔变量等)的单个实例的访问。java中的volatile关键字与C++中一样,用volatile修饰的变量在读写操作时不会进行优化(取cache里的值以提高io速度),而是直接对主存进行操作,这表示所有线程在任何时候看到的volatile变量值都相同。
在同步方法中使用wait()、notify()、notifyAll()方法:
当一个线程使用的同步方法中用到某个变量,而此变量又需要其他线程修改后才能符合本线程需要,那么可以在同步方法中使用wait()方法。中断方法的执行,使本线程等待,暂时让出CPU资源,并允许其他线程使用这个同步方法。其他线程如果在使用这个同步方法时不需要等待,那么它使用完这个同步方法时应当用notifyAll()方法通知所有由于使用这个同步方法而处于等待的线程结束等待。曾中断的线程就会从中断处继续执行,并遵循"先中断先继续"的原则。如果用的notify()方法,那么只是通知等待中的线程中某一个结束等待。
计时器线程Timer:(Timer还有很多高级操作,详细见JDK,这里做个概述)
java.swing.Timer类用于周期性地执行某些操作。有两个常用构造函数
public Timer(int delay, ActionListener listener):参数listener是计时器的监视器,计时器发生振铃的事件是ActionEvent类型事件,当振铃事件发生,监视器会监视到这个事件并回调ActionListener接口中的actionPerformed(ActionEvent e)方法。
public Timer(int delay):使用该构造方法,计时器要再调用addActionListener(ActionListener listener)方法获得监视器。
如果想让计时器只震动一次,可以让计时器调用setRepeats(boolean b)方法,参数b取false即可。
计时器还可以调用setInitialDelay(int delay)方法设置首次振铃的延时,如果没有设置首次振铃默认延时为构造函数中的参数delay。
还可以调用getDelay()和setDelay(int delay)获取和设置延时。
计时器创建后调用start()启动,调用stop()停止,即挂起,调用restart()重新启动计时器,即恢复线程。
java有点不同,实现多线程有两种方式:继承类Thread, 和 实现接口Runnable。
thread类有一个run函数,它是线程的入口,当启动一个新线程是,就从这个函数开始执行;
public class ThreadTest extends Thread{
public void run()
{
for (int i=0;i<5;i++)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args)
{
ThreadTest test1 = new ThreadTest();
ThreadTest test2 = new ThreadTest();
test1.start();
test2.start();
}
}注意线程是调用start()来开始的。
Runnable有一点点不同,实现这个类的时候和Thread一样,但是创建线程的时候还是放入一个Thread中创建:
public class RunnableTest implements Runnable
{
private int cnt;
public RunnableTest(int n)
{
super();
cnt = n;
}
public void run()
{
for (int i=0;i<5;i++)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+i+" "+--cnt);
}
}
public static void main(String[] args)
{
RunnableTest t = new RunnableTest(10);
Thread ta = new Thread(t);
Thread tb = new Thread(t);
ta.start();
tb.start();
}
}Thread和Runnable基本用法都是差不多的,但是从上面的例子可以看出,Runnable比较方便进程间数据的共享(公用了一个cnt计量单位),所以一样都使用Runnable。
线程常用的几个函数:
join(): 强制等待线程执行完毕。 例如如果在主线程里面加一句ta.join(),那么主线程会一直等待ta执行返回才接着执行后面的代码。但是在join之前已经创建的其他线程,则不会受影响,继续执行。
interrupt(): 挺有意思的一个函数,如果线程在执行sleep()函数 ,则打断它(以让sleep()函数抛出一个异常的方式中断),执行后面的语句。
setDaemon(true):设置为守护进程。守护进程的优先级是最低的,如果一个程序只剩下守护进程,那么它就中断所有守护进程而退出。 最常见的情况是,如果创建的全部是守护进程,那么当主函数main执行完毕后,就立刻终止所有线程而退出。
synchronized同步符号用法: 保证同一时刻,某段代码只有一个线程在执行。
wait()和notify(),notifyAll():让线程等待/唤醒。这两个函数比较奇怪,目前我不是很熟练,要配合synchronized符号使用,请看下面这个生产者-消费者例子(网上直接贴来的,写的挺好的):
public class tt {
public static void main(String[] args) {
Storage s = new Storage();
Producer p = new Producer(s);
Consumer c = new Consumer(s);
Thread tp = new Thread(p);
Thread tc = new Thread(c);
tp.start();
tc.start();
}
}
class Consumer implements Runnable {//消费者
Storage s = null;
public Consumer(Storage s){
this.s = s;
}
public void run() {
for(int i=0; i<20; i++){
Product p = s.pop();//取出产品
try {
Thread.sleep((int)(Math.random()*1500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer implements Runnable {//生产者
Storage s = null;
public Producer(Storage s){
this.s = s;
}
public void run() {
for(int i=0; i<20; i++){
Product p = new Product(i);
s.push(p); //放入产品
// System.out.println("生产者放入:" + p);
try {
Thread.sleep((int)(Math.random()*1500));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Product {
int id;
public Product(int id){
this.id = id;
}
public String toString(){//重写toString方法
return "产品:"+this.id;
}
}
class Storage {
int index = 0;
Product[] products = new Product[5];
public synchronized void push(Product p){//放入
while(index==this.products.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.products[index] = p;
System.out.println("生产者放入"+index+"位置:" + p);
index++;
this.notifyAll();
}
public synchronized Product pop(){//取出
while(this.index==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
this.notifyAll();
System.out.println("消费者从"+ index+ "位置取出:" + this.products[index]);
return this.products[index];
}
}


喜欢
顶
难过
囧
围观
无聊



