一个进程里面有多个线程在执行,每个线程通过cpu的算法,并行的执行。所以,对于同一资源来说,就可能存在安全性的问题了,有可能出现数据不同步的情况。在java里面多线程并发的问题,也是非常需要注意的。今天就来讲讲多线程的问题。
在java里面写一个线程很简单,只要继承一个Thread 类或者 实现一个Runnable 接口就能实现一个线程了。由于java里面,一个类只允许有一个父类,所以通过继承Thread 就有缺陷了,不能实现类中的数据共享,而Runnable确实可以的。像常见的卖票的例子,就说明这点了。
通过继承Thread 的方式实现线程的话,是这样的
public class ExtendsThread extends Thread{ PRivate int tickert=10; @Override public void run() { // TODO Auto-generated method stub super.run(); for(int i=0;i<10;i++){ System.out.println("卖票:ticket"+this.tickert); } }}public static void main(String[] args) { ExtendsThread et=new ExtendsThread(); ExtendsThread et2=new ExtendsThread(); ExtendsThread et3=new ExtendsThread(); et.start(); et2.start(); et3.start(); }会发现总共买了30张票,而我们定义的ticket为10张票,这就是Thread的缺陷了,其实也不能说是缺陷,因为是三个不同的对象了,所以才会这样,但是如果是Runnable的话,就相当于是一个对象,如下:public class ImplementRunnable implements Runnable{ private int ticket=10; @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10;i++){ if(this.ticket>0){ System.out.println("卖票:ticket"+this.ticket--); } } }}public class TicketThread { public static void main(String[] args) { ImplementRunnable et=new ImplementRunnable(); new Thread(et).start(); new Thread(et).start(); new Thread(et).start(); } }继承这个Runnable接口,就不会像之前那样的结果了。这个只是Thread 和Runnable的区别。在java多线程并发的情况下,我们需要考虑数据同步的问题,以及线程池的使用,来减少开多个线程耗费的资源问题。
在线程里面有两个方法简要说下,onStop() 和 onInterrupt() 。onStop()方法会让线程突然停止,导致数据上的丢失,这个方法也已经被java抛弃了。
onInterrupt()方法,可以实现线程的退出,但是需要正确的使用。这个方式是在线程阻塞的情况下,会接收到一个中断异常(InterruptException),像wati()或者sleep()的时候,可以调用此方法。但是,也不可直接调用,需要设置共享变量来判断是否正确的退出线程。
对于同一资源,如果有多个线程想要对其操作,那么必然会发生资源抢占的问题,如果处理不当,将会出现数据紊乱的状况。这个时候就需要考虑同步跟互斥了。就是同一时间只能有一个线程访问,其他线程需要进入等待状态,访问完成之后,再唤醒所有在等待中的线程,然后,再互相竞争资源。
synchronized 是一个同步关键字,使用了这个关键字,可以将资源锁起来,不给其他线程访问,进入等待状态。等到这个线程完成之后,会调用wati()方法,代表此线程进入等待状态。然后再调用notify()或者notifyAll()方法来唤醒线程。notify()是唤醒一个线程来访问,notifyAll()方法是唤醒所有的线程来访问。
public class Resource { private boolean flag; private int count=0; public synchronized void create(){ while(flag){ //这个标记表示生产完成,要进入等待状态 try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } count++; System.out.println(Thread.currentThread().getName()+"生产者----"+count); flag=true; notifyAll(); //这里需要调用此方法,防止,notify()会调用到本线程,导致死锁 } public synchronized void consume(){ while(!flag){ //代表消费过了就进入等待状态 try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"消费者----"+count); flag=false; notifyAll(); }}这段代码代表的是,资源。里面的代码解释都有备注。public class Producer implements Runnable{ Resource resource; public Producer(Resource resource){ this.resource=resource; } @Override public void run() { // TODO Auto-generated method stub while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } resource.create(); } }}这个是生产的线程。public class Customer implements Runnable{ Resource resource; public Customer(Resource resource){ this.resource=resource; } @Override public void run() { // TODO Auto-generated method stub while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } resource.consume(); } }}这个是消费的线程。public class ProducerCustomerTest { public static void main(String[] args) { // TODO Auto-generated method stub Resource resource=new Resource(); new Thread(new Producer(resource)).start(); new Thread(new Producer(resource)).start(); new Thread(new Customer(resource)).start(); new Thread(new Customer(resource)).start(); }}测试代码。下面就是结果了。
从打印结果中可以看出,没有出现资源不准确的情况,这个就是简单的多线程协作的处理。
当然,在使用多个线程的时候,如果不使用线程池,将会带来很大的开销。其实,线程池的使用很简单。线程池的有点大家自行google.~
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); cachedThreadPool.execute(new Producer(resource)); cachedThreadPool.execute(new Producer(resource)); cachedThreadPool.execute(new Customer(resource)); cachedThreadPool.execute(new Customer(resource));这个就是线程池当中的一个的使用了,就不详细介绍了。。。
新闻热点
疑难解答