线程
1.程序如果只有后台进程而没有一个前台进程,那么整个java程序就会结束。用setDaemon(true)可以把线程设为后台线程,普通创建的线程都是前台线程。
2.一个线程类继承Thread类,这个子类实现run方法,run方法处理的事物的处理。启动线程用start方法。[thread类本身的run方法是空的,所以需要子类去实现,start方法也是thread类里继承过来的,由于java的多态性,所以start方法会启动子类的run方法]
3.join()方法可以合并线程,如果不带参数表示永久合并,如果带参数表示线程合并多少豪秒后又分开执行
class ThreadDemo
{
public static void main(String [] args)
{
Thread tt = new TestThread();
//tt.setDaemon(true);把线程tt置为后台线程
tt.start();//这个方法会自动去调用tt的run()
int index = 0;
while(true)
{
if(index++ == 100)//执行100次后合并线程
try{tt.join(10000);//把tt线程合并到主线程10秒,如果这里是没有参数,那么就永远执行run这 //个线程,因为这个线程是个死循环,下面的语句就执行不到了,哈哈} catch(Exception e){}
System.out.println("main()"+Thread.currentThread().gerName()//得到当前执行线程的名字);
}
}
}
class TestThread extends Thread
{
public void run()
{
while(true)
{
System.out.println("run()"+Thread.currentThread().gerName()//得到当前执行线程的名字);
}
}
}
以上说了如何用继承Thread类来创建线程,还有一种方法是类来实现Runnable接口来创建线程
class TestThread extends Thread 这个就要改成 class TestThread implements Runnable
而main中的Thread tt = new TestThread(); 就要改成 Thread tt = new Thread(new TestThread());这里接受的类型是Runnable型的
那么就出现了一个问题,既然两种方法都可以创建线程,那么有什么区别呢?我们来看一个铁路模拟售票系统:
有100张票,我们启动4个线程来卖他们:
class ThreadDemo
{
public static void main(String [] args)
{
/* 如果用这种写法,你会发现实际是启动了4个线程,然后4个线程都有各自的100张票,各买各的,所以这样是错的。
new TestThread();
new TestThread();
new TestThread();
new TestThread(); */
/*如果是这种写法,从输出结果中我们发现无论start了多少次,实际还是一个线程,所以这样也是错的。
TestThread tt = new TestThread();
tt.start();
tt.start();
tt.start();
tt.start();
*/
}
}
class TestThread extends Thread
{
int tickets = 100;
public void run()
{
while(true)
{
if(tickets>0)
System.out.println(Thread.currentThread().gerName()+"is saling ticket "+tickets--);
}
}
}
这种情况下我们就使用runnable了。请看:
class ThreadDemo
{
public static void main(String [] args)
{
TestThread tt = new TestThread();
new Thread(tt).start();//四个线程调用同一个线程对象
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TestThread implements Runnable
{
int tickets = 100;
public void run()
{
while(true)
{
if(tickets>0)
System.out.println(Thread.currentThread().gerName()+"is saling ticket "+tickets--);
}
}
}
总结:使用runnable接口创建多线程比继承thread要灵活。他适合多个相同的程序代码的线程去处理同一资源的情况。
多线程的应用:
1.网络聊天工具开发
一个线程负责发消息,一个负责收消息。两者互不干扰,如果是单线程,那么就可能前面在等待,而后面就接收不了消息。
2.大量数据库记录的复制。
如果一个复制要花3天时间,那么当你中途觉得没有必要,要停止的时候,你发现你不能让他停下来。而多线程的情况,一个
现成负责拷贝,是个无限循环
while(bStop)
{get data
copy data}
另一个现成监督是否有键盘按下,也就是把原本为true的变量bstop置为false。 bStop = false;当另一线程监视到变量 变化时会停止程序
3.www服务器为每一个来访者创建一个线程
线程安全问题:
我们在上面做的铁路模拟兽票系统有个问题:线程安全问题!
假如当tickets==5的时,系统正好正好要执行tickets--,而还美意执行这个的时候,cpu被切换到另一个线程。那么tickets仍然是5,所以很坑5这张票打印了2次,另一种情况:当票为1的时候系统刚好要打印出1,而这个时候cpu马上切换到了其他线程,它发现票还是1,所以通过了判断大于0,所以打印出了票1。而这个时候票--为0,cpu切换到了刚才的线程,由于刚才的线程在执行打印,所以把票0给打印出来,这些都造成了线程的安全问题
所以我们要把if语句块作为一个原子,放到synchronized()里面。synchronized()里面必须有参数,比如
String str = new String("");
然后把if语句块放到synchronized(str){ if... }里面 这样就实现了同步,避免了线程的不安全。
另外,我们如果希望一个方法是线程安全的,那么我们可以直接在方法名前写上synchronized关键字,比如:
public synchronized void sale(){......} //实际上这种情况下synchronized使用的同步对象是this
注意:str的定义必须放到run方法之外,这个对象应该是全局的,这样才能作为锁旗标!(系统会自动对这个对象进行标识)
线程执行的内部机制:
new Thread(tt).start();
执行start并没有马上去执行线程的run方法,而是再向下继续执行一会,因为cpu还没有这么快就切换到线程上,要想让他马上切换,可以在start后写上:try(Thread.sleep(1);//哪怕是一秒,他也会切换)catch(Exception e){}
如果把代码块的同步用synchronized(this),那么代码块会和有synchronized的方法同步!
1:31 为止 5
类似下面的代码在多线程调用它的时候也要注意线程安全问题:
public void push(char c)
{
data[index] = c;
index++;
}
另外有一场景[线程间通信问题]:生产者不停地产生一组数据,消费者不停地去取数据,数据都在缓存中,
class Producer implements Runnable
{
Q q;
public Producer(Q q)
{
this.q = q;
}
public void run()
{
int i = 0;
while(true)
{
if(i==0)
{q.name = "zhangsan";
try{Thread.sleep(1);}catch(Exception e){}//用sleep来模拟cpu切换的现象
q.sex = "male";
}
else
{q.name = "lisi";
q.sex = "female";
}
i = (i+1)%2; //使i在0和1之间切换
}
}
}
class Customer implements Runnable
{
Q q;
public Customer(Q q)
{
this.q = q;
}
public void run()
{
while(true)
{
System.out.println(q.name);
System.out.println(":"+q.sex);
}
}
}
class Q
{
String name = "unknow";
String sex = "unknow";
}
//run class
class ThreadCommunation
{
public static void main(String [] args)
{
Q q = new Q();
new Thread(new Prodecer(q)).start();
new Thread(new Customer(q)).start();
}
}
运行后我们会发现名字和性别并没有对应起来,这是因为线程没有同步的问题,要解决这个问题,只需要在两个类的while里
分别加上synchronized(q){}语句块,因为他们都使用同一个对象q,所以用q作为同步监视器
上面的代码还不是很完善,因为有当消费者取得同步监视器却发现缓冲区根本没有数据的情况,那怎么办?或者当生产者在
取得同步监视器开始生产数据的时候又取得了同步监视器开始放数据,这样就会把原先的数据覆盖!我们使用了wait和notify
对程序修改如下:
class Producer implements Runnable
{
Q q;
public Producer(Q q)
{
this.q = q;
}
public void run()
{
int i = 0;
while(true)
{
synchronized(q)
{
if(q.bFull)//如果缓冲区是满的 有数据的 那么生产者线程应该wait等待消费者来取数据
try{q.wait();}catch(Exception e){}
if(i==0)
{q.name = "zhangsan";
try{Thread.sleep(1);}catch(Exception e){}//用sleep来模拟cpu切换的现象
q.sex = "male";
}
else
{q.name = "lisi";
q.sex = "female";
}
q.bFull = true;
q.notify();//通知消费者有数据要他来取数据
}
i = (i+1)%2; //使i在0和1之间切换
}
}
}
class Customer implements Runnable
{
Q q;
public Customer(Q q)
{
this.q = q;
}
public void run()
{
while(true)
{
synchronized(q)
{
if(!q.bFull) //缓冲区为空的时候交出同步监视器开始等待
try{q.wait();}catch(Exception e){}
System.out.println(q.name);
System.out.println(":"+q.sex);
q.bFull = false; //取走数据后缓冲区为空
q.notify(); //通知生产者线程开始执行,这个notify与Producer中的wait对应
}
}
}
}
class Q
{
String name = "unknow";
String sex = "unknow";
boolean bFull = false;
}
//run class
class ThreadCommunation
{
public static void main(String [] args)
{
Q q = new Q();
new Thread(new Prodecer(q)).start();
new Thread(new Customer(q)).start();
}
}
注意:wait和notify方法必须是synchronized的监视器对象的方法,既如果有
synchronized(q),那么就应该q.wait();q.notify(); 如果不写对象,他会默认是this对象
而有可能导致运行时错误[编译没有错误]
任何对象都有wait,notify,notifyAll方法,
wait:告诉当前线程放弃监视器并进入睡眠状态直到其他线程进入同一监视器并调用notify为止;
notify:唤醒同一对象监视器中调用wait的第一个线程。
用于类似饭馆有一个空位后通知所有等候就餐的顾客中的第一位可以入座的情况。
notifyAll:唤醒同一对象监视器中调用wait的所有线程。具有最高优先级的线程首先被唤醒并执行。
用于类似某个不定期的培训班终于招生满额后,通知所有的学员都来上课的情况。
实际上上面的代码有些乱,这是因为程序的结构设计不够合理,应该把数据的放和取放到q类中,然后在
这两个方法名前加上关键字synchronized,类似
public synchronized void put(String name,String sex)
{
if(bFull)
try{wait();}catch(Exception e){}
this.name = name;
try{Thread.sleep(1);}catch(Exception e){}
this.sex = sex;
bFull = true;
notify();
}
我们给别人提供的类是线程安全的,类似上面的代码。别人在使用这个类的时候就不需要考虑线程安全问题,反之,就需要在
外面处理线程安全问题
控制线程的生命周期:
class ThreadLife
{
public static void main(String [] args)
{
ThreadTest tt = new ThreadTest();
new Thread(tt).start();
for(int i=0;i<100;i++)
{
if(i==50)
tt.stopMe();
System.out.println("main() is runing");
}
}
}
class ThreadTest implements Runnable
{
private boolean bStop = false;
public void stopMe()
{
bStop = true;
}
public void run()
{
while(!bStop)
{
System.out.println(Thread.currentThread().getName()+"is running");
}
}
}