多线程

一.进程和线程

1.进程

  • 执行程序的一次执行过程,它是一个动态的概念,
  • 一个应用程序的运行就可以被看做是一个进程,有独立的内存空间和系统资源
  • 通常一个进程中可以包含若干个线程

2.线程

  • 是cpu调度和分派的基本单位,是应用程序的执行实例,运行中的实际的任务执行者

3.多线程

1)定义

  • 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”,多个线程交替占用CPU资源,而非真正的并行执行

2)多线程好处

  • 充分利用CPU的资源
  • 简化编程模型
  • 带来良好的用户体验

4.主线程

1)Thread类

  • Java提供了java.lang.Thread类支持多线程编程

2)主线程

  • main()方法即为主线程入口
  • 产生其他子线程的线程
  • 必须最后完成执行,因为它执行各种关闭动作
public class TestMain {
    //main方法自己就是一个线程,会开启栈区
    public static void main(String[] args) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前的线程名
        System.out.println(t.getName());
        //可以自行赋值线程名
        t.setName("自己赋值的线程名");
        System.out.println(t.getName());
        //或者把线程名写进参数里
        Thread th=new Thread("子线程1");
        System.out.println(th.getName());
    }
}

效果图

main
自己赋值的线程名
子线程1

二.线程的创建和启动

1.继承Thread类创建线程

1)步骤

  • 定义MyThread类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
    -注:
  • 多个线程交替执行,不是真正的“并行”
  • 线程每次执行时长由分配的CPU时间片长度决定
  • 直接调用run方法只有主线程main一条执行路径

2)代码块演示

public class Test extends Thread{
    @Override
    public void run() {//线程工作的主体
        Thread t=Thread.currentThread();
        for (int i = 1; i <=3 ; i++) {
            System.out.println(t.getName() + ":" + "第"+i+"次");
        }
    }
    public static void main(String[] args) {
        Test t=new Test();
        Test t1=new Test();
        t.setName("线程1");
        t1.setName("线程2");
        t.start();
        t1.start();
    }
}

效果图

线程2:1次
线程1:1次
线程2:2次
线程1:2次
线程2:3次
线程1:3

2.实现Runnable接口创建线程

1)步骤

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

2)代码块演示

public class Test implements Runnable{
    @Override
    public void run() {//线程工作的主体
        Thread t=Thread.currentThread();
        for (int i = 1; i <=3 ; i++) {
            System.out.println(t.getName() + ":" + "第"+i+"次");
        }
    }
    public static void main(String[] args) {
        Test t=new Test();
        Thread th=new Thread(t,"线程1");
        Thread th1=new Thread(t,"线程2");
        th.start();
        th1.start();
    }
}

效果图

线程2:1次
线程1:1次
线程2:2次
线程1:2次
线程2:3次
线程1:3

3.Callable接口

Callable:能够获取线程执行结果

1)步骤

必须重写call方法

2)代码块演示

public class TestCall implements Callable<Object> {
    //实现callable接口,可以泛型
    public int ticket=10;
    @Override
    public Object call() {//用call方法实现返回值
        return ticket;
    }
    public static void main(String[] args) throws Exception{
//        TestCall t=new TestCall();
//        try {
//            Object call = t.call();
//        } catch (Exception e) {
//            e.printStackTrace();
//        }这种不能直接实现多线程
        TestCall t=new TestCall();
        FutureTask task=new FutureTask(t);
        //用FutureTask吧callable实现类装进来
        Thread tt=new Thread(task);
        //再把task放入线程对象中
        tt.start();
        //线程运行完之后获取返回值
        Object o = task.get();
        //get来接收返回值
        System.out.println(o);
    }
}

效果图

10

4.比较创建线程的方式

在这里插入图片描述

5.练习

在这里插入图片描述

public class Exercise implements Runnable{
    public void run(){
        Thread t = Thread.currentThread();
        for (int i = 0; i <20 ; i++) {
            System.out.println(1+i+"你好,来自线程"+t.getName());
        }
    }
    public static void main(String[] args) {
        Exercise e=new Exercise();
        Thread t=new Thread(e);
        Thread t1=new Thread(e);
        t.start();
        t1.start();
    }
}

