xscn

博客园 首页 新随笔 联系 订阅 管理

 

框架图

多线程

进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。

一个进程中至少有一个线程。

如何在自定义的代码中,自定义一个线程呢?

创建线程的第一种方式:继承Thread类。

步骤:

1,定义类继承Thread

2,复写Thread类中的run方法。

目的:用于存储线程要运行的代码。

3,调用线程的start方法,

该方法两个作用:启动线程,调用run方法。

 1 class Demo extends Thread//继承Thread类
 2 {
 3     public void run()//复写run方法
 4     {
 5                for(int x=0; x<60; x++)
 6                System.out.println("demo run----"+x);
 7     }
 8 }
 9 class ThreadDemo 
10 {
11     public static void main(String[] args) 
12     {
13                Demo d = new Demo();//创建好一个线程对象。
14                d.start();//开启线程并执行该线程的run方法。
15                //d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。
16                for(int x=0; x<60; x++)
17                System.out.println("Hello World!--"+x);
18         
19 }
20 }

发现运行结果每一次都不同。因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行(多核除外)。cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性-----谁抢到谁执行,至于执行多长,cpu说的算。

 1 /*练习:创建两个线程,和主线程交替运行。
 2 线程都有自己默认的名称。
 3 Thread-编号 该编号从0开始。
 4 static Thread currentThread():获取当前线程对象。
 5 getName(): 获取线程名称。
 6 设置线程名称:setName或者构造函数。
 7 */
 8 class Test extends Thread
 9 {
10     //private String name;
11     Test(String name)
12     {
13         //this.name = name;
14         super(name);
15     }
16     public void run()
17     {
18         for(int x=0; x<60; x++)
19         {
20     System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);
21         }
22     }
23 
24 }
25 
26 
27 class ThreadTest 
28 {
29     public static void main(String[] args) 
30     {
31         Test t1 = new Test("one---");
32         Test t2 = new Test("two+++");
33         t1.start();
34         t2.start();
35         for(int x=0; x<60; x++)
36         {
37             System.out.println("main....."+x);
38         }
39     }
40 }

创建线程的第二种方式:实现Runable接口

步骤:

1,定义类实现Runnable接口

2,覆盖Runnable接口中的run方法。

将线程要运行的代码存放在该run方法中。

3,通过Thread类建立线程对象。

4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

自定义的run方法所属的对象是Runnable接口的子类对象,要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。

5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

 

 1 /*
 2 需求:简单的卖票程序。
 3 4个窗口同时买票。*/
 4 class Ticket implements Runnable//定义类实现Runnable接口
 5 {
 6     private  int tick = 100;
 7     public void run()//覆盖Runnable接口中的run方法
 8     {
 9         while(true)
10         {
11             if(tick>0)
12             {
13     System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
14             }
15         }
16     }
17 }
18 class  TicketDemo
19 {
20     public static void main(String[] args) 
21     {
22 
23         Ticket t = new Ticket();
24 
25         Thread t1 = new Thread(t);//通过Thread类建立线程对象,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
26         Thread t2 = new Thread(t);
27         Thread t3 = new Thread(t);
28         Thread t4 = new Thread(t);
29         t1.start();//调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
30         t2.start();
31         t3.start();
32         t4.start();
33 
34 
35     }
36 }

实现方式和继承方式区别

子类如果已经继承了父类,就不能再继承Thread类,只能通过实现Runnable接口的方式来运行多线程的代码。避免了单继承的局限性,资源也可以被独立使用,推荐使用实现方式。

实现方式和继承方式的线程代码存放的位置也不一样。继承Thread方法,线程方法存放在Thread及其子类的run方法中;实现Runnable方法,线程方法存在于接口的子类的run方法中,即新建类中。

run方法内部出现了异常,只能在内部解决,不能抛出,Runnable中的run没有异常处理方法。

线程状态示例图

