(九)多线程

一,简介

1.1 线程和进程的区分

  • 进程是一个“进行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。
  • 一个进程可以有多个线程,但必定有一个主线程
  • 一个线程必定只属于一个进程
  • 线程间可以使用进程数据段来进行通信,二进程需要使用IPC(Inter-Progress Communication)机制

1.2 线程的分类

线程=守护线程+用户线程

Java在start()前调用thread.setDarmon(true)可以把用户线程变为守护线程

1.3 使用多线程的优点

  • 提高应用程序的响应,对图形化界面更有意义,增强用户体验
  • 提高计算机CPU利用率
  • 改善程序结构,将长且复杂的程序分为多个线程独立运行有利于理解和修改

二、java中多线程的创建和使用

  • 一个线程对象只能执行一次start()

2.1 创建线程的两种方法(继承Thread和实现Runable)

/*
继承Thread的线程的使用方法
①.创建一个继承Thread的子类
②.重写run()方法
③.主线程中创建子类对象(对象作为参数传给Thread的构造器,从而new一个Thread)
④.调用对象的start()方法:启动此线程---->调用相应run()方法
*/
public class TestThread {
    public static void main(String[] args) {
        // 3.创建子类对象
        SubThread st = new SubThread();
        // 4.启动子线程
        st.start();
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "\t:" + i);
            if (i % 10 == 0) {
                Thread.yield();
            }
        }
    }
}
// 使用Thread子类创建方法:
// 1.创建Thread子类
class SubThread extends Thread {
    // 2.重写run()方法
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "\t:" + i);
        }
    }
}
/*
实现Runnable接口的创建方法:
①创建一个实现Runnable接口的子类
②重写run();
③主线程中创建子类对象,创建Thread对象并作为形参传给Thread的构造器
④通过Thread对象调用方法
*/
public class TestSubRunnabe {
    public static void main(String[] args) {
        SubRunnable s1=new SubRunnable();
        //SubRunnable s2=new SubRunnable();
        Thread t1=new Thread(s1);
        Thread t2=new Thread(s1);
        t1.start();//执行的是s的run()
        t2.start();
    }
}
//使用Runable创建方法:
class SubRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "\t:" + i);
            if (i % 10 == 0) {
                Thread.yield();
            }
        }
    }
}

2.2 两个方法之间的关系:

  • Thread 实现了 Runnable 接口
  • 实现的方式优于继承,因为单继承的局限性
  • 如果多个线程操作同一份资源(共享数据),更适合使用实现的方式(只创建了一个对象)

2.3 Thread类的主要方法

  • start():void;启动线程并执行run()
  • run():void;子线程要执行的代码
  • currentThread():Thread;调取当前线程。返回当前Thread
  • getName():String;获取此线程名字
  • setName(String str):void;设置此线程名字
  • yield();调用此方法的线程释放当前CPU的执行权
  • join():void;在A线程中调用B线程的join方法,A线程停止执行直至B执行完
  • isAlive():boolean;是否活着
  • sleep(long ms):void;显式让当前线程睡眠(ms)

2.4 线程调度与设置优先级

Ⅰ .时间片式
同优先级
Ⅱ.抢占式
高优先级
getPriority():int;获取优先级
setPriority(int newPriority):void;改变优先级

三 、线程生命周期

枚举类:Thread.state枚举类:
NEW:新建
TIMED_WAITING:
WAITING:就绪
RUNNABLE:运行
BLOCKED:阻塞
TERMINATED:终止

四、线程的同步

4.1 为什么要使用线程同步

  • 由于一个线程在操作共享数据中,未操作完的情况下,另一个进程参与进来,导致共享数据存在安全问题

4.2 解决方式一:同步代码块

/*
 *synchronized(同步监视器){
 *   //需要被同步的代码块(操作共享数据的代码)
 *}
 *
 * 银行有一个账户:
 * 有两个储户分别向同一个账户存3000,每次1000,每次存完打印账户余额
 * 
 * 有无多线程:两个储户,有
 * 有无共享数据:一个账户,有---->考虑线程同步
 */
