Java多线程解析

Java多线程解析

1.1   程序/进程

什么是程序?

程序就是一堆静止的代码。

什么是进程?

进程就是程序的一次执行。

 

1.2   操作系统的分类

单任务OS

一段时间内只能执行一个任务,如果要执行其他任务,必须把前一个任务结束后,才能进行第二个任务

 

代表:DOS

 

 

多任务OS

一段时间可以运行多个任务(程序),比如有两个任务A,B,当前运行A任务,如果想运行B任务,可以把A任务切换出来,让B运行。

在某个时刻只能有一个任务运行。

进程的概念和并发的概念

 

在单CPU的情况下,多个任务产生多个进程,这些进程之间可以切换,通过CPU时间片轮转来达到多任务。

并发:多个进程在一段时间内运行,叫做进程的并发。

 

并行:多个进程在同一时刻运行,叫做进程的并行。

一定个多个CPU才能并行。

进程提高了CPU的利用率。

线程

线程是把进程中的执行单元进行细分,分为能够独立运行的功能体,这些功能体就叫做线程

线程进一步提高了CPU的利用率

案例:

把学生上课比作学习的进程

 

总结

多线程进一步提高了CPU的利用率

多线程增加的程序的复杂度

1.3   进程和线程的区别和联系

 

可参考文章:

https://www.cnblogs.com/geeta/p/9474051.html

1.4   实现线程的方式

1.4.1     继承Thread

public class MyThread extends Thread{

   

    @Override

    public void run() {

        for(int i=0;i<10;i++) {

            System.out.println("MyThread:"+i);

        }

    }

}

public class Test01 {

    public static void main(String[] args) {

       

        // 【1】创建一个线程

        MyThread t1 = new MyThread();

        // 【2】启动线程,不是调用run

        t1.start();

 

    }

}

 

注意:

[1]启动线程通过start() 方法

[2] 一个进程中一定有一个主线程,一般是main方法所在的线程。

[3] 从执行结果看,具有多线程的程序,程序执行结果不确定 => 增加了程序控制的复杂性

分析线程执行轨迹

 

1.4.2     实现Runnable接口

public class MyRun implements Runnable {

 

    @Override

    public void run() {

        for(int i=0;i<10;i++) {

            System.out.println("MyThread:->"+i);

        }

    }

}

public class TestRun {

    public static void main(String[] args) {

       

        MyRun run = new MyRun();

        Thread t1 = new Thread(run);

        t1.start();

       

        for(int i=0;i<10;i++) {

            System.out.println("MainThread:=>"+i);

        }

    }

}

 

实现Runnable接口表示类具备多线程运行的能力。

1.4.3     两种实现方式的对比

从形式上

一个类继承了Thread类后就不能再继承其他类。

一个类实现了Runnable接口,还可以继承其他类。

从本质上

实现Runnable接口的类可以给多个线程 资源共享

案例:请模拟火车站购票的案例

通过继承Thead实现

public class ThreadTicket extends Thread{

   

    private static int ticket = 5;

   

    public ThreadTicket(String aName) {

        super(aName);

    }

   

    @Override

    public void run() {

        for(int i=0;i<10;i++) {

            if(ticket > 0) {

                ticket--;

                System.out.println(this.getName()+"还剩"+ticket+"张票");

            }

        }

    }

}

public class TestTicket {

    public static void main(String[] args) {

       

        ThreadTicket t1 = new ThreadTicket("窗口1");

        ThreadTicket t2 = new ThreadTicket("窗口2");

        ThreadTicket t3 = new ThreadTicket("窗口3");

        ThreadTicket t4 = new ThreadTicket("窗口4");

       

        t1.start();

        t2.start();

        t3.start();

        t4.start();

    }

}

 

通过Runnable接口实现

public class RunTicket implements Runnable {

 

    private int ticket = 5;

   

    @Override

    public void run() {

        for(int i=0;i<10;i++) {

            if(ticket > 0) {

                ticket--;

                System.out.println(Thread.currentThread().getName() + "还剩"+ticket+"张票");

            }

        }

    }

}

public class TestRun {

    public static void main(String[] args) {

       

        RunTicket run = new RunTicket();

        Thread t1 = new Thread(run,"窗口1");

        Thread t2 = new Thread(run,"窗口2");

        Thread t3 = new Thread(run,"窗口3");

        Thread t4 = new Thread(run,"窗口4");

       

        t1.start();

        t2.start();

        t3.start();

        t4.start();

       

    }

}

