黑马程序员:我对多线程的总结以及练习(重点)

1.概述
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。一个进程中至少有一个线程。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。
多线程存在的意义:充分利用cpu的空闲时间,提高进程的整体运行效率。
 
2.创建线程的两种方式
第一种:继承Thread类
步骤:
    1.子类覆盖父类中的run方法,将线程运行的代码存放在run中。
    2.建立子类对象的同时线程也被创建。
    3.通过调用start方法开启线程。
示例代码:
[java] view plaincopy
1.class Demo extends Thread  
2.{  
3.    public void run()  
4.    {  
5.        for(int i = 0;i<60;i++)  
6.        {  
7.            System.out.println(“demo run---+”i);  
8.        }  
9.    }  
10.}  
11.  
12.class ThreadDemo  
13.{  
14.    public static void main(String[] args)  
15.    {  
16.            Demo d = new Demo();//创建一个线程  
17.            d.start();//调用start方法执行该线程的run方法  
18.    }  
19.}  
第二种:实现Runnable接口
步骤:
    1.子类覆盖接口中的run方法
    2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
    3.Thread类对象调用start方法开启线程。
代码示例:
[java] view plaincopy
1.class Test implement Runnable  
2.{  
3.    public void run()//子类覆盖接口中的run方法  
4.    {  
5.        for(int x=0; x<60; x++)  
6.        {  
7.            System.out.println(“run..."+x);  
8.        }  
9.    }  
10.  
11.}  
12.  
13.  
14.class ThreadTest   
15.{  
16.    public static void main(String[] args)   
17.    {  
18.        Test te = new Test();实现Runnable接口的对象  
19.        Thread t1 = new Thread(te);将那个对象作为参数传递到Thread构造函数  
20.        Thread t2 = new Thread(te);  
21.        t1.start();//调用Thread类的start方法开启线程  
22.        t2.start();  
23.    }  
24.}  
两种线程创建方式的区别:
    继承Thread类创建对象:
        1.Thread子类无法再从其它类继承(java语言单继承)。
        2.编写简单,run()方法的当前对象就是线程对象,可直接操作。
    使用Runnable接口创建线程:
        1.可以将CPU,代码和数据分开,形成清晰的模型。
        2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。
        3.有利于保持程序的设计风格一致。
    继承Thread类线程代码存放于Thread子类run方法中;
    实现Runnable接口线程代码存在于接口的子类run方法中,而且这种方式避免了单继承的局限性,在实际应用中,几乎都采取第二种方式。
 
问题:为什么要将Runnable接口的子类对象传递给Thread的构造函数?
答:因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象。
 
3.线程的四种状态

sleep方法需要指定睡眠时间,单位是毫秒。
冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。
 
4.线程的对象获取与名称的操作
线程都有自己的默认名称,格式为:Thread-编号 该编号从0开始。
操作方法:
static Thread currentThread():获取当前线程对象。
getName(): 获取线程名称。
setName或者构造函数:设置线程名称。
示例代码:
 
[java] view plaincopy
1.class Test extends Thread  
2.{  
3.    Test(String name)//构造方法设置线程名称  
4.    {  
5.        super(name);//继承父类方法  
6.    }  
7.    public void run()//执行代码  
8.    {  
9.        for(int x=0; x<60; x++)  
10.        {  
11.            System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);  
12.        }  
13.    }  
14.  
15.}  
16.  
17.class ThreadTest   
18.{  
19.    public static void main(String[] args)   
20.    {  
21.        Test t1 = new Test("one---");//调用构造函数设置线程名称  
22.        Test t2 = new Test("two+++");  
23.        t1.start();  
24.        t2.start();  
25.  
26.        for(int x=0; x<60; x++)  
27.        {  
28.            System.out.println("main....."+x);  
29.        }  
30.    }  
31.}  
 
5.线程的安全
导致安全问题的出现的原因:
    1.多个线程访问出现延迟
    2.线程随机性
线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的
解决方法:同步(synchronized)
格式:
    synchronized(对象)
    {
        需要同步的代码;
    }