public class TestAccount {
    public static void main(String[] args) {
        //这里只创建了一个Customer对象,所以其内部可以使用this当锁
        Customer c=new Customer(new Account());
        Thread t1=new Thread(c);
        Thread t2=new Thread(c);
        t1.setName("customer1");
        t2.setName("customer2");
        t1.start();
        t2.start();
    }
}
class Account {
    double balance = 0;
    public Account() {
    }
    public void deposit(double amt) {
        balance += amt;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ",current balance is: " + balance);
    }
}
class Customer implements Runnable {
    Account account;
    public Customer(Account acc){
        this.account=acc;
    }
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            synchronized (this) {
                account.deposit(1000);
            }
        }
    }
}
说明:
  1. 共享数据:多个线程共同操作的同一个数据(变量)。
  2. 同步监视器:任何一个类的对象。哪个线程获取此监视器,就执行被同步的代码。俗称:锁。
  3. 在实现的方式中,可以考虑用this来充当锁,继承方式实现多线程,因为多个线程中有多个对象,所以不能用this当锁,可以使用一个static 对象。

4.3 解决方式二:同步方法(只能用在实现方式)

/*
 *public synchronized void method(){
 *    //需要被同步的代码块
 *}
 *1. 此同步方法能保证当其中一个线程执行此方法时,其他线程在外等待直至此线程执行完此方法
 *2. 此方法的锁为this,所以只能用在实现方式
 *
 * 银行有一个账户:
 * 有两个储户分别向同一个账户存3000,每次1000,每次存完打印账户余额
 * 
 * 有无多线程:两个储户,有
 * 有无共享数据:一个账户,有---->考虑线程同步
 */
public class TestAccount1 {
    public static void main(String[] args) {
        //创建一个账户
        Account1 account = new Account1();
        //创建两个客户
        Customer1 cus1 = new Customer1(account, "cus1");
        Customer1 cus2 = new Customer1(account, "cus2");
        cus1.start();
        cus2.start();
    }
}
class Account1 {
    double balance = 0;
    public Account1() {
    }
    //此方法执行时默认使用this当锁,在同一时刻,只能有一个线程执行该方法
    public synchronized void deposit(double amt) {
        balance += amt;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ",current balance is: " + balance);
    }
}
class Customer1 extends Thread {
    Account1 account;
    public Customer1(Account1 account, String name) {
        super(name);
        this.account = account;
    }
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.deposit(1000);
        }
    }
}
说明:
  • 对于一般方法,可以使用同步代码块,默认为this
  • 对于静态方法,可以使用同步代码块,默认当前类本身当锁: ClassName.class
  • 线程同步的弊端:由于同一个时间只有一个线程访问共享数据,效率变低
  • 释放锁的时机:代码块执行完毕或者执行wait()

4.4 死锁(DeadLock):

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

解决办法:专门的算法,原则,尽量减少同步资源的定义
死锁的一个例子:

public class TestDeadLock {
    static StringBuffer sb1 = new StringBuffer();
    static StringBuffer sb2 = new StringBuffer();
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                synchronized (sb1) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sb1.append("a");
                    //代码执行到这里时,握着sb1锁,等待sb2锁
                    synchronized (sb2) {
                        sb2.append("b");
                        System.out.println(sb1);
                        System.out.println(sb2);
                    }
                }
            }
        }.start();
        new Thread() {
            public void run() {
                synchronized (sb2) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sb1.append("c");
                    //代码执行到这里时,握着sb2锁,等待sb1锁
                    synchronized (sb1) {
                        sb2.append("d");
                        System.out.println(sb1);
                        System.out.println(sb2);
                    }
                }
            }
        }.start();
    }
}

五、线程的通信
- wait():void:当前线程挂起并放弃CPU,同步资源,排队等候,被唤醒后继续执行wait()后的代码
- notify():void :唤醒正在排队的等待同步资源的最高优先级线程结束等待
- notifyAll():void:唤醒正在排队等待资源的全部线程结束等待

java.lang.Object下的此三个方法只有在synchronized方法或代码块中才能使用,否则报异常:java.lang.IllegalMonitorStateException

多线程例题:
1. 两个线程交替打印数据
2. 生产者与消费者的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值