Java 多线程编程

Java 多线程编程

class  Stack {
    
int index=0;
    
char data[]=new char[6];
    
public synchronized void push(char c){
            data[index]
=c;
            System.out.println(
"Push char: "+c+" ->");
            
            
try{
                Thread.sleep(
1000);
            }

            
catch(Exception e){}
            index
++;
        System.out.println(
"--> Push "+c+" Completed  index="+index+"   Stack is "+data[0]+" "+data[1]+" "+data[2]);
    }

    
    
public void pop(){
            index
--;
    
            
try{
                Thread.sleep(
10);
            }

            
catch(Exception e){}    
            
            
char c=data[index];
            data[index]
=' ';
            System.out.println(
"** Pop  "+c+"**  index="+index+"   Stack is "+data[0]+" "+data[1]+" "+data[2]);    

    }

}
     

class  PushRunner  implements  Runnable {
    
private Stack s;
    
public PushRunner(Stack s){
        
this.s=s;
    }

    
    
public void run(){
            s.push(
'c');
    }


    
}


class  PopRunner  implements  Runnable {
    
private Stack s;
    
public PopRunner(Stack s){
        
this.s=s;
    }

    
public void run(){
        
synchronized(s){
            s.pop();
        }

    }

}

public   class  TestSynchronize {
    
public static void main(String[] args){
        Stack s
=new Stack();
        s.push(
'a');
        s.push(
'b');
        
new Thread(new PushRunner(s)).start();
        
new Thread(new PopRunner(s)).start();
    }

}

首先,我们讨论一下进程,进程是操作系统级别下,单独执行的一个任务。

Win32 Unix都是多任务操作系统。

多任务,并发执行是一个宏观概念,实际微观串行。

CPU同一时间刻只能执行一个任务。

OS负责进程调度,使用CPU,也就是获得时间片。




在一个进程中,再可以分为多个程序顺序执行流,每个执行流就是一个线程。

分配CPU时间片的依然是CPU,多线程时,程序会变慢,每个线程分配到的时间片少了。



进程与线程的区别,进程是数据独占的(独立数据空间),线程是数据共享的(这也是线程之间通讯容易的原因,不需要传递数据)。

Java是语言级支持多线程的,体现在有现成的封装类(java.lang.Thread)完成了必要的并发细节的工作(与操作系统打交道,分配PID等)。



两种方式来得到一个线程对象。

public   class  TestThread {
    
public static void main(String[] args)
    
{
        Thread t1 
= new MyThread();
        t1.start();
    }

}


class  MyThread  extends  Thread
{
    
public void run()
    
{
        System.out.println(
"thread run");
    }

}


一个线程对象--〉代表着一个线程--〉一个顺序执行流(run方法)

这个程序有两个线程,一个是main主线程,它调用了t1.start(),这是t1线程只是就绪状态,还没有真正启动线程,main主线程结束了!!t1运行。两个线程都退出了,进程完结。

一个进程退出,要等待进程中所有线程都退出,再退出虚拟机。

方式2,实现java.lang.Runable接口,这是这个类的对象是一个目标对象,而不能理解为是一个线程对象。

public   class  TestThread {
    
public static void main(String[] args)
    
{
        Thread t1 
= new MyThread();
        t1.start();
        
        Runnable target 
= new MyRunnable();
        Thread t2 
= new Thread(target);
        t2.start();
    }

}


class  MyThread  extends  Thread
{
    
public void run()
    
{
        System.out.println(
"thread run");
    }

}


class  MyRunnable  implements  Runnable
{
    
public void run()
    
{
        System.out.println(
"runnable run");
    }

}

不要调用run()方法,它只是执行一下普通的方法,并不会启动单独的线程。

Untitled-1.gif

上面只是线程状态图。


在某一个时间内,处于运行状态的线程,执行代码,注意可能多个线程多次执行代码。

CPU会不断从可运行状态线程调入运行,不会让CPU空闲。


Thread.sleep(1000);当前线程睡眠1秒钟,休眠后->进入阻塞->休眠结束->回到可运行状态。

在run(),有异常抛出,必须try{}catch(Exception e){},不能throws Exception,因为run()方法覆盖不能抛例外。

