java多线程

一、程序

是为完成特定任务、用某种语言编写的一组指令的集合。指一段静态的代码,是静态对象。

二、进程

进程是程序(任务)的一次执行过程,是一个动态的过程,有它自身的产生、存在和消亡的过程。进程持有资源(共享内存,共享文件)和线程。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

三、线程

线程是系统中最小的执行单元。同一进程中有多个线程,线程是一个程序内部的一条执行路径。

线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)。线程切换的开销小。

一个进程中的多个线程共享相同的内存单元/内存地址空间(堆,方法区)。他们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能会带来安全的隐患。

线程的交互包括互斥(竞争资源)、同步

一个java应用程序java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程、异常处理线程。当然如果发生异常,会影响主线程。

四、并行与并发

并行:多个cpu同时执行多个任务

并发:一个cpu(采用时间片)同时执行多个任务,比如:秒杀、多个人做同一件事

五、Java对线程的支持

java.lang包提供了Thread类和Runnable接口,这两个都有一个public void run()方法。这个方法为我们提供了线程实际执行工作的代码。start()方法:①启动线程,②调用线程的run()方法

六、Thread常用方法

long millis的单位是毫秒,也就是需要等待或睡眠的最长时间。nanos可以精确到微秒

下面的方法是Thread的对象的方法

还有一些Thread的静态方法,也就是不需要实例化对象,可以通过Thread类直接调用的方法

七、线程优先级的设置

线程的调度:

1、时间片:每个线程轮流执行时间片时间

2、抢占式:高优先级的线程抢占cpu

Java的调度方法:

同优先级线程组成先进先出队列(先到先服务),使用时间片策略。对于高优先级,使用优先调度的抢占式策略

线程的优先级等级:

MAX_PRIORITY:10          MIN_PRIORITY:1          NORM_PRIORITY:5(默认)

涉及的方法:

getPriority():返回线程优先级

setPriority(int newPriority):改变线程的优先级

说明:

线程创建时继承父线程的优先级。低优先级只是获得调度的概率低,并非一定是在高优先级之后才被调用

八、线程的创建

1、继承Thread类

步骤:新建一个类继承Thread类,重写run()方法,新建类的实例,实例.start()

2、实现Runnable接口

步骤:新建一个类实现Runnable接口,重写run()方法,新建这个类的实例,将这个实例作为参数传递给Thread,新建一个Thread对象,由Thread对象.start()

上述两种方式的比较:

1、开发中优先选择实现Runnable接口的方式。原因:①:实现的方式没有类的单继承②:实现的方式更适合来处理多个线程有共享数据的情况(只需要创建一个实现Runnable接口的类的对象,然后将这个对象当做参数传递给多个不同的Thread)

2、联系:pyublic class Thread implements Runnable

3、两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中

线程通信:wait()/notify()/notifyAll():此三个方法定义在object类中

线程分类:分为用户线程和守护线程,守护线程是用来服务用户线程的

线程的生命周期

3、实现Callable接口

与使用Runnable相比,Callable功能更强大些。

(1)相比Runnable的run()方法,Callable的call()方法可以有返回值

(2)方法可以抛出异常

(3)支持泛型的返回值

(4)需要借助FutureTask类,比如获取返回结果 。Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。FutureTask是Future接口的唯一实现类。FutureTask同时实现了Runnable、Future接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

九、线程安全问题及解决

例子:创建是三个窗口卖票,总票数100张。使用实现Runnable接口的方式

问题1:卖票过程中,出现了重票、错票,出现了线程安全问题

问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票

解决:当一个线程a在操作车票的时候,其他线程不能参与进来。直到线程a操作完之后其他线程才可以开始操作,这种情况即使a出现了阻塞,也不能被改变。在Java中,通过同步机制来解决线程的安全问题。

方式一:同步代码块

