黑马程序员——基础知识总结_多线程

来源:互联网 发布:人工智能的编程语言 编辑:程序博客网 时间:2024/05/21 08:41

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——–

从进程讲起:

进程是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径或者叫一个控制单元。线程就是进程中的一个独立控制单元,线程在控制着进程的执行,一个进程至少有一个线程。单核cpu在某个时刻只能执行一个程序,我们看到的多个程序在同时启动,是因为cpu在以我们看不见的速度在各个进程间切换。多线程并发就是一个程序里面同时向cpu申请执行权,cpu执行谁,谁就运行。这个过程是cpu说了算的。我们无法预测。
这里写图片描述
图片来自于:http://www.open-open.com/lib/view/open1371818439463.html

            第一话:创建线程的2种方法
第一种方法继承Thread类:

步骤:
1.定义类继承Thread类。
2.重写run方法。
3.调用线程的start方法启动线程接着执行run方法。

package Thread;//继承Thread类public class Thread_blogDemo  extends Thread{    static int num = 0;    Thread_blogDemo(String name)    {        super(name);//调用父类构造函数    }    //重写run方法    public void run()    {        System.out.println(this.getName()+"---"+num++);// getName()获取线程的名字    }    public static void main(String[] args)    {        Thread_blogDemo t1 = new Thread_blogDemo("t1Run");        Thread_blogDemo t2 = new Thread_blogDemo("t2Run");        Thread_blogDemo t3 = new Thread_blogDemo("t3Run");        //调用start方法开始线程        t1.start();        t2.start();        t3.start();    }}

结果图,2次结果不一样。可以看出cpu调用3个线程没跟着顺序来,这是多线程的表现。
第一种情况:
这里写图片描述
第二种情况:
这里写图片描述

start()方法是用来启动线程然后才调用run()方法,如果直接调用类中的run()方法,则只能算是单线程。不管运行多少次,都是一样的结果。
局限性:

第二种方法实现Runnable接口:

步骤:
1.实现Runnable接口
2.覆盖Runnable接口的run方法
3.通过Thread类建立线程对象。
4.将Ruannable接口的子类对象作为实际参数传递给Thread类的构造方法
5.调用Thread类的start方法,开启线程并调用Runnable接口子类的run方法

package Thread;class person{}public class Thread_blogDemo2 extends person  implements Runnable //继承了person{        String name = null;    public  Thread_blogDemo2(String name)    {        this.name = name;    }    //重写Runnable中的run方法    public void run()    {        System.out.println(name);    }    public static void main(String[] args)    {        Thread_blogDemo2 t1 = new Thread_blogDemo2("Thread1");        Thread_blogDemo2 t2 = new Thread_blogDemo2("Thread2");        Thread_blogDemo2 t3 = new Thread_blogDemo2("Thread3");//调用Thread的start方法启动线程,并执行Runnable子类中的run方法        new Thread(t1).start();        new Thread(t2).start();        new Thread(t3).start();    }}
两种方法的区别:

1.继承Thread方法,使得该类无法继承其他类,不利于今后程序的扩展。而实现Runnable接口的方法解决了这一局限性。
2.继承方式的多线程执行代码放在Thread的子类中,而实现方法则是将多线程执行代码放在Runnable子类中。

                 第二话:多线程同步隐患
问题原因:

当多条语句在操作同一线程时,一个线程还没执行完该线程的所有代码,另外线程参与进来。

/*一个卖票的程序因为同步问题,导致了票数出现了负值*/class Ticket implements Runnable{        private  int tick = 100;        //线程run方法        public  void run()        {            while(true)            {                {                       if(tick>0)                {    //Thread.sleep(30)时线程睡眠30ms为了让问题更容易暴露出来                                      try{Thread.sleep(30);}catch(Exception e){}    //每个线程都有默认的名字,currentThread方法就是得出当前线程名称                            System.out.println(Thread.currentThread().getName()+"sale..." +tick--);                        }                }        }        }    }    class TicketDemoRunnable    {        public static void main(String[] args)        {                    Ticket tic = new Ticket();                    Thread t1 = new Thread(tic);                    Thread t2 = new Thread(tic);                    Thread t3  = new Thread(tic);                    Thread t4 = new Thread(tic);                    t1.start();//执行线程                    t2.start();                    t3.start();                    t4.start();            }        }

结果图:
这里写图片描述

锁的概念:

当一个方法或者语句块持有锁,那么当一个线程在操作该区域的时候其他线程即使获得cpu执行权也无法进入该区域,直到区域运行完代码释放了锁。要保证锁能起到作用,必须保证多个线程用到的是同一个锁
同步问题解决方法:
保证一个线程可以执行完该线程的多条语句,其他线程不能参与。也就是给在相应位置加锁。对此java提出了解决方案——同步代码块,和同步函数。

同步代码块:
class Ticket implements Runnable {    private int tick = 100;    //要保证是同一个锁,而锁可以是任意对象因此用Objec作为锁的对象    Object obj = new Object();    public void run() {        //同步代码块        synchronized (obj) {            while (true) {                if (tick > 0) {                    try {                        Thread.sleep(30);                    } catch (Exception e) {                    }                    System.out.println(Thread.currentThread().getName()                            + "sale..." + tick--);                } else                    return;            }        }    }}

结果图:
这里写图片描述

同步函数:

在函数的返回值之前访问修饰符之后加上一个 synchronized ,这样就能给函数上锁了。同步函数的锁对象是什么呢?很容易想到this可以作为同步函数锁。synchronized void method(this)
但是当函数方法静态的时候,问题出现了,静态不属于任何对象。于是又想到静态进内存时,内存中没有对象但是一定有该类对应的字节码文件对象。类名.class 该对象类型是Class。因此静态函数同步锁可以用 类名.class来保证锁是同一个。synchronized void method(类名.class)

死锁:

死锁的出现是由于同步中包含同步,同步函数含有同步代码块,题目交替持有锁,在某种情况下,两个程序各持有对方所需要的
锁。

package Thread;class Test implements Runnable{    private boolean flag;    Test(boolean flag)    {        this.flag = flag;    }    public void run()    {        if(flag)        {            //a锁            synchronized(MyLock.locka)            {                System.out.println("if locka");                //b锁                synchronized(MyLock.lockb)                {                    System.out.println("if lockb");                }            }        }        else        {            //b锁            synchronized(MyLock.lockb)            {                System.out.println("else lockb");                //a锁                synchronized(MyLock.locka)                {                    System.out.println("else locka");                }            }        }    }}class MyLock{    static Object locka = new Object();    static Object lockb = new Object();}public class sisup {    public static void main(String[] args)    {        //2个对象        Test test = new Test(false);        Test test2 = new Test(true);        //2个多线程        Thread t1 = new Thread(test);        Thread t2 = new Thread(test2);        //线程开始        t1.start();        t2.start();    }}

分析:在某一时刻t1拿到a锁,t2拿到了b锁。此时a请求的是b锁,b请求的是a锁,由于他们还没执行完被加锁的区域,于是锁一直没放。就这样t1拿不到b锁也没有满足释放锁的条件,同样的t2也拿不到a锁,也无法释放a锁。这就出现了死锁。
结果图:
这里写图片描述

0 0
原创粉丝点击