09多线程 -- 基本概念

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了09多线程 -- 基本概念相关的知识,希望对你有一定的参考价值。

1.1、多线程基本使用

1、线程的创建方式 

多线程的创建有两种方式,分别如下:

继承

  • 继承Thread类,并重写run方法,将需要多线程的代码放入run方法中。
  • 通过Thread的子类的引用调用start()方法来开启线程。

实现

  • 定义类实现Runnable接口,覆盖Runnable接口中的run方法。
  • 通过Thread类建立线程对象。
  • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
  • 调用Thread类的star方法开启线程并调用Runnable接口子类的run方法。

继承的方式:

public class ThreadDemo {
    public static void main(String[] args) {
        new MyThread().start();
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        // 多线程代码
        System.out.println("多线程开启了");
    }
}

实现的方式:

public class ThreadDemo {
    public static void main(String[] args) {
        // 第一种写法
/*      MyThread t1 = new MyThread();
        Thread thread = new Thread(t1);
        thread.start();*/
        
        // 开发中写法
        new Thread(){
            @Override
            public void run() {
                System.out.println("线程开启了");
            }
        }.start();
    }
}

class MyThread implements Runnable{
    public void run() {
        System.out.println("线程开启了");
    }
}

 线程都有自己默认的名称,通过getName和setName来进行获取和设置。同时,还可以通过Thread.currentThread()来获取当前线程对象。

2、 线程的运行状态

被创建-------------------->运行-------------------------->消亡(stop(已过时)或run方法结束)

                                            |

                                          阻塞-->(阻塞状态:具备运行资格,但不具备cpu执行权。)

                                            |

                                         冻结(sleep和wait):不具备运行资格,也不具备cpu执行权。

睡眠状态和等待状态:不具备运行资格,也没有执行权。

  • 睡眠状态:sleep(time),当线程遇到sleep会进入睡眠状态,当睡眠时间到达后可能是运行状态,也可能进入临时状态(阻塞状态)。
  • 等待状态:wait(),当线程遇到wait会进入等待状态,这时需要notify()来唤醒,唤醒后可能是运行状态,也可能是临时状态(阻塞状态)。

消亡的两种方式:通过stop()命令强行结束线程或run()方法执行结束。

3、 多线程售票实例

  火车站有100张票,分别在t1、t2、t3、t4等4个窗口进行出售。

由此首先我们考虑到的是将100张票定义为静态共享变量,并开启四个线程来出售,那么代码如下:

public class ThreadDemo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket);// 售票窗1
        Thread t2 = new Thread(ticket);// 售票窗2
        Thread t3 = new Thread(ticket);// 售票窗3
        Thread t4 = new Thread(ticket);// 售票窗4
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

// 售票厅
class Ticket extends Thread {
    private static int ticks = 100;// 出售票的方法
    public void sale() {
        System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);
    }
    @Override
    public void run() {
        while (true) {
            if (ticks > 0) {
                sale();
            }
        }
    }
}

输出结果:

Thread-0sale:3
Thread-0sale:2
Thread-0sale:1
Thread-2sale:93
Thread-1sale:89
Thread-3sale:91

从结果中我们可以看出,售卖过程中我们发现出售的顺序已经错乱,这是因为线程太快导致,我们可以让线程在输出时进行睡眠20毫秒:

// 售票厅
class Ticket extends Thread{
    private static int ticks = 100;
    // 出售票的方法
    public void sale(){
        System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);
    }
    @Override
    public void run() {
        while(true){
            if(ticks > 0){
try{Thread.sleep(20);}catch(Exception e){} sale(); }
} } }

输出结果:

Thread-2sale:77
Thread-3sale:77
Thread-0sale:77
Thread-1sale:77

从结果中,我们看到出现售卖相同的票,那么线程的安全隐患就暴露出来了,那么我们如何解决这个问题呢?

我们只能通过线程同步来解决该问题,来保住每次被并发执行的代码中只能有一个线程在操作,以此解决线程的安全问题。

4、 多线程同步

由上面代码的示例我们知道,直接开启四个线程进行售卖是存在安全隐患的,我们必须通过同步来解决线程安全问题。

同步的前提:

  • 必须要有两个或者两个以上的线程。
  • 必须是多个线程使用同一个锁。必须保证同步中只能有一个线程在运行。

线程的同步解决了多线程的安全问题,但是多个线程都需要判断锁,较为耗费资源。

a) 如果需要同步的代码只是部分,则可以使用同步代码块,同步代码块的锁对象可以是任意的对象:

// 售票厅
class Ticket extends Thread {
    private static int ticks = 100;
    Object obj = new Object();
    // 出售票的方法
    public void sale() {
        System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);
    }
    @Override
    public void run() {
        while (true) {
            synchronized(obj){
                if (ticks > 0) {
                    try{Thread.sleep(20);}catch(Exception e){}
                    sale();
                }
            }
        }
    }
}

输出结果:

Thread-1sale:100
Thread-1sale:99
Thread-1sale:98
...
Thread-2sale:3
Thread-2sale:2
Thread-2sale:1

