在java中,一个线程用一个Thread对象表示
一般每一个java程序都有一个main方法(applet没有),它是主线程的入口点
而用Thread表示的线程,入口点自然不是main了,而是run方法
定义一个线程,主要就是写它的run方法
写run有两种方法,一种是继承Thread类,然后重写它的run
另一种是实现Runnable接口,然后重写它的run,如下所示:
============================
//用外部类实现多线程
class ThreadTest2
{
public static void main(String[] args)
{
new ThreadTest2();
}
ThreadTest2(){
for(int i=1; i<=5; i++){
System.out.println("creating thread "+i);
outterThread th = new outterThread(i);//创建新线程
th.start();//启动刚创建的线程
}
}
}
class outterThread extends Thread//通过继承Thread类来实现多线程;外部类
{
int count;
outterThread(int i){
count = i;
}
public void run() {
while(true){
System.out.println("thread "+count+" is running");
try{
sleep(1000*count);
}
catch(InterruptedException e){}
}
}
}
=====================================
class ThreadTest3 implements Runnable //通过实现Runnable接口来实现多线程
{
int count;
public static void main(String[] args)
{
for(int i=1; i<=5; i++)
//调用了Thread类的构造函数Thread(Runnable target)
new Thread(new ThreadTest3(i)).start();
}
ThreadTest3(int i){
System.out.println("creating thread "+i);
count = i;
}
public void run(){
while(true){
System.out.println("thread "+count+" is running");
try{
Thread.sleep(1000*count);//让线程睡眠一段时间
}
catch(InterruptedException e){}
}
}
}
可以看到,不论如何重写run,都要生成Thread对象.不同的是,第二种方法要在Thread的构造函数里传入Runnable的实现类作为参数
上面的例子还用到了start()和sleep()
前者用作启动线程,调用它之后,run方法就开始执行;后者使线程暂停参数指定的时间,单位是毫秒.这个sleep会抛出异常,所以要有try,catch块
===========================
在上面的例子中,在for循环中创建的线程和主线程是并列的,一个线程的结束不会影响另一个线程
在上面的例子中,执行完for循环,主线程就结束了,而其他线程还在运行着
用setDaemon()可以使一个线程作为后台线程执行,也就是说,当其他非Daemon线程结束时,不论run执行完没有,后台线程也将退出
反过来,你也可以使主线程等待其他线程结束后再退出.注意这里是主线程等待其他线程结束,而上面讲的daemon线程是在所有非daemon(主线程和其他非daemon)退出后再退出.要实现这一目的,可使用join().它可带可不带参数,带参数的话, 参数表示等待时间,如果过了这指定的时间不管join了的线程退出与否,主线程都会结束(如果main方法已经执行完了)
下面是例子
=======================
class DaemonAndJoin
{
public static void main(String[] args)
{
Thread th = new Thread( new Runnable(){
int count = 2;
public void run(){
while(count>0){
for(int i=0; i<5; i++){
try{
Thread.sleep(500);
System.out.println("in thread 1 ");
}catch(InterruptedException e){
e.printStackTrace();
}
}
count--;
System.out.println("one for is done");
}
}
});
Thread th2 = new Thread(new Runnable(){
public void run(){
while(true){
try{
Thread.sleep(1000);
System.out.println("in thread 2");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
});
System.out.println("main thread begins");
th.start();
th2.setDaemon(true);//设置后台线程
th2.start();
try{
th.join();//join
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("main thread quits");
}
}
==============
上面例子,如果没有th这个线程,th2和主线程都会很快结束
如果有th,但是没有join,主线程会很快结束,但是th2因为还有一个非daemon未结束,所以不会很快结束,而一直等到th结束才结束
如果没有setDaemon,则th不影响th2
==================
线程的同步
当多个线程同时对一个对象进行操作,就有可能发生问题,这时就要用线程同步来解决
考虑下面的例子
class SychronizedTest
{
public static void main(String[] args)
{
final Student stu = new Student();
Thread setNameAndNumth1 = new Thread( new Runnable(){
public void run(){
while(true){
synchronized(stu){
stu.setNameAndNum("john","jj");
}
}
}
});
Thread setNameAndNumth2 = new Thread( new Runnable(){
public void run(){
while(true){
//synchronized(stu){
stu.setNameAndNum("kate","kk");
//}
}
}
});
setNameAndNumth1.start();
setNameAndNumth2.start();
System.out.println("test started:");
}
}
class Student{
private String name;
private String id;
private int count = 0;
public /*synchronized*/ void setNameAndNum(String name,String id){
// synchronized(this){
this.name = name;
this.id = id;
count++;
if(!check())
System.out.println( count + " unmatched name and id: "+name+" "+id);
// }
}
private boolean check(){
return name.charAt(0) == id.charAt(0);
}
}
在这个例子中,Student类有两个属性.
有两个线程,它们修改同一个Student类的实例,然后检查修改后的这个实例的两个属性符不符合要求(判断两个属性的第一个字符是否一样)
如果不符合要求,则在控制台输出语句,否则不输出
例子中有三部分注释,每一部分说明了一种同步方式
最上面方式使Student类的方法都按同步的方式执行
第二种通过synchronized关键字,使单一一个方法,而不是全部方法按同步的方式执行
第三种只是一个方法里的某一部分按同步的方法执行(虽然在这个例子里,方法的整个方法体都被括起来了)
====================================
最后附上经典的生者消费者程序
class WaitNotifyTest
{
public static void main(String[] args)
{
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);
producerThread.start();
consumerThread.start();
}
}
class Producer implements Runnable{
Clerk clerk;
Producer(Clerk clerk){
this.clerk = clerk;
}
public void run(){
System.out.println("producer starts producing product " );
for(int i=0; i<10; i++){
try{
Thread.sleep( (int)(Math.random()*3000) );
}catch(InterruptedException e){
e.printStackTrace();
}
clerk.setProduct(i);
// System.out.println("producer is producing product " + i);
}
}
}
class Consumer implements Runnable{
Clerk clerk;
Consumer(Clerk clerk){
this.clerk = clerk;
}
public void run(){
System.out.println("consumer starts consuming product " );
for(int i=0; i<10; i++){
try{
Thread.sleep( (int)(Math.random()*3000) );
}catch(InterruptedException e){
e.printStackTrace();
}
clerk.getProduct();
// System.out.println("consumer is consuming product "+ i);
}
}
}
class Clerk{
int product = -1;
public synchronized void setProduct(int product){
if( this.product != -1 ){
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
this.product = product;
System.out.println("producer is producing product " + product);
notify();
}
public synchronized void getProduct(){
if( product == -1 ){
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println("consumer is consuming product "+ product);
this.product = -1;
notify();
}
}