能进入运行状态,只能由操作系统来调度。

一旦sleep-〉阻塞->交出程序执行权。


等待用户输入,输入输出设备占用CPU,处于阻塞的线程没有机会运行,输入完毕,重新进入可运行状态。


第三种进入阻塞状态的可能。

t1.join()调用后,运行状态线程放出执行权,作为t1的后续线程,等待t1结束。也就是说至少得等t1线程run完毕,才可能进入运行状态来执行,可不是说t1执行完,一定马上就是调用t1.join()的线程马上进入可运行行状态。只有操作系统有权利决定谁进入运行状态。

join的实质就是让两个线程和二为一,串行。

t1.join();执行这条语句现场是被保护起来的。t1结束,调用线程有机会运行时,会从上次的位置继续运行。

public   class  TestThread {
    
public static void main(String[] args)
    
{
        Thread t1 
= new MyThread();
        t1.start();
        
        
for(int i = 0; i < 50; i++)
        
{
            System.out.println(
"#");
            
if(i == 24)
                
try{
                    t1.join();
                }
catch(InterruptedException e)
                
{
                    e.printStackTrace();
                }

        }

    }

}


class  MyThread  extends  Thread
{
    
public void run()
    
{
        
for(int i = 0; i < 35; i++)
        
{
            System.out.println(
"*");
        }

    }

}



线程优先级,setPriotity(1--100),数越大,优先级越高。

开发中不提倡自省设置优先级,操作系统可能忽略优先级,不具有跨平台性(两方面,可运行,执行效果一致),因为这种方式很粗略。

static void yield(),运行状态的线程(当前线程),调用yield方法,马上交出执行权。回到可运行状态。

=============================

Thread对象有个run方法,当start()时,Thread进行系统级调用,系统分配一个线程空间,此时对象可以获得CPU时间片,一个顺序执行流程可以独立运行,线程结束,对象还在,只是系统回收线程。

=============================


两个线程同时的资源,称为临界资源,会有冲突。

堆栈数据结构,有一个char[]和一个index(表示实际长度,也表示下一个要插入元素的位置)。

一个push操作,有两个核心操作(加元素,修改index)。都执行和都没执行,没有问题。

但假设一个线程做了一个步,就交出执行权,别的线程,执行同样的代码,会造成数据不一致。

数据完整性也是一个要在开发中注意的地方。

------------------------------------------

所以为了保证数据安全,要给数据加锁。

一个Java对象,不仅有属性和方法,还有别的东西。

任何一个对象,都有一个monitor,互斥锁标记,可以交给一个线程。

只有拿到这个对象互斥锁标记的线程,才能访问这个对象。

synchronized可以修饰方法和代码块。

synchronized(obj){
   obj.setValue(123);
}

不是每个线程都能进入这段代码块,只有拿到锁标记的线程才能进入执行完,释放锁标记,给下一个线程。

记住,锁标记是对对象来说的,锁的是对象。

当synchronized标识方法时,那么就是锁当前对象。

class  Stack {
    
int index=0;
    
char data[]=new char[6];
    
public synchronized void push(char c){
            data[index]
=c;
            System.out.println(
"Push char: "+c+" ->");
            
            
try{
                Thread.sleep(
1000);
            }

            
catch(Exception e){}
            index
++;
        System.out.println(
"--> Push "+c+" Completed  index="+index+"   Stack is "+data[0]+" "+data[1]+" "+data[2]);
    }

    
    
public void pop(){
            index
--;
    
            
try{
                Thread.sleep(
10);
            }

            
catch(Exception e){}    
            
            
char c=data[index];
            data[index]
=' ';
            System.out.println(
"** Pop  "+c+"**  index="+index+"   Stack is "+data[0]+" "+data[1]+" "+data[2]);    

    }

}
     

class  PushRunner  implements  Runnable {
    
private Stack s;
    
public PushRunner(Stack s){
        
this.s=s;
    }

    
    
public void run(){
            s.push(
'c');
    }


    
}


class  PopRunner  implements  Runnable {
    
private Stack s;
    
public PopRunner(Stack s){
        
this.s=s;
    }

    
public void run(){
        
synchronized(s){
            s.pop();
        }

    }

}

