java 多线程
1. 后台线程:
如果在某个线程运行之前调用了setDaemon方法,则该线程就变为后台线程。
对java程序来说,只要还有一个前台线程在运行,这个进程就不会结束,如果一个进程只有后台线程在运行,这个进程就会结束。
public class TestSetDaemon { public static void main(String[] args) { Thread th = new Thread(new ThreadTest()); //th.setDaemon(true); th.start(); } } class ThreadTest implements Runnable{ public void run(){ while(true){ System.out.println("threadtest is running"); } } }
2. 联合线程:
thread.join()作用是把thread所对应的线程合并到调用thread.join()语句的线程中。
public class TestJoin { public static void main(String[] args) { Thread th=new Thread(new JoinThread()); th.start(); int i=1; while(true){ i++; if(i==100) th.join(10000); System.out.println("main***************"); Thread.sleep(100); } } } class JoinThread implements Runnable{ public void run(){ while(true){ Thread.sleep(100); System.out.println("threadtest is running"); } } }
3.继承Thread与Runnable接口的区别:
适合多个相同的程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好的体现了面向对象的设计思想。
public class ExtendsThread { public static void main(String[] args) { Thread th1=new Tick(); Thread th2=new Tick(); th1.start(); th2.start(); } } class Tick extends Thread{ public static int count=100; //必须是全局 public void run(){ while(count>0){ System.out.println(Thread.currentThread()+"*****"+(count--)); } } } public class ImplemtRunnable { public static void main(String[] args) { Ticket t = new Ticket(); Thread th1 = new Thread(t); Thread th2 = new Thread(t); th1.start(); th2.start(); } } class Ticket implements Runnable{ int count=100; public void run(){ while(count>0){ System.out.println(Thread.currentThread()+"***"+(count--)); } } }
线程同步:sychronized关键字的原理:任何一个对象都有一个标志位,它有0,1两种状态,开始状态为1,当线程执行到含有sychronized的关键字时就会检查它所带的对象的标志位是1还是0,如果标志位为1,则能继续向下执行,同时将对象的标志位置0。如果对象的标志位为0,线程就会阻塞,将它放在该对象的阻塞队列中,一直等到该标志位为1,才从该对象的阻塞队列中取出一个等待的线程,继续执行。我们的线程在加上sychronized关键字后,并不能决定CPU的调度问题,只是在标志位为0时,不能继续执行,这样可以保证在一段时间内,该代码段只被一个线程访问,从而保证线程安全。
public void run(){ while(true){ if(count>0){//要保证这条语句的原子性 try {Thread.sleep(100);} catch (InterruptedException e) {} System.out.println(Thread.currentThread()+"*******"+(count--)); } } } //同步代码块 class Ticket implements Runnable{ int count=100; String str=new String(); //同步对象定义不能放在run方法中,因为每个对象在调用run方法时都会产生一个新的同步对象,并不是同一个监视器对象 public void run(){ while(true){ synchronized (str) { if(count>0){//通过synchronized保证这条语句的原子性 try {Thread.sleep(100);} catch (InterruptedException e) {} System.out.println(Thread.currentThread()+"****"+(count--)); } } } } //同步方法 class Ticket implements Runnable{ int count=100; public void run(){ while(true){ this.sale(); } } public synchronized void sale(){ if(count>0){//要保证这条语句的原子性 try {Thread.sleep(100);} catch (InterruptedException e) {} System.out.println(Thread.currentThread()+"***"+(count--)); } } } //方法与代码块同步 public class ImplemtRunnable { public static void main(String[] args) throws InterruptedException { Ticket t = new Ticket(); Thread th1 = new Thread(t); th1.start(); Thread.sleep(100); //确保在th2运行之前th1已经启动 Thread th2 = new Thread(t); t.str=new String("methord"); th2.start(); } } class Ticket implements Runnable{ int count=100; String str=new String(); public void run(){ if(str.equals("methord")){ while(true){ this.sale(); } }else{ while(true){ synchronized (this) { if(count>0){//要保证这条语句的原子性 try {Thread.sleep(100);} catch () {} System.out.println(Thread.currentThread().getName()+"******block*****"+(count--)); } } } } } public synchronized void sale(){ if(count>0){//要保证这条语句的原子性 try {Thread.sleep(100);} catch (InterruptedException e) {} System.out.println(Thread.currentThread()+"*******methord*****"+(count--)); } } }
线程死锁:线程A得到监视器1,而同时请求监视器2;线程B得到了监视器2,而同时请求监视器1
synchronized(str1){ synchronized(str2){
synchronized(str2){ synchronized(str1){
} }
} }
多个线程访问同一数据时就会发生线程安全问题,就必须实现对原子性的操作进行同步操作(要使用同一个监视器)。
public void push(char c)
{ //两个线程同时访问时 线程1进入push方法时,index=2,线程1将c放在data[2]中,还没来得急进行index++。CPU此时又调度线程2执行,此时index仍为2,线程2又将数据放在data[2]中,就会导致在此位置放置了两次数据,而index=3中没有数据。
data[index]=c; index++;
}
4.线程间的通信:
Person{name,sex}
两个问题:1.生产者线程在产生Person{chejt,男}后,继续生产Person在将name=chejr还没来得急将性别变为女,此时线程切换到消费者线程,消费者线程就取到{chjr,男},造成数据错误。2. 生产者线程在产生Person{chejt,男}后,继续生产Person在将name=chejr,sex=女之后,消费者线程才取数据,造成数据的丢失。
wait:告诉当前线程放弃执行监视器()并进入休眠状态直到其他线程进入同一监视器并调用notify为止。
notify:唤醒同一对象监视器中调用wait的第一个线程继续向下执行。用于类似饭馆有一个空座位通知所有等候就餐的顾客中的第一位可以入座的请款。
notifyAll:唤醒同一对象监视器中调用wait的所有线程,具有最高优先级别的线程首先被唤醒并执行。用于类似某个不定期的培训班终于招生满后,通知所有学员都来上课的情况。
自己设计的类或者库中提供的类是否是线程安全的?如果不是,就要在生产者和消费者之中自己编写线程同步和通信问题。
class Queue{ String name; char sex; boolean isFull; public Queue(String name,char sex,boolean isFull){ this.name=name; this.isFull=isFull; this.sex=sex; } public void put(String name,char sex){ this.name=name; try{Thread.sleep(100);}catch(Exception e){} this.sex=sex; } public void get(){ System.out.println(this.name); try{Thread.sleep(10);}catch(Exception e){} System.out.println(this.sex); } } //消费者 class Consumer implements Runnable{ Queue q; //队列,用于在生产者和消费者之间传递数据 public Consumer(Queue q){ this.q=q; } @Override public void run(){ while(true){ synchronized (q) { //为了实现生产者和消费者同步,使用同一个锁其标(实现线程之间的同步)
,加入线程之间的通信可以解决消费者和生产者之间多次取和多次读的问题 if(!q.isFull){ try{q.wait();}catch(Exception e){} //在同步代码块中要调用wait和notify
时,必须调用锁其标对象的wait和notify } q.get(); q.isFull=false; q.notify(); } } } } class Produce implements Runnable{ Queue q; int i=1; public Produce(Queue q){ this.q=q; //接收从外部传来的参数,这样可以保证生产者和消费者操作同一个缓冲区 } @Override public void run(){ while(true){ synchronized (q) { if(q.isFull){ try{q.wait();}catch(Exception e){} } if(i%2==0){ q.put("chejt"+i,'m'); }else{ q.put("www"+i,'f'); } i++; q.isFull=true; q.notify(); } } } } //测试类 public class ThreadCommunication{ public static void main(String[] args){ Queue q=new Queue("unkown",'u',false); Thread pro = new Thread(new Produce(q)); Thread con = new Thread(new Consumer(q)); pro.start(); con.start(); } } 将类做成线程安全的类: package org.cjt.comm1; class Queue{ private String name; private char sex; private boolean isFull; public Queue(String name,char sex,boolean isFull){ this.name=name; this.isFull=isFull; this.sex=sex; } public synchronized void put(String name,char sex){ if(isFull){ try{wait();}catch(Exception e){} } this.name=name; try{ Thread.sleep(10); }catch(Exception e){} this.sex=sex; this.isFull=true; notify(); } public synchronized void get(){ if(!isFull){//为了实现生产者和消费者同步,使用同一个锁其标(实现线程之间的同步),加入线程之间的通信可以解决消费者和生产者之间多次取和多次读的问题 try{wait();}catch(Exception e){} } System.out.println(this.name); try{Thread.sleep(10);}catch(Exception e){} System.out.println(this.sex); this.isFull=false; notify(); } } //消费者 class Consumer implements Runnable{ Queue q; //队列,用于在生产者和消费者之间传递数据 public Consumer(Queue q){ this.q=q; } @Override public void run(){ while(true){ q.get(); } } } class Produce implements Runnable{ Queue q; public Produce(Queue q){ this.q=q; //接收从外部传来的参数,这样可以保证生产者和消费者操作同一个缓冲区 } @Override public void run(){ int i=1; while(true){ if(i%2==0){ q.put("chejt"+i,'m'); }else{ q.put("www"+i,'f'); } i++; } } }
在类设计时 ,一定要有面向对象的思想,尽量将要对类中数据操作封装成类的方法。
stop方法:容易导致死锁的发生。eg:如果线程1获得锁其标的线程,在线程2中调用stop方法将线程1终止,其它要等待的线程就永远得不到锁其标,这样就容易导致死锁。