三.线程的状态

在这里插入图片描述

四.线程调度

线程调度指按照特定机制为多个线程分配CPU的使用权
在这里插入图片描述

1.线程优先级

  1. 线程优先级由1~10表示,1最低,默认优先级为5
  2. 优先级高的线程获得CPU资源的概率较大
public class Exercise extends Thread{
    public void run(){
        Thread t = Thread.currentThread();
        for (int i = 0; i <3 ; i++) {
            System.out.println(1+i+"你好,来自线程"+t.getName());
        }
    }
    public static void main(String[] args) {
        Exercise e=new Exercise();
        Exercise e1=new Exercise();
        e.setPriority(10);//一种用数字表达优先级
        e1.setPriority(MIN_PRIORITY);//一种用字母表达优先级
        e.start();
        e1.start();
    }
}

效果图

1你好,来自线程Thread-0
1你好,来自线程Thread-1
2你好,来自线程Thread-0
2你好,来自线程Thread-1
3你好,来自线程Thread-0
3你好,来自线程Thread-1

2.线程休眠

  1. 让线程暂时睡眠指定时长,线程进入阻塞状态
  2. 睡眠时间过后线程会再进入可运行状态
  3. millis为休眠时长,以毫秒为单位
  4. 调用sleep()方法需处理InterruptedException异常
