黑马程序员——Java练习笔记——多线程

来源:互联网 发布:linux中more命令 编辑:程序博客网 时间:2024/06/16 13:01

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

1.进程,线程的概念
进程:正在执行的程序
线程:进程中一个负责程序执行的控制单元(执行路径)
2.多线程的好处和弊端
好处:解决了多部分代码同时运行的问题
弊端:降低了运行效率
原因:多个应用程序同时执行都是CPU在做着快速切换完成的,这个切换是随机的,CPU在切换时需要耗费时间的,因此降低了运行效率。
3.创建线程的两种方式及示例
3-1.继承Thread类
1、定义一个类继承Thread类。
2、覆盖Thread类中的run方法。
3、直接创建Thread的子类对象创建线程。
4、调用start方法开启线程并调用线程的任务run方法执行。
示例代码:

class ThreadDemo1{    public static void main(String[] args)     {        Demo d1 = new Demo("黎明");        d1.start();        Demo d2 = new Demo("李白");        d2.start();    }}class Demo extends Thread{    private String name;    Demo(String name)    {        this.name = name;    }    public void run()    {        System.out.println("名字:"+name+"--线程名字:"+Thread.currentThread().getName());    }}

结果:
这里写图片描述

3-2.实现Runnable接口
1、定义类实现Runnable接口。
2、覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3、通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。
所以要在线程对象创建时就必须明确要运行的任务。
4、调用线程对象的start方法开启线程。
示例代码:

class ThreadDemo2 {    public static void main(String[] args)     {        Demo d = new Demo();        Thread t1 = new Thread(d);        Thread t2 = new Thread(d);        t1.start();        t2.start();    }}class Demo implements Runnable{    public void run()    {        show();    }    public void show()    {        for (int i=0;i<10 ;i++ )        {            System.out.println(i+"---线程名字:"+Thread.currentThread().getName());        }    }}

结果:
这里写图片描述

实现Runnable接口的好处:
1,将线程的任务从线程的子类中分离出来,进行了单独的封装,
按照面向对象的思想将任务封装成对象。
2,避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。

4.线程安全问题示例,原因,解决方案
示例:模拟4个线程同时卖100张票
代码:

class ThreadDemo3 {    public static void main(String[] args)     {        Ticket t = new Ticket();        Thread t1 = new Thread(t);        Thread t2 = new Thread(t);        Thread t3 = new Thread(t);        Thread t4 = new Thread(t);        t1.start();        t2.start();        t3.start();        t4.start();    }}class Ticket implements Runnable{    private int num = 100;    public void run()    {        show();    }    public void show()    {        while (true)        {            if (num>0)            {                try                {                    Thread.sleep(10);                }                catch (InterruptedException e)                {                    e.printStackTrace();                }                System.out.println("线程名字:"+Thread.currentThread().getName()+"--num:"+ num--);            }        }    }}

结果:
这里写图片描述
原因分析:
出现上图安全问题的原因在于Thread-0通过了if判断后,在执行到“num–”语句之前,num此时仍等于1。
CPU切换到Thread-1、Thread-2、Thread-3之后,这些线程依然可以通过if判断,从而执行“num–”的操作,因而出现了0、-1、-2的情况。

线程安全问题产生的原因:
1、多个线程在操作共享的数据。
2、操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

线程安全问题的解决方案:
思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}

同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。

修改后的代码:

class ThreadDemo4 {    public static void main(String[] args)     {        Ticket t = new Ticket();        Thread t1 = new Thread(t);        Thread t2 = new Thread(t);        Thread t3 = new Thread(t);        Thread t4 = new Thread(t);        t1.start();        t2.start();        t3.start();        t4.start();    }}class Ticket implements Runnable{    private int num = 1000;    Object obj = new Object();    public void run()    {        show();    }    public void show()    {        while (true)        {            synchronized(obj)            {            if (num>0)            {                try                {                    Thread.sleep(5);                }                catch (InterruptedException e)                {                    e.printStackTrace();                }                System.out.println("线程名字:"+Thread.currentThread().getName()+"--num:"+ num--);            }            }        }    }}

结果:
这里写图片描述

5.利用同步代码块解决安全问题案例
需求:储户,两个,每个都到银行存钱,每次存100,共存三次
代码:

class ThreadDemo5 {    public static void main(String[] args)     {        Cus s = new Cus();        Thread t1 = new Thread(s);        Thread t2 = new Thread(s);        t1.start();        t2.start();    }}class Cus implements Runnable{    private Bank b = new Bank();    public void run()    {        for (int i=0;i<3 ;i++ )        {            b.add(100);        }    }}class Bank{    private int sum;    public void add(int num)    {        synchronized(this)        {            sum = sum +num;            System.out.println("sum=: "+sum);        }    }}

结果:
这里写图片描述

6.安全问题的另一种解决方案:同步函数
代码:

class ThreadDemo6 {    public static void main(String[] args)     {        Cus s = new Cus();        Thread t1 = new Thread(s);        Thread t2 = new Thread(s);        t1.start();        t2.start();    }}class Cus implements Runnable{    private Bank b = new Bank();    public void run()    {        for (int i=0;i<3 ;i++ )        {            b.add(100);        }    }}class Bank{    private int sum;    public synchronized void add(int num)    {               sum = sum +num;            System.out.println("sum=: "+sum);       }}

结果:
这里写图片描述

同步函数和同步代码块的区别:
1、同步函数的锁是固定的this。
2、同步代码块的锁是任意的对象。
建议使用同步代码块。

由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

7.死锁
死锁:
同步中嵌套同步,可能会发生,是由于 两个线程相互等待 对方已被锁定的资源。
循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。
示例代码:

class Ticket implements Runnable{    private  int tick = 1000;    Object obj = new Object();    boolean flag = true;    public  void run()    {        if(flag)        {            while(true)            {                synchronized(obj)                {                    show();                }            }        }        else            while(true)                show();    }    public synchronized void show()//this    {        synchronized(obj)        {            if(tick>0)            {                try{Thread.sleep(10);}catch(Exception e){}                System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);            }        }    }}class  ThreadDemo7{    public static void main(String[] args)     {        Ticket t = new Ticket();        Thread t1 = new Thread(t);        Thread t2 = new Thread(t);        t1.start();        try{Thread.sleep(10);}catch(Exception e){}        t.flag = false;        t2.start();    }}

结果:
这里写图片描述

原因分析:
由上图可以看到程序已经被锁死,无法向下执行。
由上图代码可以看到,run方法中的同步代码块需要获取obj对象锁,才能执行代码块中的show方法。
而执行show方法则必须获取this对象锁,然后才能执行其中的同步代码块。
当线程t1获取到obj对象锁执行同步代码块,线程t2获取到this对象锁执行show方法。同步代码块中的show方法因无法获取到this对象锁无法执行,show方法中的同步代码块因无法获取到obj对象锁无法执行,就会产生死锁。

避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,
保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。

8.线程间通信
多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。

等待/唤醒机制涉及的方法:
①wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
②notify():唤醒线程池中的一个线程(任何一个都有可能)。
③notifyAll():唤醒线程池中的所有线程。

①wait可以指定时间也可以不指定。sleep必须指定时间。
②在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。

0 0
原创粉丝点击