public   class  TestSynchronize {
    
public static void main(String[] args){
        Stack s
=new Stack();
        s.push(
'a');
        s.push(
'b');
        
new Thread(new PushRunner(s)).start();
        
new Thread(new PopRunner(s)).start();
    }

}



注意此代码中,Stack这个临界资源类中的push方法中,有一个Thread.sleep,它让当前进程阻塞,也就是让拥有Stack对象s锁标记的线程阻塞,但这时它并不释放锁标记

所以Synchronized使用是有代价的,牺牲效率换数据安全,要控制synchronized代码块,主要是数据写,修改做同步限制,读就不用了。

还有一点要注意:synchronized不能继承, 父类的方法是synchronized,那么其子类重载方法中就不会继承“同步”。

一个线程可以拥有很多对象锁标记,但一个对象的锁标记只能给一个线程。

等待锁标记的线程,进入该对象的锁池。

1.jpg

每个对象都有一个空间,锁池,里面都是等待拿到该对象的锁标记的线程。

当然还是操作系统来决定谁来获得锁标记,在上一个锁标记释放掉后。



死锁,线程A拿到resourceA标记,去请求resourceB;线程B拿到resourceB标记,去请求resourceA;

线程间通讯机制->协调机制

一个对象不仅有锁和锁池,另外还有一个空间[等待队列]。

synchronized(路南){
      想要获得路北资源的线程,调用路南.wait();将自己的所有锁标记都释放。以便其他线程满足条件运行程序后,自己也就可以正常通过了。
}

调用obj.wait(),表示某一个线程释放所有锁标记并进入obj这个对象的等待队列。

等待队列也是阻塞状态。一个线程调用obj对象的notify(),会通知等待队列中的一个线程可以出来,notifyAll()是通知所有线程。

Untitled-1.gif

 

 

public   class  ProducerConsumer {
    
public static void main(String[] args){
        SyncStack s
=new SyncStack();
        Runnable p
=new Producer(s);
        Runnable c
=new Consumer(s);
        
new Thread(p).start();
        
new Thread(c).start();
    }

}



class  Producer  implements  Runnable {
    SyncStack s;
    
public Producer(SyncStack s){
        
this.s=s;
    }

    
    
public void run() {
        
for(int i=1;i<=20;i++){
            
char c=(char)(Math.random()*26+'A');
            s.push(c);
            
try{
                Thread.sleep(
20);
            }

            
catch (InterruptedException e){
                e.printStackTrace();
            }

        }

    }

}


class  Consumer  implements  Runnable {
    SyncStack s;
    
public Consumer(SyncStack s){
        
this.s=s;
    }

    
    
public void run(){
        
for(int i=1;i<=20;i++){
            
char c=s.pop();
            
try{
                Thread.sleep(
400);
            }

            
catch (InterruptedException e){
                e.printStackTrace();
            }

        }

    }

}


class  SyncStack {
    
private int index=0;  // the index next char added into, also presents the number of chars in stack
    private char[] data=new char[6];
    
    
public synchronized void push(char c) {
        
while (index==data.length)  {
            
try{
                
this.wait();
            }

            
catch (InterruptedException e){}
        }

        
        data[index]
=c;
        index
++;
        System.out.println(
"Char "+c+" Pushed into Stack");
        
for(int k=0;k<data.length;k++) System.out.print(data[k]);
        System.out.println();
        
        
this.notifyAll();

    }

    
    
public synchronized char pop()  {
        
while (index==0)  {
            
try{
                
this.wait();
            }

            
catch (InterruptedException e){}
        }

        
        index
--;
        
char c=data[index];
        data[index]
=' ';
        System.out.println(
"Char "+c+" Poped  from Stack");
        
        
for(int k=0;k<data.length;k++) System.out.print(data[k]);
        System.out.println();
        
        
this.notifyAll();

        
return c;
    }

}

上面为经典的生产者消费者问题,生产者使用SyncStack的push方法,消费者使用pop方法。


push方法:

  while (index==data.length)  {
   try{
    this.wait();//<-----------释放所有锁标记,阻塞现场保留
   }
   catch (InterruptedException e){}
  }

