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:3Thread-0sale:2Thread-0sale:1Thread-2sale:93Thread-1sale:89Thread-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对该问题进行了针对性的处理,请参考下面的新特性。

时间: 2024-10-13 10:51:54

09、多线程(一) -- 基本概念的相关文章

多线程中基本概念

01 进程 进程是指在系统中正在运行的一个应用程序.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内. 进程 负责非配内存 主线程跟子线程都在栈区中运行:非常可贵 主线程栈区1M 子线程栈区512KB 通过“活动监视器”可以查看Mac系统中所开启的进程 02 线程 1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程) 一个进程(程序)的所有任务都在线程中执行 多线程 3-1 基本概念 即1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务. 3-2 线程

Java的多线程 --线程的概念

Java的多线程--线程的概念 一个程序同时执行多个任务,通常,每个任务称之为线程(thread),她是线程控制的简称. 可以运行一个以上的任务的程序叫做多线程程序. 1.感觉线程和进程很像,那么他们的区别是什么? 其实他们是有本质的区别:每个进程拥有自己独立的一整套变量,而线程则共享数据. 2.在Java中怎么创建一个线程去执行一个任务. 主要有4个步骤. 1)将任务代码移到实现了Runnable接口类的run方法中,这个接口很简单,只有一个方法. public interface Runna

多线程的基本概念和Delphi线程对象Tthread介绍

多线程的基本概念和Delphi线程对象Tthread介绍 作者:xiaoru WIN 98/NT/2000/XP是个多任务操作系统,也就是:一个进程可以划分为多个线程,每个线程轮流占用CPU运行时间和资源,或者说,把CPU 时间划成片,每个片分给不同的线程,这样,每个线程轮流的“挂起”和“唤醒”,由于时间片很小,给人的感觉是同时运行的. 多线程带来如下好处: 1)避免瓶颈: 2)并行操作: 3)提高效率:多线程的两个概念: 1) 进程:也称任务,程序载入内存,并分配资源,称为“一个进程”. 注意

OC中多线程的一些概念

1.进程1.1>进程是指在系统中正在运行的一个应用程序(同时打开QQ和Xcode,系统会分别启动2个进程)1.2>每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内 2.线程1.1>一个进程想要执行任务,必须得有线程(每个进程至少要有一条线程,即主线程)1.2>线程是进程的基本执行单元,进程的所有任务都在线程中执行3.多线程3.1>一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务      进程→车间, 线程→车间工人3.2>多线程技术

多线程笔试面试概念问答

题目转自http://blog.csdn.net/morewindows/article/details/7392749 第一题:线程的基本概念.线程的基本状态及状态之间的关系? 线程,有时称为轻量级进程,是CPU使用的基本单元:它由线程ID.程序计数器.寄存器集合和堆栈组成.它与属于同一进程的其他线程共享其代码段.数据段和其他操作系统资源(如打开文件和信号). 线程有四种状态:新生状态.可运行状态.被阻塞状态.死亡状态.状态之间的转换如下图所示: 第二题:线程与进程的区别? 1. 线程是进程的

Java多线程(一)、多线程的基本概念和使用(转)

Java多线程(一).多线程的基本概念和使用 分类: javaSE综合知识点 2012-09-10 16:06 2196人阅读 评论(0) 收藏 举报 一.进程和线程的基础知识 1.进程和线程的概念 进程:运行中的应用程序称为进程,拥有系统资源(cpu.内存) 线程:进程中的一段代码,一个进程中可以有多段代码.本身不拥有资源(共享所在进程的资源) 在java中,程序入口被自动创建为主线程,在主线程中可以创建多个子线程. 区别: 1.是否占有资源问题 2.创建或撤销一个进程所需要的开销比创建或撤销

Linux程序设计学习笔记----多线程编程基础概念与基本操作

转载请注明出处,http://blog.csdn.net/suool/article/details/38542543,谢谢. 基本概念 线程和进程的对比 用户空间资源对比 每个进程在创建的时候都申请了新的内存空间以存储代码段\数据段\BSS段\堆\栈空间,并且这些的空间的初始化值是父进程空间的,父子进程在创建后不能互访资源. 而每个新创建的线程则仅仅申请了自己的栈,空间,与同进程的其他线程共享该进程的其他数据空间包括代码段\数据段\BSS段\堆以及打开的库,mmap映射的文件与共享的空间,使得

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

一.进程和线程的基础知识 1.进程和线程的概念 进程:运行中的应用程序称为进程,拥有系统资源(cpu.内存) 线程:进程中的一段代码,一个进程中可以有多段代码.本身不拥有资源(共享所在进程的资源) 在java中,程序入口被自动创建为主线程,在主线程中可以创建多个子线程. 区别: 1.是否占有资源问题 2.创建或撤销一个进程所需要的开销比创建或撤销一个线程所需要的开销大. 3.进程为重量级组件,线程为轻量级组件 多进程: 在操作系统中能同时运行多个任务(程序) 多线程: 在同一应用程序中有多个功能

分布式、高并发和多线程三个概念的简单区分

每当提起分布式.高并发和多线程这三个概念的时候,很多的程序员都会认为分布式=高并发=多线程,即认为这三个概念是相同的,但实际上这种认为是错误的. 什么是分布式 关于分布式的概念,我们大可以如此理解:它是为了解决性能瓶颈问题以及单个物理服务器容量不足问题所采用的优化手段. 而分布式这个领域需要解决极多的问题,且这些问题都在不同的技术层面上,包括分布式文件系统.分布式缓存.分布式数据库和分布式计算等.一些名词如Hadoop.Zookeeper和MQ等也都都跟分布式有关. 如果从理念层面看待,分布式的

Java多线程(1)——基本概念

一.基本概念 进程(process):程序的运行实例.进程与程序之间的关系就好比播放中的视频与对应的视频文件.进程是程序向操作系统申请资源(如内存空间和文件句柄)的基本单位. 线程(thread):进程中可独立执行的最小单位.一个进程可以包含多个线程,同一个进程中的所有线程共享该进程中的资源.Java平台中的一个线程就是一个对象. 任务(task):线程所要完成的计算. 串行(sequential):顺序逐一完成. 并发(concurrent):同一时间段交替完成. 并行(parallel):同