黑马程序员-多线程

---------------------- ASP.Net+Android+IOS开发</a>、 .Net培训、期待与您交流! ----------------------


多线程

当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。比如main方法会启动一个线程,后台的垃圾回收线程等

多线程的优势:
1)进程间不能共享内存,但线程之间共享内存非常容易
2)系统创建进程需要为该进程重新分配系统资源,创建线程代价小得多,所以效率高
3)Java语言内置多线程功能支持,简化Java的多线程编程


继承Thread类创建线程

定义Thread类的子类,并重写run方法,创建Thread子类的实例,用线程对象的start方法启动线程

每启动一个线程都要创建一个Thread类的子类实例,多个线程无法处理同一份资源,多个线程不能共享实例属性

/**
其中的两个线程分别处理各自类中的资源
*/

class FirstThread extends Thread
{
    public static void main(String[] args)
    {
        new FirstThread().start();
        new FirstThread().start();

        for(int i = 0; i < 100; i++)
        {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }

    public void run()
    {
        for(int i = 0; i < 100; i++)
        {
            //Thread对象的getName()返回当前该线程的名字
            System.out.println(getName()+"---"+i);
        }
    }
}


实现Runnable接口创建线程

定义Runnable接口的实现类,重写run方法。创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,调用线程对象的start方法来启动该线程

Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run方法

/**
向两个线程传入同一个target,会共享target的成员变量

同时,带来了线程安全问题:两个线程处理同一份资源ticket,存在这种可能:当ticket为1时,第一个线程进入for循环在①处停住了,第二个线程又进入了for循环并成功卖出了最后一张票,ticket变成0,但第一个线程还在for循环内部,又卖出了不存在的0,就出现了问题。

可以通过同步代码块、同步方法、同步锁解决这个问题
*/

class Ticket implements Runnable
{
    private int ticket = 100;

    public void run()
    {
        for(; ticket > 0; ticket--)
        {
            //①
            System.out.println(Thread.currentThread().getName() + "+++++" + ticket);
        }
    }
}

class ThreadTest2
{
    public static void main(String[] args)
    {
        Ticket t = new Ticket();

        //传入同一个对象t,两个线程共享t的成员变量
        new Thread(t).start();
        new Thread(t).start();
    }
}


同步代码块

同步代码块可以解决多线程中的安全问题,同步代码块需要同步监视器,同步监视器可以是任意对象

synchronized(obj)
{
    ...
    //同步代码块
}
obj就是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,任何时刻只能有一条线程可以获得对同步监视器的锁定


同步方法

同步方法就是使用synchronized关键字来修饰某个方法,该方法称为同步方法

对于同步方法而言,无需显示指定同步监视器,同步方法的同步监视器是this,也就是该对象本身

静态同步方法的监视器是Class对象

/**
同步方法
*/

class SynchronizedMethodTest
{
    public static void main(String[] args)
    {
        Ticket t = new Ticket();

        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }
}

class Ticket implements Runnable
{
    private int tickets = 1000;

    public void run()
    {
        while(true)
        {
            sellTicket();
        }
    }

    //同步方法持有的监视器是this
    public synchronized void sellTicket()
    {
        if(tickets > 0)
        {
            System.out.println(Thread.currentThread().getName() + "+++" + (tickets--));
        }
        else
        {
            System.exit(1);
        }
    }
}


同步锁

从1.5之后,Java提供了另外一种线程同步的机制:它通过显示定义同步锁对象来实现同步

Method:Interface Lock
void lock()    //加锁
void unlock()    //解锁

Class ReentrantLock:

ReentrantLock类实现了Lock接口,重写了lock、unlock方法

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
/**
同步锁
*/

class Ticket implements Runnable
{
    private int tickets = 1000;
    //定义ReentrantLock
    Lock lock = new ReentrantLock();