b) 函数需要被对象调用。那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this

// 售票厅
class Ticket extends Thread {
    private static int ticks = 100;
    Object obj = new Object();
    // 出售票的方法
    public void sale() {
        System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);
    }
    @Override
    public void run() {
        while (true) {
            synSal();
        }
    }
    // 同步函数
    public synchronized void synSal(){
        if (ticks > 0) {
            try{Thread.sleep(20);}catch(Exception e){}
            sale();
        }
    }
}

输出结果:

Thread-1sale:100
Thread-1sale:99
Thread-1sale:98
...
Thread-2sale:3
Thread-2sale:2
Thread-2sale:1

c) 当同步函数被static(静态)所修饰的时候,使用的锁是所在类的字节码文件(类名.class)。

// 售票厅
class Ticket extends Thread {
    private static int ticks = 100;
    Object obj = new Object();
    // 出售票的方法
    public static void sale() {
        System.out.println(Thread.currentThread().getName() + "sale:" + ticks--);
    }
    @Override
    public void run() {
        while (true) {
            synSal();
        }
    }
    // 同步函数
    public static synchronized void synSal(){
        if (ticks > 0) {
            try{Thread.sleep(20);}catch(Exception e){}
            sale();
        }
    }
}

输出结果:

Thread-1sale:100
Thread-1sale:99
Thread-1sale:98
...
Thread-2sale:3
Thread-2sale:2
Thread-2sale:1

d) 多线程--死锁,同步中嵌套同步,而锁却不同就会造成死锁,从而导致程序无法继续向下运行。

class DeadLock implements Runnable{
    //定义一个标记
    private boolean flag;
    DeadLock(boolean flag){
        this.flag = flag;
    }
    //重写run方法
    public void run(){
        if(flag){
            synchronized(MyLock.locka){
                synchronized(MyLock.lockb){
                    System.out.println("if locka");
                }
            }
        }
        else{
            synchronized(MyLock.lockb){
                synchronized(MyLock.locka){
                    System.out.println("else lockb");
                }
            }
        }
    }
}
//定义两个锁
class MyLock{
    static Object locka = new Object();
    static Object lockb = new Object();
}
public class DeadLockDemo {
    public static void main(String[] args) {
        new Thread(new DeadLock(true)).start();
        new Thread(new DeadLock(false)).start();
    }
}

5、多线程之间的通信

a) 等待唤醒机制

  • notify():激活线程池中 wait的线程。(谁先进去就唤醒谁)
  • notifyAll():激活线程池中所有wait的线程。
  • notify,notifyAll和wait等方法必须用在同步中,也就是说同步是前提。
  • 而且这几种方法都是继承自Object类,出现异常,只能try而不能抛。
  • 等待和唤醒必须是同一个锁,锁可以是任意对象,所以方法定义在Object类中。
  • 都使用在同步中,因为要持有监视器(锁)的线程操作。
  • 所以要使用在同步中,因为只有同步才具有锁。

b) 生产者和消费者实例

  我们来看一个生产者和消费者的实例,在生产商品的同时将商品销售或消费掉。

class ProducerConsumerDemo {
    public static void main(String[] args) {
        Resource res = new Resource();      
        Producer pro = new Producer(res);
        Consumer con = new Consumer(res);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
class Resource { private String name; private int count = 1; private boolean flag = false; public synchronized void set(String name) { while (flag) try { this.wait(); } catch (Exception e) { } this.name = name + "--" + count++; System.out.println(Thread.currentThread().getName() + "...生产者.." + this.name); flag = true; this.notifyAll(); } public synchronized void out() { while (!flag) try { wait(); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + "...消费者........." + this.name); flag = false; this.notifyAll(); } } class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res = res; } public void run() { while (true) { res.set("+商品+"); } } } class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while (true) { res.out(); } } }

为什么要定义while判断标记?

  对于多个生产者和消费者需要用while判断标记,来让被唤醒的线程再一次判断标记。

为什么要定义notifyAll()?

  因为在本线程进入冻结或睡眠状态时,需要唤醒对方线程,如果用notify(),容易出现只唤醒本方线程的情况,导致程序中所有线程都进入等待状态。

我们可以看下简单的总结:

  一个生产线程对应一个消费线程,我们可以用if(flag)来判断标记,唤醒必须用notify()。

if(flag)    -->    notify()

  多个生产线程对应多个消费线程,我们必须用while(flag)来判断标记,唤醒必须用notifyAll()。(while循环下唤醒是notify()的话会导致全部等待)

while(flag)    -->    notifyAll();

死锁和全部等待:死锁是争抢执行权而导致程序停止,全部等待是所有线程都进入冻结状态。

针对唤醒本方的同时也唤醒对方的问题,Jdk1.5对该问题进行了针对性的处理,请参考下面的新特性。

以上是关于09多线程 -- 基本概念的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程

Java多线程——多线程的基本概念和使用

多线程是啥

linux多线程概念详述

多线程--线程概念

Java多线程:从基本概念到避坑指南