synchronized(同步监视器){

//需要被同步的代码

说明:操作共享数据的代码,即为需要被同步的代码。

共享数据:多个线程共同操作的变量

同步监视器:锁,任何一个类的对象都可以充当锁(要求:多个线程必须同用一把锁

使用继承Thread的方式实现

package duoxianceng;

public class Thread100 extends Thread{
    private static int i=100;   //必须是static,保证多个实例操作的是同一个变量
    static Object obj=new Object();  //必须是static,要保证锁是唯一的
    @Override
    public void run() {
        while(true){
            synchronized (Thread100.class){//Thread100.class得到类Thread100的对象,类的对象是惟一的
            //synchronized(obj){
                if(i>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("分线程"+getName()+"     "+i);
                    i--;
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread100 t1=new Thread100();
        t1.setName("窗口一");
        Thread100 t2=new Thread100();
        t2.setName("窗口二");
        Thread100 t3=new Thread100();
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

使用实现Runnable的方式实现

package duoxianceng;

public class Runna100 implements Runnable{
    Object obj=new Object();//为了保证锁是唯一的,不能定义在函数体内
    int i=100;//为了保证多个线程操作的是同一个i
    @Override
    public void run() {
        //在实现Runnable接口创建多线程的方式中,可以采用this充当同步监视器
        while (true) { //while不能被包含在synchronized里面,放在里面就相当于一个线程拿着锁一顿while循环,i>0退出之后已经无法执行了,其他线程没有工作
            synchronized (this){
            //synchronized(obj) {
                if (i > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("分线程" + Thread.currentThread().getName() + "     " + i);
                    i--;
                }
            }
        }
    }

    public static void main(String[] args) {
        Runna100 r1=new Runna100();
        Thread t1=new Thread(r1,"窗口1");
        Thread t2=new Thread(r1,"窗口2");
        Thread t3=new Thread(r1,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

方式二:同步方法

同步方法仍然涉及到同步监视器,只是不需要我们显示的声明

非静态的同步方法,同步监视器:this。静态的同步方法,同步监视器:当前类本身

继承Thread的同步方法

package duoxianceng;

public class Threadfunc extends Thread{
    static int i=100;
    public void run(){
        while(true){
            show();
        }
    }
    static synchronized void show(){// 该方法必须是static,将同步的操作单独成一个方法,同步监视器:Threadfunc.class
        if(i>0){
            System.out.println(Thread.currentThread().getName()+i);
            i--;
        }
    }

    public static void main(String[] args) {
        Threadfunc t1=new Threadfunc();
        t1.setName("窗口一");
        Threadfunc t2=new Threadfunc();
        t2.setName("窗口二");
        Threadfunc t3=new Threadfunc();
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

实现Runnable的同步方法

package duoxianceng;

public class Runnafunc implements Runnable{
    int i=100;
    @Override
    public void run() {
        while(true){
            show();
        }
    }
    public synchronized void show(){//同步监视器:this
        if(i>0){
            System.out.println(Thread.currentThread().getName()+i);
            i--;
        }
    }

    public static void main(String[] args) {
        Runnafunc rf=new Runnafunc();
        Thread t1=new Thread(rf,"线程1");
        Thread t2=new Thread(rf,"线程2");
        Thread t3=new Thread(rf,"线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}

方法三、lock

package duoxianceng;

import java.util.concurrent.locks.ReentrantLock;

public class LockTry implements Runnable{
    private int ticket=100;
    //1.实例化ReentrantLock
    private ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        while(true){//while要放在最外面,不能放在try内部,那样的话就只能有一个线程在运行了
            try{
                //2.调用lock方法
                lock.lock();
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(ticket>0){
                        System.out.println(Thread.currentThread().getName()+ticket);
                        ticket--;
                    }else{
                        break;
                    }
                }finally {
                    //3.调用解锁方法
                    lock.unlock();
                    }
        }

    }

    public static void main(String[] args) {
        LockTry lt=new LockTry();
        Thread t1=new Thread(lt,"线程1");
        Thread t2=new Thread(lt,"线程2");
        Thread t3=new Thread(lt,"线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}

synchronized与Lock对比

1、Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,除了作用域会自动释放

2、lock只有代码块锁,synchronized有代码块锁和方法锁

3、使用lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的可扩展性

优先使用顺序:lock--->同步代码块----->同步方法

十、线程死锁问题

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法:专门的算法、原则。尽量减少同步资源的定义。尽量避免嵌套同步。

例题1:

银行有一个账户,有两个储户分别向同一个账户存3000元。每次存1000,存3次。每次存完打印账户余额。

package duoxianceng;

class Account{
    public double balance=0;  //账户余额
    public Account(double balance) {
        this.balance = balance;
    }

    //存钱的方法
    public synchronized void AddMoney(double m){
        if(m>0){
            balance+=m;
            System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
        }

    }

}
class Customer extends Thread{
    private Account account;   //保证两个储户操作的是同一个账户
    public Customer(Account t){
        this.account=t;
    }    //保证两个储户操作的是同一个账户

    @Override
    public void run() {
        for(int i=0;i<3;i++){
            account.AddMoney(1000);
        }
    }
}
public class Bank {
    public static void main(String[] args) {
        Account a=new Account(0);
        Customer c=new Customer(a);
        Customer c2=new Customer(a);
        c.setName("甲");
        c2.setName("乙");
        c.start();
        c2.start();
    }

}

例题2(线程通信):

使用两个线程打印1-100,线程1和线程2交替打印。

涉及到的三个方法:

wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器

notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒多个线程中优先级最高的

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程

注意:1、wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中

           2、wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会异常

           3、wait(),notify(),notifyAll()三个方法是定义在java.lang.Object中

package duoxianceng;

public class Xianchengtongxin implements Runnable{
    private int num=0;
    private Object obj=new Object();
    @Override
    public void run() {
        while(true){
            synchronized (obj){
            //synchronized (this){//this是类Xianchengtongxin的对象,作为同步监视器,必须唯一
                //唤醒线程
                obj.notify();
                //this.notify();
                if(num<=100){
                    try {
                        //sleep()会使线程阻塞,但他不会释放同步监视器
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"---"+num);
                    num++;
                    //使得调用如下wait()方法的线程进入阻塞状态,线程会释放同步监视器
                    try {
                        obj.wait();
                        //this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }

            }

        }

    }

    public static void main(String[] args) {
        Xianchengtongxin x=new Xianchengtongxin();
        Thread t1=new Thread(x,"线程1");
        Thread t2=new Thread(x,"线程2");
        t1.start();
        t2.start();
    }
}

sleep()和wait()的异同?

1、相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态

2、不同点:<1>两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()

                    <2>调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中

                    <3>关于是否释放同步监视器:如果两个方法都使用在同步方法或同步代码块中,sleep()不会释放同步监视器,wait()会释放同步监视器。

例题3(线程通信)

生产者/消费者问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产,如果店中没有产品,店员会告诉消费者等一下,如果店中有产品了再通知消费者取走产品。

package duoxianceng;

class Clerk{

    private int product=0;
    public synchronized void produceProduct() {
        this.notify();//用来唤醒消费者线程
        if(product<20){
            product++;
            System.out.println(Thread.currentThread().getName()+"正在生产第"+product+"个产品");

        }else{
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void consumeProduct() {
        if(product>0){
            System.out.println(Thread.currentThread().getName()+"正在消费第"+product+"个产品");
            product--;
            notify();//用来唤醒生产者线程
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Producer extends Thread{
    private Clerk cle;

    public Producer(Clerk cle) {
        this.cle = cle;
    }

    @Override
    public void run() {
        System.out.println(getName()+"开始生产产品");
        while(true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cle.produceProduct();
        }

    }
}
class Consumer extends Thread{
    private Clerk cle;

    public Consumer(Clerk cle) {
        this.cle = cle;
    }

    @Override
    public void run() {
        System.out.println(getName()+"开始消费产品");
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cle.consumeProduct();
        }


    }
}
public class Productor {
    public static void main(String[] args) {
        Clerk cl=new Clerk();
        Producer p=new Producer(cl);
        Consumer c1=new Consumer(cl);
        p.start();
        c1.start();

    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值