线程

  • 线程

  • 创建线程方式一:继承Thread

  1. A extends Thread
  2. 覆盖Thread中的run方法,将线程的任务代码封装到run方法中。
  3. 在主函数中:

    A a = new A();

    a.start()  //创建并启动线程

 

  • 创建线程方式二:实现Runnable接口

  1. Class A implements Runnable
  2. 覆盖接口中的run方法,将线程的任务代码封装到run方法中。
  3. 在主函数中:

    A a = new A();

    Thread t = new Thread(a);  //接口引用作为参数传到Thread类中

    t.start();

 

  第二种方式的好处:

  1. 将线程的任务从线程的子类中分离出来,进行了单独的封装。
  2. 避免了java单继承的局限性。

 

 

  • 同步

 

同步的好处:解决了线程的安全问题。

同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。

同步的前提:同步中必须有多个线程并使用同一个锁。


 

问题案例

 

 1 class TicketSouce implements Runnable
 2 {
 3     //票的总数
 4     private int ticket=10;
 5     public void run()
 6     {
 7         for(int i=1;i<50;i++)              //每个窗口能卖49张票
 8         {
 9             if(ticket>0)
10             {
11                 //休眠1s秒中,为了使效果更明显,否则可能出不了效果
12                 try {
13                     Thread.sleep(1000);
14                 } catch (InterruptedException e) {
15                     e.printStackTrace();
16                 }
17                 System.out.println(Thread.currentThread().getName()+"号窗口卖出"+this.ticket--+"号票");
18             }
19         }
20     }
21 }
22 public class Test {
23     public static void main(String args[])
24     {
25         TicketSouce mt=new TicketSouce();
26         //基于火车票创建三个窗口
27         new Thread(mt,"a").start();
28         new Thread(mt,"b").start();
29         new Thread(mt,"c").start();
30     } 
31 
32 }
a号窗口卖出9号票
b号窗口卖出10号票
c号窗口卖出8号票
a号窗口卖出7号票
c号窗口卖出7号票
b号窗口卖出6号票
c号窗口卖出5号票
a号窗口卖出4号票
b号窗口卖出3号票
c号窗口卖出2号票
b号窗口卖出1号票
a号窗口卖出0号票
c号窗口卖出-1号票

可以看到a号窗口和和c号窗口都卖出了7号票,并且a号和c号窗口分别卖出了0号和-1号票。造成这种情况的原因是:

1、a线程和b线程在ticket=7的时候,a线程取出7号票以后,ticket还没来的及减1,b线程就取出了ticket,此时ticket还等于7;

2、在ticket=1时,b线程取出了1号票,ticket还没来的及减1,a、c线程就先后进入了if判断语句,这时ticket减1了,那么当a、c线程取票的时候就取到了0号和-1号票。

 

 1 // 两个储户,每个都到银行存钱,每次存100,共存3次
 2 class Bank{
 3     private int sum;
 4     void add(int num){
 5         sum = sum + num;
 6         
 7         //休眠1s秒中,为了使效果更明显,否则可能出不了效果
 8         try{
 9             Thread.sleep(1000);
10             }catch(InterruptedException i){}
11         
12         System.out.println("sum="+sum);
13 }
14 }
15 
16 class Cus extends Bank implements Runnable{
17     
18     public void run(){
19             
20             for(int i=0;i<3;i++){
21                 add(100);
22         }
23     }
24 }
25 
26 public class test {
27 
28     public static void main(String[] args) {
29         Cus c = new Cus();
30         Thread t1 = new Thread(c);
31         Thread t2 = new Thread(c);       //两个进程
32         
33         t1.start();
34         //t1.start();   //一个线程不能开启两次,会抛出无效线程状态异常
35         t2.start();
36         
37     }
38 }

会出现输出值相同的情况,原因是这段代码里的add()方法里没有限制进程进入,(可能会出现当进程0进入时sum变成100,进程0还没打印结果就进入休眠状态,此时进程1进入此方法中,sum值变成200,200ms后进程0唤醒,结果进程0和进程1均输出sum=200的情况),当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。为了解决这种线程安全问题,需要用到同步。

 

  线程安全问题产生的原因:

  1. 多个线程在操作共享的数据。
  2. 操作共享数据的线程代码有多条。

 

解决思路:

将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

 


 

  • 形式一:同步代码块

1 Synchronized(对象)
2 {
3     需要被同步的代码 ;
4 }

问题出在哪就把同步代码块加在哪,不用把run()方法里面的所有内容加进去。

点击查看当synchronized放错位置会导致的结果

 

 

 1 // 两个储户,每个都到银行存钱,每次存100,共存3次
 2 class Bank{
 3     private int sum;
 4     Object obj = new Object();   //只生成一个obj对象当做同步锁,不要放在add()方法里,否则调用一次就会生成一个obj对象
 5     void add(int num){
 6         
 7         synchronized(obj){       //问题出在哪段代码就把在那一段封装到synchronized块中;
 8         sum = sum + num;
 9         
10         try{
11             Thread.sleep(200);
12             }catch(InterruptedException i){}
13         
14         System.out.println("sum="+sum);}
15     }
16 }
17 
18 class Cus extends Bank implements Runnable{
19     
20     public void run(){
21             
22             for(int i=0;i<3;i++){
23                 add(100);
24         }
25     }
26 }
27 
28 public class test {
29 
30     public static void main(String[] args) {
31         Cus c = new Cus();
32         Thread t1 = new Thread(c);
33         Thread t2 = new Thread(c);       //两个进程
34         
35         t1.start();
36         t2.start();
37         
38     }
39 
40 }

 

  •  形式二:同步函数

   即有synchronized关键字修饰的方法。

1   public synchronized void show(){}

 

//      验证同步函数用的是this锁
/*
    函数需要被对象调用。那么函数都有一个所属对象调用,就是this
    所以同步函数使用的锁是this

    
    通过该程序进行验证

    使用两个线程来卖票
    一个线程在同步代码块中
    一个线程在同步函数中
        都在执行卖票操作


*/

class Ticket implements    Runnable
{
    private int tick = 1000;
    
    //Object obj = new Object();

    boolean flag = true;

    public void run()
    {
        if(flag)//第一个进入的线程实行下面代码
        {
                while(true)
                {
                    //synchronized(obj)//由于同步函数的是以this对象为锁的 此处如果使用obj对象作为锁
                                        //则不能实现同步 输出的数据会出现错误 程序的安全性不能得到保证
                    synchronized(this)//和下面的show函数使用同样的锁 可以保证同步
                    {
                        if(tick>0)
                        {
                            try{Thread.sleep(40);}catch(Exception e){}
                            System.out.println(Thread.currentThread().getName() +" .....code..."+ tick--);

                        }
                    }
                }
        }//第二个进入的代码实行下面代码
        else
            while(true)
                show();//this.show();
    
    }
    public synchronized void show()//同步函数 以this为锁
    {
        if(tick>0)
        {
            try{Thread.sleep(40);}catch(Exception e){}
            System.out.println(Thread.currentThread().getName() +" .....show..."+ tick--);
        }        
    }

}


class ThisLockDemo
{
    public static void main(String []args)
    {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
    
        t1.start();//开启第一个线程  但不一定马上执行
        
        t.flag = false;//改变标志
        try{Thread.sleep(40);}catch(Exception e){}//让主线程睡眠40毫秒  保证第一个线程先开始运行 且标志位改变

        t2.start();    
    }
}

 

 

点击查看Synchronized 静态方法和非静态方法的异同

 

posted @ 2015-08-05 23:07  JonthanJ  阅读(112)  评论(0)    收藏  举报