分析多线程运行轨迹

 

1.5   多线程和代理设计模式

 

1.6   线程的生命周期

 

1.7   线程的常用方法

1.7.1     [1]获取当前线程

public static void main(String[] args) {

        // 返回当前正在执行的线程的对象

        Thread mainThread = Thread.currentThread();

        System.out.println("当前线程的名称:"+mainThread.getName());

        System.out.println("当前线程的Id:"+mainThread.getId());

    }

1.7.2     [2]线程优先级

Thread中定义三个静态字段表示优先级

优先级字段

MAX_PRIORITY

10

MIN_PRIORITY

1

NORM_PRIORITY

5

MyThread02 t1 = new MyThread02("t1");

        t1.setPriority(6);

        t1.start();

       

        MyThread02 t2 = new MyThread02("t2");

        t2.setPriority(Thread.MAX_PRIORITY);

        t2.start();

结论

[1]优先级高不一定先运行。

[2]优先级高说明线程有可能被最先调度

1.7.3     [3]测试线程的活动状态

使用Thread类中的isAlive()方法,判断线程的状态

活动状态的界定:[调用start后,未死亡]

public static void main(String[] args) {

        MyThread03 t1 = new MyThread03();

        System.out.println(t1.isAlive());

        t1.start();

       

        System.out.println(t1.isAlive());

    }

1.7.4     [4]线程的强制执行

Join方法

调用该方法的线程强制执行,其它线程处于阻塞状态,该线程执行完毕后,其它线程再执行

=> 其他线程阻塞,直到该线程执行完成。