同步可以解决安全问题的根本原因就是在那个对象上,该对象如同锁的功能。
4个窗口售票示例:
[java] view plaincopy
1.class Ticket implements Runnable  
2.{  
3.    private  int tick = 1000;  
4.    Object obj = new Object();//建立一个obj类对象,供synchronized作参数  
5.    public void run()  
6.    {  
7.        while(true)  
8.        {  
9.            synchronized(obj)//同步锁  
10.            {  
11.                if(tick>0)  
12.                    System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);  
13.  
14.            }  
15.        }  
16.    }  
17.}  
18.  
19.class  TicketDemo2  
20.{  
21.    public static void main(String[] args)   
22.    {  
23.  
24.        Ticket t = new Ticket();//创建Ticket对象供Thread对象作构造函数的参数用  
25.  
26.        Thread t1 = new Thread(t);  
27.        Thread t2 = new Thread(t);  
28.        Thread t3 = new Thread(t);  
29.        Thread t4 = new Thread(t);  
30.        //开始4个线程  
31.        t1.start();  
32.        t2.start();  
33.        t3.start();  
34.        t4.start();  
35.  
36.    }  
37.}  

同步的前提:
|---->同步需要两个或者两个以上的线程
|---->多个线程使用的是同一个锁
未满足这两个条件,不能称其同步。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行的效率。
同步函数
    格式:在函数上加上synchronized修饰符即可。
    同步函数使用的锁:函数需要被对象调用,那么函数都有一个所属对象引用,就是this。
   所以同步函数使用的锁是this。
关于死锁
    同步中嵌套同步,但是锁却不同,就会产生死锁
 
如:A线程持有A锁,B线程持有B锁,让A线程去抢夺B锁,B线程去抢夺A锁
[java] view plaincopy
1.class Dead implements Runnable  
2.{  
3.    private booleanb = false;  
4.    Dead(booleanb)  
5.    {  
6.        this.b = b;  
7.    }  
8.    public void run()  
9.    {  
10.        while(true)  
11.        {  
12.            if(b)  
13.            {  
14.                synchronized(Locks.locka)  
15.                {//0线程,持有了A锁  
16.                    System.out.println(Thread.currentThread().getName()+"locka...+..if");  
17.                    //等待B锁  
18.                    synchronized(Locks.lockb)  
19.                    {  
20.                        System.out.println(Thread.currentThread().getName()+"lockb...+..if");  
21.                    }  
22.                }  
23.            }  
24.            else  
25.            {  
26.                synchronized(Locks.lockb)  
27.                {  
28.                    //1线程就进来了,持有了B锁  
29.                    System.out.println(Thread.currentThread().getName()+"lockb...+..else");  
30.                    synchronized(Locks.locka)//等待获得A锁  
31.                    {  
32.                        System.out.println(Thread.currentThread().getName()+"locka...+..else");  
33.  
34.                    }  
35.                }  
36.            }  
37.        }  
38.    }  
39.}  
40.//创造锁  
41.class Locks  
42.{  
43.    public static Object locka = new Object();  
44.    public static Object lockb = new Object();  
45.}  
46.class DeadLock  
47.{  
48.    public static void main(String[]args)  
49.    {  
50.        Dead d1 = new Dead(true);  
51.        Dead d2 = new Dead(false);  
52.        Thread t1 = new Thread(d1);  
53.        Thread t2 = new Thread(d2);  
54.        t1.start();  
55.        t2.start();  
56.    }  
57.}  
 
6.线程间通信
多个线程操作同一个资源,但是操作的动作不同
 
注意:wait();notify();notifyAll()都使用在同步中,因为要对持有监视器的线程操作,所以要使用在同步中,因为只有同步才具有锁。
问题:wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
    1.这些方法存在与同步中。
    2.使用这些方法时必须要标识所属的同步的锁。
    3.锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
注意:不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。

问题:wait(),sleep()有什么区别?
    wait():释放cpu执行权,释放锁。
    sleep():释放cpu执行权,不释放锁。
 