冻结。线程暂停,但没有结束。sleep(),暂停指定time时间后,自动运行;wait()等待,不传入参数不会运行;notify(),唤醒,线程继续运行。冻结状态放弃了执行资格,时间到了,或被唤醒后,进入临时状态,等待获得运行权。

消亡。run方法运行完毕后,自动消亡,或者stop(),运行时让线程终止。

临时状态又称阻塞状态。CPU一次只能运行一个线程,其他线程都在临时状态,在等待CPU的执行权;也就是说start后线程不一定马上运行,此时具备运行资格(内存中拥有空间且随时可以运行),但没有运行权(获取资源被CPU执行)。

运行,既有运行资格,又有运行权的状态。

多线程运行时的安全问题

上面卖票的代码示例有可能会实现一张票卖多次,还可能出现0号票和负数票的可能。发生这个问题的原因是,所有进程是同步运行的,可能会同一张票被买了多次。这种情况不是时时都会发生,但肯定是有可能会发生的。为了查看这种情况,调用Thread的sleep()方法。

 1 class Ticket implements Runnable
 2 {
 3     private  int tick = 100;
 4     public void run()
 5     {
 6         while(true)
 7         {
 8             if(tick>0)
 9             {
10                 try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}
11                 System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
12             }
13         }
14     }
15 }
16 class  TicketDemo
17 {
18     public static void main(String[] args) 
19     {
20 
21         Ticket t = new Ticket();
22 
23         Thread t1 = new Thread(t);
24         Thread t2 = new Thread(t);
25         Thread t3 = new Thread(t);
26         Thread t4 = new Thread(t);
27         t1.start();
28         t2.start();
29         t3.start();
30         t4.start();
31      }
32 }

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块和同步函数。

同步代码块

synchronized(对象)

{

需要被同步的代码

}

对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。比如火车上的卫生间例子

如何判断需要被同步的代码

1. 明确哪些代码是多线程运行代码。

 2. 明确共享数据。

 3. 明确多线程运行代码中哪些语句是操作共享数据的。同步指定的语句,即操作共享数据的语句。

同步的前提

1,必须要有两个或者两个以上的线程。

2,必须是多个线程使用同一个锁。

同步函数

格式:在函数上加上synchronized修饰符即可。

同步函数使用的锁是this,函数需要被对象调用,函数都有一个所属对象的引用,即this。

注意:同步函数被静态修饰后,使用的锁不再是this,使用的锁是该方法所在类的字节码文件对象,即类名.class。因为静态方法中没有this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象,即类名.class,该对象的类型是Class,此文件在内存中是唯一的,因为字节码文件是一次加载,不再重复。

同步的好处和弊端:解决了多线程的安全问题;多个线程需要判断锁,较为消耗资源。

死锁

同步中嵌套同步,而锁不同,有可能导致死锁,开发时应尽量避免死锁。

 1 /*死锁示例*/
 2 class Test implements Runnable
 3 {
 4     private boolean flag;
 5     Test(boolean flag)
 6     {
 7        this.flag = flag;
 8     }
 9 
10     public void run()
11     {
12         if(flag)
13         {
14             while(true)
15             {
16                 synchronized(MyLock.locka)
17                 {
18                     System.out.println(Thread.currentThread().getName()+"...if locka");
19                     synchronized(MyLock.lockb)
20                     {
21                     System.out.println(Thread.currentThread().getName()+"...if lockb");                    
22                     }
23                 }
24             }
25         }
26         else
27         {
28             while(true)
29             {
30                 synchronized(MyLock.lockb)
31                 {
32                     System.out.println(Thread.currentThread().getName()+".....else lockb");
33                     synchronized(MyLock.locka)
34                     {
35                     System.out.println(Thread.currentThread().getName()+".....else locka");
36                     }
37                 }
38             }
39         }
40     }
41 }
42 
43 class MyLock
44 {
45     static Object locka = new Object();
46     static Object lockb = new Object();
47 }
48 class  DeadLockTest
49 {
50     public static void main(String[] args) 
51     {
52         Thread t1 = new Thread(new Test(true));
53         Thread t2 = new Thread(new Test(false));
54         t1.start();
55         t2.start();
56     }
57 }