public static void main(String[] args) {

 

        MyThread04 t1 = new MyThread04("t1");

        t1.start();

 

        for (int i = 0; i < 5; i++) {

            if(i == 3) {

                try {

                    t1.join();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }else {            

                System.out.println(Thread.currentThread().getName() + "=>" + i);

            }

        }

    }

1.7.5     [5]线程的休眠

1.7.6     [6]线程的礼让

public static void main(String[] args) {

       

        MyThread06 t1 = new MyThread06();

        t1.start();

       

        for (int i = 0; i <5; i++) {

            if(i==3) {

                // 线程礼让

                Thread.yield();

            }else {            

                System.out.println(Thread.currentThread().getName()+"=>"+i);

            }

        }

    }

Thread-0->0

main=>0

Thread-0->1

main=>1

Thread-0->2

main=>2   ----àmain线程礼让一次

Thread-0->3   -----à主线程礼让一次,礼让给t1执行。

main=>4

Thread-0->4

 

注意:

[1]礼让的线程进入就绪状态。

[2]线程礼让完成后,各个线程又开始抢占CPU。

其中的一种特殊情况

main=>0

Thread-0->0

Thread-0->1

main=>1

main=>2    -----à main开始礼让

main=>4

Thread-0->2

Thread-0->3

Thread-0->4

 

API解析

A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

1.7.7     [7]线程中断

public class MyThread07 extends Thread{

    @Override

    public void run() {

        System.out.println("[1] 线性开始执行");

        for (int i = 0; i < 5; i++) {

            try {

                Thread.sleep(1000);

                System.out.println("[2] 线程休眠正常结束");

            } catch (InterruptedException e) {

                System.out.println("[3] 线程被中断");

                break;

                // e.printStackTrace();

            }

        }

       

        System.out.println("[4]线程结束");

    }

}

public class Test07Interrupt {

    public static void main(String[] args) {

       

        MyThread07 t1 = new MyThread07();

        t1.start();

       

        try {

            Thread.sleep(2000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

       

        t1.interrupt();

    }

}

 

总结

[1]终止线程的方法stop已经不建议使用,容易导致死锁。

[2]一般可以通过中断线程的方法iterrupt中断线程的执行,让线程提前结束。提前让线程结束是一种正常的结束。

1.8   线程安全

多线程虽然提高了CPU的利用率,但是导致严重的数据错乱的问题。

窗口1还剩5张票

窗口2还剩5张票

窗口2还剩3张票

窗口2还剩2张票

窗口1还剩4张票

窗口2还剩1张票

同步的概念

同步让一个逻辑单元执行完成。例如买票系统中把

if(ticket > 0) {

     System.out.println(this.getName()+"还剩"+ticket+"张票");

     ticket--;

}

定义成逻辑单元(一个整体)。

逻辑单元要么都执行,要么都不执行

如何保证?è 引入同步的概念保证逻辑单元的统一执行。

1.8.1     [1]同步代码块

// 同步代码块

// mutex(互斥元,互斥体;互斥量) ==> 互斥锁

synchronized (mutex) {            

    if(ticket > 0) {

        System.out.println(this.getName()+"还剩"+ticket+"张票");

        ticket--;

    }

}

[1]把逻辑单元放到同步代码块中

[2]给同步代码块加互斥锁

互斥锁一定要是一个对象;互斥锁最好要选择共享资源作为互斥锁

1.8.2     [2]同步方法

当逻辑单元过于复杂时,可以选择使用同步方法,使用synchronized修饰

public class MyRun implements Runnable{

 

    private int ticket = 10;

   

    @Override

    public void run() {

        for(int i=0;i<20;i++) {

            this.buyTicket();

        }

    }

   

    public synchronized void buyTicket() {

        if(ticket>0) {

           

            System.out.println(Thread.currentThread().getName()+"还剩"+ticket+"张票");

           

            try {

                // 模拟买票的过程

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

           

            ticket--;

        }

    }

 

}

同步总结

 

1.8.3     死锁(B)

同步保证多线程访问共享资源时数据不会错乱,但是过多的同步会导致死锁。

当线程t1需要访问A资源,把A加锁,同时还需要B资源,t1也将B资源加锁,而B资源被线程t2加锁,而t2又需要A资源,结果导致t1,t2陷入相互等待的过程。

public class MyThread01 extends Thread{

   

    private Object A;

    private Object B;

   

    public MyThread01(Object a,Object b) {

        super();

        this.A = a;

        this.B = b;

    }

   

    @Override

    public void run() {

       

        synchronized (A) {

           

            System.out.println("t1用于A,然后申请B资源");

           

            synchronized (B) {

                System.out.println("t1用于A,申请到B资源");

            }

        }

       

        System.out.println("t1正常结束...");

    }

}

public class MyThread02 extends Thread{

   

    private Object A;

    private Object B;

   

    public MyThread02(Object a,Object b) {

        super();

        this.A = a;

        this.B = b;

    }

   

    @Override

    public void run() {

       

        synchronized (B) {

           

            System.out.println("t2拥有B资源,然后申请A资源");

            synchronized (A) {

                System.out.println("t2拥有B资源,申请到A资源");

            }

        }

       

        System.out.println("t2正常结束...");

    }

}

public class Test {

    public static void main(String[] args) {

       

        // 创建两个资源A,B

        Object A = new Object();

        Object B = new Object();

       

        MyThread01 t1 = new MyThread01(A, B);

        //MyThread01 t2 = new MyThread01(A, B);

        MyThread02 t2 = new MyThread02(A, B);

       

        t1.start();

        t2.start();

    }

}

死锁一般情况下表示互相等待,是程序运行时出现的一种问题!

实际案例:买饭

1.9   生产者和消费者问题

package sxt06.thread07;

 

public class GoodsStore {

 

    private String brand;

    private String name;

   

    // 声明一个标识,标识仓库中是否有商品

    private boolean hasGoods = false;

 

    public String getBrand() {

        return brand;

    }

 

    public void setBrand(String brand) {

        this.brand = brand;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    /**

     * 生产者生产商品

     * @param brand

     * @param name

     */

    public synchronized void push(String brand, String name) {

       

        if(hasGoods) {

            try {

                // 当前线程等待

                super.wait();

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

       

        this.setBrand(brand);

       

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

       

        this.setName(name);

        System.out.println("生产了-->"+brand+name);

       

        hasGoods = true;

        super.notify();

    }

 

    /**

     * 消费者取商品

     */

    public synchronized void get() {

       

        if(!hasGoods) {

            try {

                super.wait();

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

       

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println("消费了<--"+brand+name);

       

        hasGoods = false;

        super.notify();

    }

}

注意:

以上方法都只能在同步方法或者同步代码块中使用,否则会抛出异常

总结:

在线程间通信过程中,要解决两个核心问题

[1]线程同步问题。把程序的逻辑单元放到同步代码块和同步方法中

[2]线程间通信问题。wait/notify


 

猜你喜欢

转载自www.cnblogs.com/eric666666/p/10719820.html