public class Test extends Thread{
    @Override
    public void run() {//线程工作的主体
        Thread t=Thread.currentThread();
        for (int i = 1; i <=3 ; i++) {
            System.out.println(t.getName() + ":" + "第"+i+"次");
            try {
                Thread.sleep(2000);//每次打印完一句延迟2秒再次打印
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Test t=new Test();
        t.start();
    }
}

效果图

Thread-0:1次
Thread-0:2次
Thread-0:3

3.线程的强制运行

  1. 使当前线程暂停执行,等待其他线程结束后再继续执行本线程
  2. millis:以毫秒为单位的等待时长
  3. nanos:要等待的附加纳秒时长
  4. 需处理InterruptedException异常
    在这里插入图片描述
public class Test extends Thread{
    @Override
    public void run() {//线程工作的主体
        Thread t=Thread.currentThread();
        for (int i = 1; i <=3 ; i++) {
            System.out.println(t.getName() + ":" + "第"+i+"次");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Test t=new Test();
        t.start();
        for (int i = 1; i <4 ; i++) {
            System.out.println(Thread.currentThread().getName()+"第"+i+"次");
            if (i==2) t.join(1);
            //如果某个线程里有join,在join时处于阻塞状态,
            // 一旦等待参数里的毫秒数超时则该线程直接退出阻塞状态,又继续运行
            //或者说join里的线程在join前一直是阻塞状态,
            // 则等待参数里的毫秒数超时则不继续等待,
            // 被join的线程继续运行
        }
    }
}

效果图

main第1次
Thread-0:1次
main第2次
Thread-0:2次
Thread-0:3次
main第3

4.线程的礼让

  1. 暂停当前线程,允许其他具有相同优先级的线程获得运行机会
  2. 该线程处于就绪状态,不转为阻塞状态
    在这里插入图片描述
    在这里插入图片描述
public class Test extends Thread{
    @Override
    public void run() {//线程工作的主体
        Thread t=Thread.currentThread();
        for (int i = 1; i <=3 ; i++) {
            System.out.println(t.getName() + ":" + "第"+i+"次");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Test t=new Test();
        t.start();
        for (int i = 1; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+"第"+i+"次");
            if (i%2==0) Thread.yield();
        }
    }
}

效果图

main第1次
Thread-0:1次
main第2次
Thread-0:2次
main第3次
Thread-0:3次
main第4

练习

1.模拟多人爬山

  1. 每个线程代表一个人
  2. 可设置每人爬山速度
  3. 每爬完100米显示信息
  4. 爬到终点时给出相应提示
public class ClimbThread implements Runnable {
    private int time;//爬100米需要的时间
    private int num=0;//爬多少个100米
    public ClimbThread(int time, int kilo) {
        this.time = time;
        this.num = kilo/100;
    }
    public int getTime() { return time; }
    public void setTime(int time) { this.time = time; }
    public int getNum() { return num; }
    public void setNum(int num) { this.num = num; }
    public void run(){
        Thread t = Thread.currentThread();
        while (num>0){
            try {
                t.sleep(this.time);//线程休眠的时间就是爬100米所需要的时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(t.getName()+"已经爬完100米!");
            num--;
        }
        System.out.println(t.getName()+"到达终点!");
        }
    public static void main(String[] args) throws Exception{
        System.out.println("************开始爬山************");
        ClimbThread m1=new ClimbThread(1000,500);
        Thread t1=new Thread(m1,"年轻人");
        ClimbThread m2=new ClimbThread(3000,500);
        Thread t2=new Thread(m2,"老年人");
        t1.start();
        t2.start();
    }
}

效果图

************开始爬山************
年轻人已经爬完100米!
年轻人已经爬完100米!
老年人已经爬完100米!
年轻人已经爬完100米!
年轻人已经爬完100米!
年轻人已经爬完100米!
年轻人到达终点!
老年人已经爬完100米!
老年人已经爬完100米!
老年人已经爬完100米!
老年人已经爬完100米!
老年人到达终点!

2.线程的优先级

  1. 显示主线程、子线程默认优先级
  2. 将主线程设置为最高优先级、子线程设置为最低优先级并显示
public class Demo1 extends Thread{
    @Override
    public void run() { }
    public static void main(String[] args) {
        System.out.println("********显示默认优先级*********");
        System.out.println("主线程名:"+Thread.currentThread().getName()+",优先级:"+Thread.currentThread().getPriority());
        Demo1 d=new Demo1();
        System.out.println("子线程名:"+d.getName()+",优先级:"+d.getPriority());
        System.out.println("********修改默认优先级后*********");
        Thread.currentThread().setPriority(10);
        d.setPriority(1);
        System.out.println("主线程名:"+Thread.currentThread().getName()+",优先级:"+Thread.currentThread().getPriority());
        System.out.println("子线程名:"+d.getName()+",优先级:"+d.getPriority());
    }
}

效果图

********显示默认优先级*********
主线程名:main,优先级:5
子线程名:Thread-0,优先级:5
********修改默认优先级后*********
主线程名:main,优先级:10
子线程名:Thread-0,优先级:1

3.模拟叫号看病

  1. 某科室一天需看普通号50个,特需号10个
  2. 特需号看病时间是普通号的2倍
  3. 开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高
  4. 当普通号叫完第10号时,要求先看完全部特需号,再看普通号
  5. 使用多线程模拟这一过程
public class Demo2 extends Thread {
    @Override
    public void run() {
        Thread t=new Thread("特需号");
            t.setPriority(7);
            for (int i = 1; i <=10; i++) {
                System.out.println(t.getName()+":"+i+"号病人在看病!");
                try {
                    t.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    public static void main(String[] args) {
        Demo2 d1=new Demo2();
        d1.start();
        Thread t=new Thread("普通号");
        for (int i =1; i <=50; i++) {
            if (i==11){
                try {
                    d1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            t.setPriority(3);
            System.out.println(t.getName()+":"+i+"号病人在看病!");
            try {
                t.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

效果图

普通号:1号病人在看病!
特需号:1号病人在看病!
普通号:2号病人在看病!
特需号:2号病人在看病!
普通号:3号病人在看病!
普通号:4号病人在看病!
特需号:3号病人在看病!
普通号:5号病人在看病!
普通号:6号病人在看病!
特需号:4号病人在看病!
普通号:7号病人在看病!
特需号:5号病人在看病!
普通号:8号病人在看病!
普通号:9号病人在看病!
特需号:6号病人在看病!
普通号:10号病人在看病!
特需号:7号病人在看病!
特需号:8号病人在看病!
特需号:9号病人在看病!
特需号:10号病人在看病!
普通号:11号病人在看病!
普通号:12号病人在看病!
普通号:13号病人在看病!
普通号:14号病人在看病!
普通号:15号病人在看病!
普通号:16号病人在看病!
普通号:17号病人在看病!
普通号:18号病人在看病!
普通号:19号病人在看病!
普通号:20号病人在看病!
普通号:21号病人在看病!
普通号:22号病人在看病!
普通号:23号病人在看病!
普通号:24号病人在看病!
普通号:25号病人在看病!
普通号:26号病人在看病!
普通号:27号病人在看病!
普通号:28号病人在看病!
普通号:29号病人在看病!
普通号:30号病人在看病!
普通号:31号病人在看病!
普通号:32号病人在看病!
普通号:33号病人在看病!
普通号:34号病人在看病!
普通号:35号病人在看病!
普通号:36号病人在看病!
普通号:37号病人在看病!
普通号:38号病人在看病!
普通号:39号病人在看病!
普通号:40号病人在看病!
普通号:41号病人在看病!
普通号:42号病人在看病!
普通号:43号病人在看病!
普通号:44号病人在看病!
普通号:45号病人在看病!
普通号:46号病人在看病!
普通号:47号病人在看病!
普通号:48号病人在看病!
普通号:49号病人在看病!
普通号:50号病人在看病!

五.线程的同步方法

1.同步方法

使用synchronized修饰的方法控制对类成员变量的访问
在这里插入图片描述
或者
在这里插入图片描述

案例(卖票)

public class Demo3 extends Thread {
    public int left=5;
    public int used=0;
    @Override
    public void run(){
        while (true){
            buy();
            if (left<=0)break;
        }
    }
    public synchronized void  buy(){
        if (left<=0)return;
            left--;
            used++;
        System.out.println("恭喜"+Thread.currentThread().getName());
        System.out.println("当前已卖:"+used+",余票为:"+left);
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
        public static void main(String[] args) {
        Demo3 d=new Demo3();
        Thread t1=new Thread(d,"王");
        Thread t2=new Thread(d,"张");
        Thread t3=new Thread(d,"李");
        t1.start();
        t2.start();
        t3.start();
    }
}

效果图

恭喜王
当前已卖:1,余票为:4
恭喜李
当前已卖:2,余票为:3
恭喜李
当前已卖:3,余票为:2
恭喜李
当前已卖:4,余票为:1
恭喜张
当前已卖:5,余票为:0

2.同步代码块

使用synchronized关键字修饰的代码块
在这里插入图片描述
syncObject为需同步的对象,通常为this
效果与同步方法相同
在这里插入图片描述

1)案例

public class Demo3 extends Thread {
    public int left=5;
    public int used=0;
    @Override
    public void run(){
        while (true){
            if (left<=0)break;
            synchronized (this){
                if (left<=0)return;
                left--;
                used++;
                System.out.println("恭喜"+Thread.currentThread().getName());
                System.out.println("当前已卖:"+used+",余票为:"+left);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public synchronized void  buy(){
        if (left<=0)return;
            left--;
            used++;
        System.out.println("恭喜"+Thread.currentThread().getName());
        System.out.println("当前已卖:"+used+",余票为:"+left);
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
        public static void main(String[] args) {
        Demo3 d=new Demo3();
        Thread t1=new Thread(d,"王");
        Thread t2=new Thread(d,"张");
        Thread t3=new Thread(d,"李");
        t1.start();
        t2.start();
        t3.start();
    }
}

效果图

恭喜王
当前已卖:1,余票为:4
恭喜李
当前已卖:2,余票为:3
恭喜张
当前已卖:3,余票为:2
恭喜李
当前已卖:4,余票为:1
恭喜李
当前已卖:5,余票为:0

2)多个并发线程访问同一资源的同步代码块时

  1. 同一时刻只能有一个线程进入synchronized(this)同步代码块
  2. 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
  3. 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码

3)同步代码块的优缺点

**优点:**解决了多线程的数据安全问题
**缺点:**当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

六.线程安全的类型

1.线程安全的类型

查看ArrayList类的add()方法定义
在这里插入图片描述
ArrayList类的add()方法为非同步方法
当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题
在这里插入图片描述
在这里插入图片描述

2.常见类型对比

Hashtable && HashMap

  1. Hashtable
  2. 继承关系
    实现了Map接口,Hashtable继承Dictionary类
  3. 线程安全,效率较低
  4. 键和值都不允许为null
  5. HashMap
  6. 继承关系
    实现了Map接口,继承AbstractMap类
  7. 非线程安全,效率较高
  8. 键和值都允许为null

StringBuffer && StringBuilder

前者线程安全,后者非线程安全

3.练习

1)模拟接力赛跑

  1. 多人参加1000米接力跑
  2. 每人跑100米,换下个选手
  3. 每跑10米显示信息
public class Demo3 implements Runnable{
    public int kilo=1000;
    @Override
    public void run() {
        while (true){
            if (kilo<=0) break;
            go();
        }
    }
    public synchronized void go(){
        System.out.println(Thread.currentThread().getName()+"拿到接力棒!");
        for (int i = 10; i <=100; i+=10) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
        }
        kilo-=100;
    }
    public static void main(String[] args) {
        Demo3 d=new Demo3();
        Thread t1=new Thread(d,"1号选手");
        Thread t2=new Thread(d,"2号选手");
        Thread t3=new Thread(d,"3号选手");
        Thread t4=new Thread(d,"4号选手");
        Thread t5=new Thread(d,"5号选手");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

效果图

1号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1001号选手拿到接力棒!
1号选手跑了101号选手跑了201号选手跑了301号选手跑了401号选手跑了501号选手跑了601号选手跑了701号选手跑了801号选手跑了901号选手跑了1005号选手拿到接力棒!
5号选手跑了105号选手跑了205号选手跑了305号选手跑了405号选手跑了505号选手跑了605号选手跑了705号选手跑了805号选手跑了905号选手跑了1004号选手拿到接力棒!
4号选手跑了104号选手跑了204号选手跑了304号选手跑了404号选手跑了504号选手跑了604号选手跑了704号选手跑了804号选手跑了904号选手跑了1002号选手拿到接力棒!
2号选手跑了102号选手跑了202号选手跑了302号选手跑了402号选手跑了502号选手跑了602号选手跑了702号选手跑了802号选手跑了902号选手跑了1003号选手拿到接力棒!
3号选手跑了103号选手跑了203号选手跑了303号选手跑了403号选手跑了503号选手跑了603号选手跑了703号选手跑了803号选手跑了903号选手跑了100

2)网络购票

  1. “桃跑跑”、“张票票”、“黄牛党”共同抢10张票
  2. 限“黄牛党”只能抢一张票
public class Demo4 extends Thread {
    public int left=10;
    public int used=0;
    @Override
    public void run(){
        while (true){
            if (left<=0)break;
            buy();
            if (Thread.currentThread().getName().equals("黄牛党"))return;
        }
    }
    public synchronized void  buy(){
        if (left<=0)return;
            left--;
            used++;
        System.out.println(Thread.currentThread().getName()+"抢到第"+used+"票,剩余"+left+"张票!");
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
        public static void main(String[] args) {
        Demo4 d=new Demo4();
        Thread t1=new Thread(d,"桃跑跑");
        Thread t2=new Thread(d,"张票票");
        Thread t3=new Thread(d,"黄牛党");
        t1.start();
        t2.start();
        t3.start();
    }
}

效果图

桃跑跑抢到第1,剩余9张票!
张票票抢到第2,剩余8张票!
桃跑跑抢到第3,剩余7张票!
黄牛党抢到第4,剩余6张票!
桃跑跑抢到第5,剩余5张票!
张票票抢到第6,剩余4张票!
桃跑跑抢到第7,剩余3张票!
张票票抢到第8,剩余2张票!
桃跑跑抢到第9,剩余1张票!
张票票抢到第10,剩余0张票!

七.concurrent并发包

java.util.concurrent 提供了一系列方便并发编程的类

  • 阻塞队列BlockingQueue
  • 延迟队列DelayQueue
  • 闭锁CountDownLatch
  • 线程池ExecutorService
    ……
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值