JavaSE基础---多线程

 进程:正在进行的程序。其实就是一个应用程序运行时的内存分配空间。

线程:进程中一个程序执行控制单元,一条执行路径。进程负责的事应用程序的空间的标识,线程负责的事应用程序的执行顺序。

进程和线程的关系:一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、和变量。

JVM启动时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。

当产生垃圾时,收垃圾的动作,是不需要主线程来完成的,应为这样,会出现主线程中的代码执行停止,去运行垃圾会收器代码,效率较低,所以由一个单独的线程来负责垃圾回收。

  

一、创建多线程程序的两种方法:Thread类和Runnable接口

  (一)通过继承Thread类来完成

    1、步骤:

      (1)定义类继承Thread类;

      (2)复写run方法,将要让线程运行的代码都储存到run方法中

      (3)通过创建Thread类的子类对象,创建线程对象

      (4)调用线程的start方法,开启线程,并自动执行run方法,注意:start()方法会执行两条命令,1、开启线程,2、执行run方法

    2、线程的状态:

      (1)被创建:start();

      (2)运行:具备执行资格,同时具备执行权。

      (3)冻结:sleep(time),wait()---notify()唤醒;线程释放执行权,同时释放执行资格;

      (4)零时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权

      (5)消亡:stop()

  (二)通过实现Runnable接口来完成(如果一个类已经继承了其他类,就不能继承Thread类了,Java的单继承局限性。于是只能对该类进行额外的功能扩展)

    1、步骤:

      (1)定义类实现Runnbale接口;

      (2)覆盖接口中的run方法(封装线程要执行的代码);

      (3)通过Thread类创建线程对象;

      (4)将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数;(明确线程要执行的代码run方法)

      (5)调用Thread对象的start()方法,开启线程,并执行Runnable接口的子类对象中的run()方法。

    2、Runnable接口

      (1)避免了Java单继承的局限性

      (2)Thread类描述时,内部定义的run方法,也来自于Runnable接口

      (3)Runnable接口将线程要执行的任务封装成了对象

  (三)案例

    1、买票

      (1)通过继承Thread类

 1 class Ticket extends Thread    
 2 {
 3     private static int num = 100;//出售票的总数,如果不设置静态,每创建一个对象,就会有属于自己的100张票,最终会出售400张票,所以设置为静态,让他成为所有对象共享的数据。
 4     public void run()
 5     {
 6         while(true)
 7         {
 8             if(num>0)
 9             {
10                 System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
11             }
12         }
13     }
14 }
15 
16 class TicketDemo
17 {
18     public static void main(String[] args)
19     {
20         Ticket t1 = new Ticket();
21         Ticket t2 = new Ticket();
22         Ticket t3 = new Ticket();
23         Ticket t4 = new Ticket();
24 
25         t1.start();
26         t2.start();
27         t3.start();
28         t4.start();
29     }
30 }

 

      (2)通过实现Runnable接口

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

 

 

二、多线程的安全问题

  当一个线程在执行操作共享数据的多条带没带过程中,其他线程参与了运算,会导致线程安全问题的产生。

  1、出现安全问题的两个前提

    (1)多个线程在操作共享数据

    (2)操作共享数据的线程代码有多条

  2、解决方案

     只要让某一线程在执行操作共享数据的多条代码时,让其他程序不能执行数据的操作就可以了。即同步

三、同步

  1、定义同步的两个前提

    (1)必须要有两个或两个以上的线程,才需要同步

    (2)多个线程必须保证使用的是同一个锁

  2、同步的两种表现形式

    (1)同步代码块:

        格式:

synchronized(对象)//可以是任意对象,该对象其实就是锁
{
    //需要被同步的代码
}

        案例:

          a、上边买票的案例优化 

 1 class Ticket implements Runnable    
 2 {
 3     private int num = 100;//出售票的总数
 4     Object obj = new Object();
 5     public void run()
 6     {
 7         while(true)
 8         {
 9                 synchronized(obj)
10                 {
11                 if(num>0)
12                 {
13                         try {
14                             Thread.sleep(10);
15                         } catch (InterruptedException e) {
16                              
17                         }
18                     System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
19                   }
20             }
21         }
22     }
23 }
24 
25 class TicketDemo
26 {
27     public static void main(String[] args)
28     {
29         Ticket t = new Ticket();
30         Thread t1 = new Thread(t);
31         Thread t2 = new Thread(t);
32         Thread t3 = new Thread(t);
33         Thread t4 = new Thread(t);
34 
35         t1.start();
36         t2.start();
37         t3.start();
38         t4.start();
39     }
40 }

 

        b、一张银行卡,两个人每人每次到银行存100,存三次

 1 class Bank
 2 {
 3     private int sum;
 4     private Object obj = new Object();
 5     public void add(int num)
 6     {
 7         synchronized(obj)//注意:不能再此处直接new Object(),这样的话,每次创建一个对象,就会new一个新的Object,用的就不是同一个锁了,
 8         {
 9             sum = sum+num;
10             try{Thread.sleep(10);}catch (InterruptedException e){}
11             System.out.println(Thread.currentThread().getName()+"...sum="+sum);
12         }
13     }
14 }
15 
16 class Cus implements Runnable
17 {
18     private Bank b = new Bank();
19     public void run()
20     {
21         for (int x=3; x>0; x--) {
22              b.add(100);
23         }
24     }
25 }
26 
27 class BankDemo
28 {
29     public static void main(String[] args)
30     {
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 }

 

 

    (2)同步函数:就是将同步关键字定义在函数上,让函数具备同步性。

        同步函数锁使用的锁就是this

        当同步函数被static修饰时,静态函数的调用不需要对象,但是静态函数所属类的字节码文件在加载进内存时,这个字节码文件就被封装成了对象。所以此时同步函数所使用的锁就是该类的字节码文件对象。(类名.class)

        案例:上边银行存款的例子、

 1 class Bank
 2 {
 3     private int sum;
 4     private Object obj = new Object();
 5     public synchronized void add(int num)
 6     {
 7         //synchronized(obj)//注意:不能再此处直接new Object(),这样的话,每次创建一个对象,就会new一个新的Object,用的就不是同一个锁了,
 8         //{
 9             sum = sum+num;
10             try{Thread.sleep(10);}catch (InterruptedException e){}
11             System.out.println(Thread.currentThread().getName()+"...sum="+sum);
12         //}
13     }
14 }
15 
16 class Cus implements Runnable
17 {
18     private Bank b = new Bank();
19     public void run()
20     {
21         for (int x=3; x>0; x--) {
22              b.add(100);
23         }
24     }
25 }
26 
27 class BankDemo
28 {
29     public static void main(String[] args)
30     {
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 }

 

  2、同步代码块和同步函数的区别

    (1)同步代码块使用的锁可以是任意对象。

        同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

  3、用同步代码块还是同步函数

    在一个类中只有一个同步时,可以使用同步函数,但是如果有多个同步,就必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

  4、使用同步的好处和弊端

    (1)好处:解决了线程的安全问题

    (2)弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

        死锁:常见情景之一:同步的嵌套

        

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

 

四、单例模式涉及的多线程问题

  1、饿汉式

1 class Single
2 {
3     private static finally Single s = new Single();//静态方法访问的内容必须是静态的。
4     private Single(){}
5     public static Single getInstance()//因为单例不能在其他类中创建单例类对象,所以getInstance方法必须是静态的,可以直接被Single类调用。
6     {
7         return s;
8     }
9 }

  2、懒汉式(单例模式的延迟加载形式)

    当多线程访问懒汉式时,懒汉式的getInstance方法中对共享数据s进行了多条语句的操作,所以容易出现线程安全问题。为了解决该问题,加入同步机制,但是每次调用懒汉式对象,都会判断synchronized锁,降低了效率。于是通过双重判断的形式解决效率问题。

 1 class Single
 2 {
 3     private static Single s = null;
 4     private Single(){}
 5     public static Single getInstance(){
 6         if(s==null)
 7         {
 8             synchronized(Single.class)//此处的锁是懒汉式类字节码文件对象
 9             {
10                 if(s==null)
11                     s = new Single();
12                 }
13         }
14         return s;
15     }
16 }

 五、多线程

  1、线程间通讯

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

      思路:(1)将资源封装成对象;

         (2)将不同线程执行的任务(其实就是run方法)也封装在不同的对象中。

    多生产者多消费者问题

 1 /*
 2  * 资源对象
 3  */
 4 class Resource
 5 {
 6     private String name;
 7     private int count =1;
 8     private Boolean flag = false;
 9     public synchronized void set(String name)
10     {
11         while(flag)
12         {
13             try{this.wait();}catch(InterruptedException e) {}
14         }
15         this.name = name+this.count;
16         System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
17         count++;
18         flag = true;
19         notifyAll();//t0,t1的锁是pro对象,t2,t3的锁是con对象,这里的notifyAll()只是唤醒以Pro对象为锁的线程
20     }
21     public synchronized void out()
22     {
23         while(!flag)
24         {
25             try{this.wait();}catch(InterruptedException e) {}
26         }
27         System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
28         flag = false;
29         notifyAll();
30     }
31 }
32 
33 /*
34  * 生产者对象
35  */
36 class Producer implements Runnable
37 {
38     private Resource r;
39     Producer(Resource r)
40     {
41         this.r = r;
42     }
43     public void run()
44     {
45         while(true)
46         {
47             r.set("烤鸭");
48         }
49     }
50 }
51 
52 class Consumer implements Runnable
53 {
54     private Resource r;
55     Consumer(Resource r)
56     {
57         this.r = r;
58     }
59     public void run()
60     {
61         while(true)
62         {
63             r.out();
64         }
65     }
66 }
67 
68 /*
69  * 消费者对象
70  */
71 class ProducerConsumerDemo
72 {
73     public static void main(String[] args)
74     {
75         Resource r = new Resource();
76         Producer pro = new Producer(r);
77         Consumer con = new Consumer(r);
78         Thread t0 = new Thread(pro);
79         Thread t1 = new Thread(pro);
80         Thread t2 = new Thread(con);
81         Thread t3 = new Thread(con);
82         t0.start();
83         t1.start();
84         t2.start();
85         t3.start();
86     }
87 }    

     相关知识:

      等待唤醒机制

        wait();  将同步中的线程处于冻结状态。释放了执行权,释放了执行资格。同时将线程对象存储到线程池中。

        notify();  唤醒线程池中某一个等待的线程。如果A锁上的线程被wait了,那么此时该线程就处于A锁的线程池中,只能由A锁的notify()唤醒。

        notifyAll(); 唤醒线程池中所有线程。

        以上三个方法都被定义在Object类中,是因为这些方法存在于同步中,调用时都必须要标识所属的同步的锁,而锁可以是任意对象,所以任意对象都可以使用的方法一定定义在Object类中。

      wait和sleep的区别:
        (1)wait可以指定等待时间也可以不指定时。不指定时间,只能由对应的notify()或者notifyAll()来唤醒

            sleep必须指定时间,时间到了就自动从冻结状态转成运行状态(临时阻塞状态)

        (2)wait线程会释放执行权,而且线程会释放锁

            sleep线程会释放执行权,但不是不释放锁

      线程的停止:

        (1)通过stop方法停止,但是该方式已过时

        (2)让线程运行的代码结束,也就是结束run方法。一般run方法里一定有循环,所以只要结束循环即可。a、定义循环的结束标记;b、如果线程处于冻结状态,是不能督导标记的,这时就需要通过Thread类的interrupt方法,将其冻结状态强制清除,让线程恢复到具备执行资格的状态,这时该线程就可以读到结束标记,并结束线程。

    java.lang.Thread类相关方法:

        interrupt()        中断线程,其实是将线程从冻结状态强制恢复到运行状态中,让线程具备CPU的之赐你个资格。

        setPriority(int newPriority) 更改线程的优先级

        getPriority();       返回线程的优先级

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

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

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

        join()           临时加入一个线程时可以调用join()方法

六、面试题

  1、以下代码是否有错误?

1 class Test implements Runnable
2 {
3     public void run(Thread t)
4     {}
5 }

    解析:第一行有错误,class应该被abstract修饰,public void run(Thread t)方法没有覆盖Runnable接口的run方法,是函数的重载,所以Test类中有抽象方法,是抽象类。

  2、以下代码是否有错误,如果没有,运行结果是什么?

class ThreadTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run()
            {
                System.out.println("runnable run");
            }
        })
        {
            public void run() {
                System.out.println("subThread run");
            }
        }.start();
    }
}

    解析:没有错误,结果是subThread run,

posted @ 2018-04-17 21:05  爱你如初  阅读(136)  评论(0编辑  收藏  举报