新特性:JDK1.5之后新增了java.until.concurrent.locks这个包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。
生产和消费实例中升级:
JDK1.5 中提供了多线程升级解决方案。
    将同步Synchronized替换成现实Lock操作。
    将Object中的wait,notify notifyAll,替换了Condition对象。
    该对象可以Lock锁 进行获取。
    该示例中,实现了本方只唤醒对方操作。
代码如下:
[java] view plaincopy
1.import java.util.concurrent.locks.*;  
2.  
3.class ProducerConsumerDemo   
4.{  
5.    public static void main(String[] args)   
6.    {  
7.        Resource r = new Resource();  
8.  
9.        Producer pro = new Producer(r);  
10.        Consumer con = new Consumer(r);  
11.  
12.        Thread t1 = new Thread(pro);  
13.        Thread t2 = new Thread(pro);  
14.        Thread t3 = new Thread(con);  
15.        Thread t4 = new Thread(con);  
16.  
17.        t1.start();  
18.        t2.start();  
19.        t3.start();  
20.        t4.start();  
21.  
22.    }  
23.}  
24.  
25.class Resource  
26.{  
27.    private String name;  
28.    private int count = 1;  
29.    private boolean flag = false;  
30.    private Lock lock = new ReentrantLock();  
31.  
32.    private Condition condition_pro = lock.newCondition();  
33.    private Condition condition_con = lock.newCondition();  
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.  
45.            System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);  
46.            flag = true;  
47.            condition_con.signal();  
48.        }  
49.        finally  
50.        {  
51.            lock.unlock();//释放锁的动作一定要执行。  
52.        }  
53.    }  
54.    public  void out()throws InterruptedException  
55.    {  
56.        lock.lock();  
57.        try  
58.        {  
59.            while(!flag)  
60.                condition_con.await();  
61.            System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);  
62.            flag = false;  
63.            condition_pro.signal();  
64.        }  
65.        finally  
66.        {  
67.            lock.unlock();  
68.        }  
69.          
70.    }  
71.}  
72.  
73.class Producer implements Runnable  
74.{  
75.    private Resource res;  
76.  
77.    Producer(Resource res)  
78.    {  
79.        this.res = res;  
80.    }  
81.    public void run()  
82.    {  
83.        while(true)  
84.        {  
85.            try  
86.            {  
87.                res.set("+商品+");  
88.            }  
89.            catch (InterruptedException e)  
90.            {  
91.            }  
92.              
93.        }  
94.    }  
95.}  
96.  
97.class Consumer implements Runnable  
98.{  
99.    private Resource res;  
100.  
101.    Consumer(Resource res)  
102.    {  
103.        this.res = res;  
104.    }  
105.    public void run()  
106.    {  
107.        while(true)  
108.        {  
109.            try  
110.            {  
111.                res.out();  
112.            }  
113.            catch (InterruptedException e)  
114.            {  
115.            }  
116.        }  
117.    }  
118.}  
 
7.停止线程
1.定义循环结束标记(run方法结束)
    因为线程运行代码一般都是循环,只要控制了循环即可。
2.使用interrupt(中断)方法。
    该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
 
特殊情况:
    当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
    Thread类提供该方法 interrupt();
测试代码:
[java] view plaincopy
1.class StopThread implements Runnable  
2.{  
3.    private boolean flag =true;  
4.    public synchornized void run()  
5.    {  
6.        while(flag)  
7.        {  
8.            try  
9.            {  
10.                wait();//调用线程等待  
11.            }  
12.            catch(InterruptedException e)  
13.            {  
14.                System.out.println(Thread.currentThread().getName()+"...Exception");  
15.                flag = false;//在异常中控制线程的运行  
16.            }  
17.            System.out.println(Thread.currentThread().getName()+"....run");  
18.        }  
19.    }  
20.    public void changeFlag()  
21.    {  
22.        flag = false;  
23.    }  
24.}  
25.  
26.class  StopThreadDemo  
27.{  
28.    public static void main(String[] args)   
29.    {  
30.        StopThread st = new StopThread();  
31.          
32.        Thread t1 = new Thread(st);  
33.        Thread t2 = new Thread(st);  
34.  
35.  
36.        t1.setDaemon(true);  
37.        t2.setDaemon(true);  
38.        t1.start();  
39.        t2.start();  
40.  
41.        int num = 0;  
42.  
43.        while(true)  
44.        {  
45.            if(num++ == 60)  
46.            {  
47.                //st.changeFlag();  
48.                t1.interrupt();//调用interrupt方法来中断线程,抛出异常  
49.                t2.interrupt();  
50.                break;  
51.            }  
52.            System.out.println(Thread.currentThread().getName()+"......."+num);  
53.        }  
54.        System.out.println("over");  
55.    }  
56.}  