线程间通信

就是多个线程在操作同一个资源,但是操作的动作不同。

wait();//wait(),sleep()区别wait()释放cpu执行权,释放锁。sleep()释放cpu执行权,不释放锁。
notify();
notifyAll();
都使用在同步中,因为要对持有监视器(锁)的线程操作,只有同步才具有锁。
都要定义Object类中,因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。 

生产者消费者代码示例

 1 class ProducerConsumerDemo 
 2 {
 3     public static void main(String[] args) 
 4     {
 5         Resource r = new Resource();
 6 
 7         Producer pro = new Producer(r);
 8         Consumer con = new Consumer(r);
 9 
10         Thread t1 = new Thread(pro);
11         Thread t2 = new Thread(pro);
12         Thread t3 = new Thread(con);
13         Thread t4 = new Thread(con);
14 
15         t1.start();
16         t2.start();
17         t3.start();
18         t4.start();
19 
20         //new Thread(new Producer(r)).start();匿名对象写法。
21         //new Thread(new Producer(r)).start();
22         //new Thread(new Consumer(r)).start();
23         //new Thread(new Consumer(r)).start();
24 
25     }
26 }
27 class Resource
28 {
29     private String name;
30     private int count = 1;
31     private boolean flag = false;
32             
33     public synchronized void set(String name)
34     {
35         while(flag)//对于多个生产者和消费者线程,要定义while判断标记,为的是让被唤醒的线程再一次判断标记。
36             try{this.wait();}catch(InterruptedException e){}
37         this.name = name+"--"+count++;
38 
39         System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
40         flag = true;
41         this.notifyAll();//需要唤醒对方线程定义notifyAll,因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
42     }  
43     public synchronized void out()
44     {
45         while(!flag)
46             try{wait();}catch(InterruptedException e){}
47         System.out.println(Thread.currentThread().getName()+"...消费者.........."+this.name);
48         flag = false;
49         this.notifyAll();
50     }
51 }
52 class Producer implements Runnable
53 {
54     private Resource res;
55 
56     Producer(Resource res)
57     {
58         this.res = res;
59     }
60     public void run()
61     {
62         while(true)
63         {
64             res.set("+商品+");
65         }
66     }
67 }
68 class Consumer implements Runnable
69 {
70     private Resource res;
71 
72     Consumer(Resource res)
73     {
74         this.res = res;
75     }
76     public void run()
77     {
78         while(true)
79         {
80             res.out();
81         }
82     }
83 }

 

JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换成了Condition对象。
该对象可以Lock锁进行获取。
该示例中,实现了本方只唤醒对方操作。

Lock:替代了Synchronized
    lock
    unlock
    newCondition()

Condition:替代了Object中 wait notify notifyAll
    await();
    signal();
    signalAll();