如果货架满了,生产者即使拥有锁标记,也不能再生产商品了,必须wait()。等待消费者消费物品,否则永远不会从SyncStack对象的等待队列中出来。

等待通知,何时通知呢?

public synchronized char pop()  {
  while (index==0)  {
   try{
    this.wait();
   }
   catch (InterruptedException e){}
  }
  
  index--;
  char c=data[index];
  data[index]=' ';
  System.out.println("Char "+c+" Poped  from Stack");
  
  for(int k=0;k<data.length;k++) System.out.print(data[k]);
  System.out.println();
  
  this.notifyAll(); //<-------------所有等待队列中的生产者都出了队列,因为没有锁标记,只能进入锁池。

  return c;
 }

为什么判断是一个while循环,而不是一个if呢?

注意:我们假设一种情形:

1) 有十个生产者线程,货架已经满了,10生产者依次获得锁标记,依次都调用this.wait(),都进入同一个SyncStack对象的等待队列,10个进程阻塞住,

2) 有一个消费者线程获得该SyncStack对象锁标记,一个消费者消费一个,执行完消费,调用this.notifyAll()
释放锁标记。

3) 刚才消费者调用SyncStack对象的notifyAll()后,10个线程都出来了,准备生产商品,全部进入锁池。

这十个线程的代码现场,还在wait()这个函数调用后面,也就是一旦或者锁标记,要继续从这里执行。

this.wait();//从这一句的后面继续执行。

4) 但如果有一个生产者push的话,货架已经就满了,但这时还有9个在锁池中,依次获得锁标记,但由于是while需要再次判断是否货架满不满,才能继续前行进行生产。如果是if,就会直接push,数组越界。


===================================

释放锁标记只有两种途径,代码执行完,wait()

让线程结束,就是想办法让run方法结束。

注意下面的bStop,标志位,可以在线程进入wait状态时,对某一线程调用interrupt(),线程抛出InterruptedException,然后根据标志位,方法返回。

;
class  TestInterrupt
{
    
public static void main(String[] args)
    
{
        Thread1 t1
=new Thread1();
        t1.start();
        
int index=0;
        
while(true)
        
{
            
if(index++==500)
            
{
                t1.stopThread();
                t1.interrupt();
                
break;
            }

            System.out.println(Thread.currentThread().getName());
        }

        System.out.println(
"main() exit");
    }

}


class  Thread1  extends  Thread
{
    
private boolean bStop=false;
    
public synchronized void run()
    
{
        
while(!bStop)
        
{
            
try    
            
{
                wait();
            }

            
catch(InterruptedException e)
            
{
                
//e.printStackTrace();
                if(bStop)  return;
            }

            
            System.out.println(getName());
        }

    }

    
public void stopThread()
    
{
        bStop
=true;
    }

}

Exc:

AB1CD2....
 
数字与字母依次打印。用线程完成。

public   class  Test {
    
public static void main(String[] args)
    
{
        Object o 
= new Object();
        PrintChar pc 
= new PrintChar(o);
        PrintNum pn 
= new PrintNum(o);
        pn.start();
        pc.start();
        
    }


}

class  PrintChar  extends  Thread {
    
private int index = 0;
    
private Object obj = null;
    
    
public PrintChar(Object o)
    
{
        
this.obj = o;
    }

    
public void run(){
    
    
synchronized(obj){
            
for( ;index < 26; index++)
            
{
                
                    System.out.print((
char)(index+'A'));
                    obj.notifyAll();
                    
if(index != 25)
                        
try{
                                obj.wait();
                        }
catch(InterruptedException e){}
            }
        
    }
    
}

}


class  PrintNum  extends  Thread {
  
private int index = 1;
  
private Object obj = null;
  
public PrintNum(Object o)
  
{
      
this.obj = o;
  }

  
    
public void run(){
        
        
synchronized(obj){
            
for( ;index < 53;index++)
            
{
                    System.out.print(index);                    
                    
if(index%2==0){            
                        obj.notifyAll();            
                        
if(index != 52){
                            
try{                        
                                    obj.wait();                        
                            }
catch(InterruptedException e)
                            
{}
                        }

                    }
                
                
            }

        }

    }


}


你可能感兴趣的:(Java 多线程编程)