8.线程类的其他方法
守护线程
    setDaemon():将该线程标记为守护线程或用户线程,守护线程就相当于后台线程,当前台线程结束时,后台线程跟着也结束。
    注意:该方法必须在启动线程前调用。 
          该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
用法示例代码如下:
[java] view plaincopy
1.class StopThread implements Runnable  
2.{  
3.    public  void run()//原本run方法是一个死循环,但是将线程定义为守护线程后,主线程结束,访问run方法的线程立即也结束了  
4.    {  
5.        while(true)  
6.        {         
7.            System.out.println(Thread.currentThread().getName()+"....run");  
8.        }  
9.    }  
10.}  
11.class  StopThreadDemo  
12.{  
13.    public static void main(String[] args)   
14.    {  
15.        StopThread st = new StopThread();     
16.        Thread t1 = new Thread(st);  
17.        Thread t2 = new Thread(st);  
18.        //将t1,t2定义为守护线程  
19.        t1.setDaemon(true);  
20.        t2.setDaemon(true);  
21.        t1.start();//开始线程  
22.        t2.start();  
23.        int num = 0;  
24.        while(true)  
25.        {  
26.            if(num++ == 60)  
27.            {  
28.                break;  
29.            }  
30.            System.out.println(Thread.currentThread().getName()+"......."+num);  
31.        }  
32.        System.out.println("over");//主线程结束,t1,t2也结束  
33.    }  
34.}  

join()
抢夺cpu执行权,适用于临时加入线程用,先执行完,其他线程才执行。
测试代码如下:
[java] view plaincopy
1.class Demo implements Runnable  
2.{  
3.    public void run()  
4.    {  
5.        for(int x=0; x<70; x++)  
6.        {  
7.            System.out.println(Thread.currentThread().getName()+"....."+x);  
8.        }  
9.    }  
10.}  
11.class  JoinDemo  
12.{  
13.    public static void main(String[] args) throws Exception  
14.    {  
15.        Demo d = new Demo();  
16.        Thread t1 = new Thread(d);  
17.        Thread t2 = new Thread(d);  
18.        t1.start();  
19.        t1.join();//t1获取cpu的执行权,主线程处于冻结状态,只有t1结束主线程才能恢复运行状态  
20.        t2.start();  
21.        //t1.join();主线程冻结,t1,t2交替运行,t1结束,主线程才继续  
22.        for(int x=0; x<80; x++)  
23.        {       }  
24.        System.out.println("over");  
25.    }  
26.}  

setPriority()
更改线程的优先级。三个优先级分别为:
    MAX_PRIORITY(最高优先级,10)
    MIN_PRIORITY(最低优先级,1)
    NORM_PRIORITY(默认优先级,5)
首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。在其他情况下,线程优先级被设定为指定的newPriority 和该线程的线程组的最大允许优先级相比较小的一个。
 
yeild()
暂停当前正在执行的线程对象,并执行其他线程。(使线程交替执行)
 
个人总结:
    线程的运行状态与线程之间的通信个人很难理解,可能还得多多写代码操练才行,线程的操作给我的感觉就是你必须要考虑得很全面,每个线程的执行状态,每个线程读取的数据什么的都必须要考虑清楚,这要才能避免线程的安全问题。还是觉得线程这一块比较难,得多下点工夫。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