java基础---多线程

java基础—多线程


1、多线程的概念

  • 进程、线程、多线程的概念:

    • 进程:正在进行中的程序。
    • 线程:进程中一个负责程序执行的控制单元(执行路径)。
  • 多线程的好处:解决了多部分代码同时运行的问题。

  • 多线程的弊端:线程太多,会导致效率的降低。

2、创建线程方式一:继承Thread类

  1. 定义一个类继承Thread类。

  2. 覆盖Thread类中的run方法。

  3. 直接创建Thread的子类对象创建线程。

  4. 调用start方法开启线程并调用线程的run方法执行。

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

  1. 定义类实现Runnable接口。

  2. 覆盖接口中的run方法,并将线程的任务代码封装到run方法中。

  3. 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。

  4. 调用线程对象的start方法开启线程。

  5. 实现Runnable接口的好处:

    • 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
    • 避免了java单继承的局限。所以,创建线程的第二种方式较为常用。

4、线程的安全问题

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

    1. 多个线程在操作共享的数据。
    2. 操作共享数据的线程代码有多条。
  • 线程安全问题的解决方案

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

    • 在java中,用同步代码块就可以解决这个问题。(或者在函数上加synchronized修饰符即可)

    • 同步代码块的格式:

     synchronized(对象)
    {
        需要被同步的代码
    }
  • 同步的特点:

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

    • 同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

    • 同步的前提:必须是多个线程并使用同一把锁。

  • 利用同步代码块解决安全问题案例:

    • 需求:储户,两个,每个都到银行存钱,每次存100元,共存三次。
    • 思路:

      1. 定义一个Cons(储户)类并实现Runnable接口;

      2. 复习Cons类中的run方法,执行Bank(银行)类中存储这个动作;

      3. 将存储这个动作上锁,一次只能让有一个客户存钱。

      4. 创建两个线程并开启线程。

    • 代码:
class Bank
{
    private int sum;
    public void add(int num)
    {   
        synchronized(this)//保证每次只有一个线程在调用
        {
            sum+=num;
            System.out.println("sum="+sum);
        }
    }
}
//实现Runnable接口
class Cons implements Runnable
{
    Bank b=new Bank();
    //复写Runnable中的run方法。
    public void run()
    {
        for(int x=0;x<3;x++)
        b.add(100);
    }
}
class BankDemo 
{
    public static void main(String[] args) 
    {
        //把资源进行封装,传入给线程
        Cons c=new Cons();
        //创建两个线程
        Thread t1=new Thread(c);
        Thread t2=new Thread(c);
        //开启线程
        t1.start();
        t2.start();
    }
}
  • 输出结果:

5、多线程下的单例模式

  • 饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。

  • 懒汉式存在安全问题,可以使用同步函数解决。

    • 代码:
class Single
{
    private Single(){}
    private static Single s=null;
    public static Single getInstance()
    {
        //第一次判断,提高了效率,若对象已存在就不用判断锁了
        if(s==null)
        {
            //保证每次只有一个线程在调用
            synchronized(Single.class)
            {
                if(s==null)
                    s=new Single();
            }
        }
        return s;
    }
}

6、死锁

  • 定义:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限地阻塞,因此程序不可能正常终止。

  • 产生死锁的原因:

    1. 因为系统资源不足。
    2. 进程运行推进的顺序不合适。
    3. 资源分配不当等。
  • 产生死锁的四个必要条件:

    1. 互斥条件:一个资源每次只能被一个进程使用。
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
  • 示例代码:

class Test implements Runnable
{
    //flag是标识
    private boolean flag;
    Test(Boolean flag)
    {
        this.flag=flag;
    }
    public void run()
    {
        if(flag)
        {
            while (true)
            {
                //锁a
                synchronized(MyLock.locka)
                {
                    System.out.println(Thread.currentThread().getName()+"……if lacka……");
                }
                //锁b
                synchronized(MyLock.lockb)
                {
                    System.out.println(Thread.currentThread().getName()+"……if lackb……");
                }
            }           
        }
        else 
        {
            while (true)
            {
                //锁b
                synchronized(MyLock.lockb)
                {
                    System.out.println(Thread.currentThread().getName()+"……else lackb……");
                }
                //锁a
                synchronized(MyLock.locka)
                {
                    System.out.println(Thread.currentThread().getName()+"……else lacka……");
                }
            }
        }       
    }
}
class MyLock
{
    public static final Object locka=new Object();
    public static final Object lockb=new Object();
}
class DeadLockDemo 
{
    public static void main(String[] args) 
    {
        Test t1=new Test(true);
        Test t2=new Test(false);
        //创建线程
        Thread d1=new Thread(t1);
        Thread d2=new Thread(t2);
        //开启线程
        d1.start();
        d2.start();
    }
}