    public void run()
    {
        while(true)
        {
            //加锁
            lock.lock();
            try
            {
                if(tickets > 0)
                {
                    System.out.println(Thread.currentThread().getName()+"+++"+tickets--);
                }
                else
                {
                    System.exit(1);
                }
            }
            //解锁,在finally块中可以保证执行
            finally
            {
                lock.unlock();
            }
        }
    }
}

class ThreadTest2
{
    public static void main(String[] args)
    {
        Ticket t = new Ticket();

        new Thread(t).start();
        new Thread(t).start();
    }
}


死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁,两个线程各持一把锁,想要对方的锁,但各自必须要到锁才可以放锁,就死锁了

/**
死锁
*/

//创建两个监视器
class MyLock
{
    static Object lock1 = new Object();
    static Object lock2 = new Object();
}

class Thread1 implements Runnable
{
    public void run()
    {
        while(true)
        {
            synchronized(MyLock.lock1)
            {
                System.out.println("Thread1...lock1");
                synchronized(MyLock.lock2)
                {
                    System.out.println("Thread1...lock2");
                }
            }
        }
    }
}

class Thread2 implements Runnable
{
    public void run()
    {
        while(true)
        {
            synchronized(MyLock.lock2)
            {
                System.out.println("Thread2...lock2");
                synchronized(MyLock.lock1)
                {
                    System.out.println("Thread2...lock1");
                }
            }
        }
    }
}

class DeadlockTest
{
    public static void main(String[] args)
    {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }
}


线程的协调运行

根类Object提供的wait()、notify()、notifyAll()三个方法,必须由同步监视器对象来调用,分两种情况:
1)对于同步方法,因该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法
2)对于同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这三个方法

wait()    //使当前线程等待,会释放锁
notify()    //唤醒在此同步监视器上等待的任意一个线程
notifyAll()    //唤醒在此同步监视器上等待的所有线程

当使用Lock对象来保证同步时,Java提供了一个Condition接口来保持协调,如下:

import java.util.concurrent.locks.*;
/**
生产者消费者例子

多个生产者生产产品,多个消费者购买产品。实现逻辑是生产一个产品,消费一个产品,使用Condition控制线程的协调运行

本例中使用的Lock和Condition,如果使用同步方法,则把await改为wait(),signal改为notifyAll
*/

class ProducerAndConsumerTest
{
    public static void main(String[] args)
    {
        //启动两个生产者线程
        new Thread(new Producer()).start();
        new Thread(new Producer()).start();

        //启动两个消费者线程
        new Thread(new Consumer()).start();
        new Thread(new Consumer()).start();
    }
}

class Producer implements Runnable
{
    public void run()
    {
        while(true)
        {
            Product.in();
        }
    }
}

class Consumer implements Runnable
{
    public void run()
    {
        while(true)
        {
            Product.out();
        }
    }
}

class Product
{
    private static int num = 0;    //产品编号

    private static boolean flag = false;    //是否有产品

    private static Lock lock = new ReentrantLock();


    private static Condition con_in = lock.newCondition();
    private static Condition con_out = lock.newCondition();

    public static void in()
    {
        lock.lock();
        try
        {    
            //使用while而不是if的原因:当等待线程被唤醒时,仍需要通过flag判断是否存在商品
            while(flag)
            {
                try{con_in.await();}catch(Exception e){}    //线程等待并释放锁
            }

            //打印一行语句,说明已生产商品
            System.out.println(Thread.currentThread().getName() + "+++" + (++num));

            //设置标记为true,说明存在商品
            flag = true;
            //唤醒消费者的一个线程(随机)
            con_out.signal();
        }
        finally
        {
            lock.unlock();
        }
    }

    public static void out()
    {
        lock.lock();
        try
        {
            while(!flag)
            {
                try{con_out.await();}catch(Exception e){}
            }
            System.out.println(Thread.currentThread().getName() + "---" + num);
            flag = false;
            con_in.signal();
        }
        finally
        {
            lock.unlock();
        }
    }
}



---------------------- ASP.Net+Android+IOS开发</a>、 .Net培训、期待与您交流! ----------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值