生产者消费者代码1.5示例

  1 import java.util.concurrent.locks.*;
  2 class ProducerConsumerDemo2 
  3 {
  4     public static void main(String[] args) 
  5     {
  6         Resource r = new Resource();
  7 
  8         Producer pro = new Producer(r);
  9         Consumer con = new Consumer(r);
 10 
 11         Thread t1 = new Thread(pro);
 12         Thread t2 = new Thread(pro);
 13         Thread t3 = new Thread(con);
 14         Thread t4 = new Thread(con);
 15         
 16         t1.start();
 17         t2.start();
 18         t3.start();
 19         t4.start();
 20        
 21     }
 22 }
 23 class Resource
 24 {
 25     private String name;
 26     private int count = 1;
 27     private boolean flag = false;
 28             
 29     private Lock lock = new ReentrantLock();//创建一个lock对象
 30 
 31     private Condition condition_pro = lock.newCondition();//返回绑定到此lock实例的Condition实例
 32     private Condition condition_con = lock.newCondition();
 33 
 34 
 35 
 36     public  void set(String name)throws InterruptedException
 37     {
 38         lock.lock();//获取锁。
 39         try
 40         {
 41             while(flag)
 42                 condition_pro.await();
 43             this.name = name+"--"+count++;
 44             System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
 45             flag = true;
 46             condition_con.signal();
 47         }
 48         finally
 49         {
 50             lock.unlock();//释放锁的动作一定要执行。
 51         }
 52     }
 53     public  void out()throws InterruptedException
 54     {
 55         lock.lock();
 56         try
 57         {
 58             while(!flag)
 59                 condition_con.await();
 60             System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
 61             flag = false;
 62             condition_pro.signal();
 63         }
 64         finally
 65         {
 66             lock.unlock();
 67         }
 68         
 69     }
 70 }
 71 class Producer implements Runnable
 72 {
 73     private Resource res;
 74 
 75     Producer(Resource res)
 76     {
 77         this.res = res;
 78     }
 79     public void run()
 80     {
 81         while(true)
 82         {
 83             try
 84             {
 85                 res.set("+商品+");
 86             }
 87             catch (InterruptedException e)
 88             {
 89             }
 90             
 91         }
 92     }
 93 }
 94 
 95 class Consumer implements Runnable
 96 {
 97     private Resource res;
 98 
 99     Consumer(Resource res)
100     {
101         this.res = res;
102     }
103     public void run()
104     {
105         while(true)
106         {
107             try
108             {
109                 res.out();
110             }
111             catch (InterruptedException e)
112             {
113             }
114         }
115     }
116 }

 

优势在于,一个锁里可以绑定多个Condition对象。以前同步里面多个线程拥有一个锁,绑定一个对象,等待唤醒操作也只能由这个对象决定;现在锁可以绑定多个对象,进而有多个等待唤醒操作,操作更加灵活,只需要区分Condition对象即可。最大的优点是可以做到本方只唤醒对方的操作。可以在嵌套中使用,不会出现死锁的情况。


停止线程
stop方法已经过时不再使用。

1.定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可停止线程。
2.使用interrupt(中断)方法。
该方法是清除线程的冻结状态,使线程回到运行状态中来。

join()
当A线程执行到了B线程的join()方法时,A就会等待。等B线程都执行完,A才会执行。join()可以用来临时加入线程执行。

setPriority(int num)
设置线程优先级数。
static int MAX_PRIORITY
          线程可以具有的最高优先级。
static int MIN_PRIORITY
          线程可以具有的最低优先级。
static int NORM_PRIORITY
          分配给线程的默认优先级。

setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

yield()
暂停当前正在执行的线程对象,并执行其他线程。

实际开发中一般多线程写法

 1 class ThreadTest 
 2 {
 3     public static void main(String[] args) 
 4     {
 5         
 6         new Thread()
 7         {
 8             public void run()
 9             {
10                 for(int x=0; x<100; x++)
11                 {
12                     System.out.println(Thread.currentThread().getName()+"....."+x);
13                 }
14             }
15         }.start();
16         
17         
18         for(int x=0; x<100; x++)
19         {
20             System.out.println(Thread.currentThread().getName()+"....."+x);
21         }
22 
23         Runnable r  = new Runnable()
24         {
25             public void run()
26             {
27                 for(int x=0; x<100; x++)
28                 {
29                     System.out.println(Thread.currentThread().getName()+"....."+x);
30                 }
31             }
32         };
33         new Thread(r).start();
34         /*
35         new Thread(new Runnable()
36         {
37             public void run()
38             {
39                 for(int x=0; x<100; x++)
40                 {
41                     System.out.println(Thread.currentThread().getName()+"....."+x);
42                 }
43             }
44         }).start();
45         */
46     }
47 }

 

 

posted on 2013-07-25 00:08  xscn  阅读(345)  评论(0编辑  收藏  举报