7、线程间通信

  • 等待/唤醒机制涉及的方法啊:
    1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
    2. notify():唤醒线程池中的一个线程(任何一个都有可能)
    3. notifyAll():唤醒线程池中的所有线程。
  • wait和sleep的区别:
    1. wait可以指定时间也可以不指定,sleep必须指定时间。
    2. 在同步中,对CPU的执行权和锁的处理不同。
      1. wait:释放执行权,释放锁。
      2. sleep:释放执行权,不释放锁。
  • 示例(生产者-消费者问题):
    • 代码:
class Resource
{
    private String name;
    private String sex;
    private boolean flag=false;
    public synchronized void set(String name,String sex)
    {
        //flag为true时,线程t1冻结,等待唤醒
        if(flag)
            try
            {
                this.wait();    
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        this.name=name;
        this.sex=sex;
        flag=true;
        notify();
    }
    public synchronized void out()
    {
        //flag为false时,线程t2冻结,等待唤醒
        if(!flag)
            try
            {
                this.wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println(name+"……"+sex);
            flag=false;
            notify();
    }
}
//输入,实现Runable接口
class Input implements Runnable
{
    Resource r;
    Input(Resource r)
    {
        this.r=r;
    }
    public void run()
    {
        int x=0;
        while(true)
        {
            if (x==0)
            {
                r.set("小明","男");
            }
            else
            {
                r.set("小红","女");
            }
            x=(x+1)%2;
        }

    }
}
//输出,实现Runnable接口
class Output implements Runnable
{
    Resource r;
    Output(Resource r)
    {
        this.r=r;
    }
    public void run()
    {
        while(true)
        {
            r.out();
        }
    }
}
class ResourceDemo
{
    public static void main(String[] args) 
    {
        //封装资源
        Resource r=new Resource();
        //创建任务
        Input in=new Input(r);
        Output out=new Output(r);
        //创建线程
        Thread t1=new Thread(in);
        Thread t2=new Thread(out);
        //开启线程
        t1.start();
        t2.start();
    }
}
  • 输出结果:
    这里写图片描述

8、JDK1.5新特性

  • JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显式动作。
    • Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
    • lock():获取锁。
    • unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
    • Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
      • Condition接口中的await方法对应于Object中的wait方法。
      • Condition接口中的signal方法对应于Object中的notify方法。
      • Condition接口中的signalAll方法对应于Object中的notifyAll方法。
  • 示例(生产者、多消费者问题:一个Lock、两个Condition)
    • 代码:
import java.util.concurrent.locks.*;
class Resource
{
    private String name;
    private int count=1;
    private boolean flag=false;

    //创建一个锁对象
    Lock lock=new ReentrantLock();

    //通过已有的锁获取该锁上的监视对象
    Condition pro_con=lock.newCondition();
    Condition con_con=lock.newCondition();
    public void set(String name)
    {
        //获取锁
        lock.lock ();
        try
        {
            while(flag)
            {
                try
                {
                    //生产者监视器冻结
                    pro_con.await();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
                this.name=name+count;
                count++;
                System.out.println(Thread.currentThread().getName()+"……生产"+this.name);
                flag=true;
                //唤醒消费者监视器
                con_con.signal();
        }
        finally
        {
            //释放锁
            lock.unlock();
        }
    }
    public void out()
    {
        //获取锁
        lock.lock();
        try
        {
            while(!flag)
            {
                try
                {
                    //消费者监视器冻结
                    con_con.await();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"…… 消费"+name);
            flag=false;
            //唤醒生产者监视器
            pro_con.signal();
        }
        finally
        {
            //释放锁
            lock.unlock();
        }
    }
}
//输入
class Producer implements Runnable
{
    Resource r;
    Producer(Resource r)
    {
        this.r=r;
    }
    public void run()
    {
        while(true)
        {
            r.set("烤鸭");
        }
    }
}
//输出
class Consumer implements Runnable
{
    Resource r;
    Consumer(Resource r)
    {
        this.r=r;
    }
    public void run()
    {
        while(true)
        {
            r.out();
        }
    }
}
class ProducerConsumerDemo2
{
    public static void main(String[] args) 
    {
        Resource r=new Resource();
        Producer pro=new Producer(r);
        Consumer con=new Consumer(r);
        Thread t1=new Thread(pro);
        Thread t2=new Thread(pro);
        Thread t3=new Thread(con);
        Thread t4=new Thread(con);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
  • 输出结果:

猜你喜欢

转载自blog.csdn.net/ly_20104803/article/details/49648867