一个进程里面有多个线程在执行,每个线程通过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));
这个就是线程池当中的一个的使用了,就不详细介绍了。。。