多线程

多线程

进程与线程

  • 进程: 正在运行中的程序
  • 线程: 就是进程中一个负责程序执行的控制单元(执行路径)

每个线程都有自己运行的内容,这个内容可以称之为线程要执行的任务。
一个进程中可以多个执行路径,即多线程;多线程的出现是为了能同时执行多段代码。


JVM中的多线程解析

JVM启动时就启动了多个线程,至少有两个线程(main/GC);一个是执行main函数的线程,该线程的任务代码都定义在main函数中;一个是负责垃圾回收的线程。

常见的误区是main线程执行完程序就结束了,其实JVM还有其他线程在执行。

class Demo {
    @Override
    public void finalize() {//覆盖父类的finalize方法
        System.out.println("clear");
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        new Demo();
        new Demo();
        new Demo();
        System.gc();// 垃圾回收器
        System.out.println("hello");
        /*
         * 至少有两个线程,所以多执行几次可以看到不同结果;
         * 不过CPU的速度非常快,估计很难看到
         * hello 
         * clear 
         * clear 
         * clear
         */
    }
}

为什么需要多线程的代码演示

class Demo {
    private String name;

    Demo(String name) {
        this.name = name;
    }

    public void show() {
        for (int x = 0; x < 10; x++) {
            // for(int y=-999999999;y<999999999;y++){}//加上这句代码的话执行会变慢;
            // 且如果上一个线程未执行完,下一个线程便不会执行,因此需要多线程
            System.out.println(name + "---x=" + x);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Demo d1 = new Demo("hoki");
        Demo d2 = new Demo("Lin");
        d1.show();
        d2.show();
    }
}

多线程的创建

1. 继承Thread类,重写run方法

创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行,而运行的指定代码就是这个执行路径的任务。

JVM创建的主线程的任务都定义在了主函数中,而自定义的线程它的任务在哪?
Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述。
这个任务就通过Thread类中的run方法来体现的,也就是说,run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。

开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可。

步骤

  1. 定义一个类继承Thread类
  2. 覆盖Thread类中的run方法
  3. 直接参加Thread的子类对象创建线程
  4. 调用start方法开启线程并调用线程的任务run方法执行

调用run方法与调用start方法的区别

直接调用run方法是由主线程去执行,也就是创建了新的线程却没有去开启,这跟平常调用某个类中的某个方法是一样的,和多线程没有关系,而调用start方法则可以开启新建的线程,run方法由新建的线程去执行。

class Demo extends Thread {
    private String name;

    Demo(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int x = 0; x < 10; x++) {
            System.out.println(name + "___x=" + x);
        }
    }

}

public class ThreadDemo {
    public static void main(String[] args) {
        Demo d1 = new Demo("hoki");
        Demo d2 = new Demo("Lin");
        d1.start();// start方法使该线程开始执行,JVM调用该线程的run方法
        d2.start();
    }
}

获取当前线程的名称

class Demo extends Thread {
    private String name;

    Demo(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int x = 0; x < 10; x++) {
            System.out.println(name + "___x=" + x + "---threadName=="
                    + Thread.currentThread().getName());

        }
    }

}

public class ThreadDemo {
    public static void main(String[] args) {
        Demo d1 = new Demo("hoki");
        Demo d2 = new Demo("Lin");
        d1.start();// start方法使该线程开始执行,JVM调用该线程的run方法
        System.out.println("这句由主线程执行" + "---threadName=="
                + Thread.currentThread().getName());// 获取主线程的线程名称
        d2.start();
    }
}

多线程运行

class Demo extends Thread {
    private String name;

    Demo(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int x = 0; x < 10; x++) {
            System.out.println(name + "___x=" + x + "---threadName=="
                    + Thread.currentThread().getName());

        }
    }

}

public class ThreadDemo {
    public static void main(String[] args) {
        Demo d1 = new Demo("我是一条线程");
        Demo d2 = new Demo("我也是一条线程");
        d1.start();// start方法使该线程开始执行,JVM调用该线程的run方法
        System.out.println(4/0);//证明main方法出栈时,其他正在执行的线程依旧执行,不弹栈。
        d2.start();

        for (int x = 0; x < 20; x++) {
            System.out.println("这句由主线程执行" +x+ "---threadName=="
                    + Thread.currentThread().getName());// 获取主线程的线程名称
        }
    }
}  

main方法出栈时,其他正在执行的线程依旧执行,不弹栈。

2. 实现Runnable接口

因为java是单继承机制的,一旦一个类想实现多线程而其本身已有继承就可以用实现Runnable接口的方式来实现多线程;这种实现方式较常用。

步骤

  1. 定义类实现Runnable接口
  2. 覆盖接口中的run方法,将线程的任务代码封装到run方法中
  3. 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递
  4. 调用线程对象的start方法开启线程
class Demo implements Runnable {

    public void run() {
        show();
    }

    public void show() {

        for (int x = 0; x < 10; x++) {
            System.out.println("___x=" + x + "---threadName=="
                    + Thread.currentThread().getName());

        }
    }

}

public class ThreadDemo {
    public static void main(String[] args) {

        Demo d = new Demo();

        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);
        t1.start();
        t2.start();

    }
}

简单模拟多线程的设计

Thread的设计

class Thread {
    private Runnable r;

    Thread() {
    }

    Thread(Runnable r) {
        this.r = r;
    }

    public void run() {
        if (r != null)
            r.run();
    }

    public void start() {
        run();
    }
}

Runnable的设计

class ThreadImpl implements Runnable{

    public void run() {
        System.out.println("runnable run");
    }

}

Test

main{
    ThreadImpl i = new ThreadImpl();
    Thread t = new Thread(i);
    t.start();
}

多线程案例—-卖票

需求:每张票只能卖一次

class Ticket implements Runnable {
    private int ticketNum = 100;

    public void run() {
        while (true) {
            if (ticketNum > 0) {
                System.out.println(Thread.currentThread().getName()
                        + "...sale..." + ticketNum--);
            }
        }
    }
}

public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        Thread t4 = new Thread(ticket);
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

线程安全问题

多线程是随机的,存在着安全隐患

class Ticket implements Runnable {
    private int ticketNum = 100;

    public void run() {
        while (true) {
            if (ticketNum > 0) {
                try{
                    //因为实现的是接口,覆盖了接口中的方法,所以只能catch异常,不能声明异常
                    Thread.sleep(10);//结果显示的票数有负数的
                }catch(InterruptedException e){

                }
                System.out.println(Thread.currentThread().getName()
                        + "...sale..." + ticketNum--);
            }
        }
    }
}

public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        Thread t4 = new Thread(ticket);
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

线程安全问题产生的原因

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

  1. 多个线程在操作共享的数据
  2. 操作共享数据的线程代码有多条

解决思路

将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

在java中,用同步代码块可以解决;

class Ticket implements Runnable {
    private int ticketNum = 100;
    //为了让多个线程使用同一个引用,所以这个对象必须只能有一个引用
    private Object obj = new Object();

    public void run() {
        while (true) {
            synchronized(obj)
            {
                if (ticketNum > 0) {
                    System.out.println(Thread.currentThread().getName()
                            + "...sale..." + ticketNum--);
                }
            }

        }
    }
}

public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        Thread t4 = new Thread(ticket);
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

同步的好处和弊端

同步代码块,也可称为同步锁,一旦有一个线程获得,其他线程就无法进入该锁区域的代码块

  • 好处: 解决了线程安全的问题
  • 弊端: 相对降低了效率,因为同步外的线程都会判断同步锁

同步的前提

必须有多个线程并使用同一个

同步函数

需求: 有两个储户,每个都到银行存钱,每次存100,共存三次

class Bank {
    private int sum;

    // private Object obj = new Object();
    public synchronized void add(int num) {// 同步函数
    // synchronized (obj) {
        sum += num;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
        }
        System.out.println("sum = " + sum);
        // }
    }
}

class Customer implements Runnable {

    private Bank b = new Bank();

    public void run() {
        for (int x = 0; x < 3; x++) {
            b.add(100);
        }
    }

}

public class BankDemo {

    public static void main(String[] args) {
        Customer c = new Customer();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);

        t1.start();
        t2.start();
    }

}

同步函数和同步代码块的区别

  1. 同步函数的锁是固定的this
  2. 同步代码块的锁是任意的对象

建议使用同步代码块

静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。


死锁

常见情景之一: 同步嵌套

class MyLock {
    public static final Object locka = new Object();
    public static final Object lockb = new Object();
}

class Test implements Runnable {
    private boolean flag;

    Test(boolean flag) {
        this.flag = flag;
    }

    public void run() {
        if (flag) {
            synchronized (MyLock.locka) {
                System.out.println(Thread.currentThread().getName()
                        + "...if...locka...");
                synchronized (MyLock.lockb) {
                    System.out.println(Thread.currentThread().getName()
                            + "...if...lockb...");

                }
            }
        } else {
            synchronized (MyLock.lockb) {
                System.out.println(Thread.currentThread().getName()
                        + "...else...lockb...");
                synchronized (MyLock.locka) {
                    System.out.println(Thread.currentThread().getName()
                            + "...else...locka...");

                }
            }

        }
    }
}

public class DeadLockTest {

    public static void main(String[] args) {
        Test a = new Test(true);
        Test b = new Test(false);

        Thread t1 = new Thread(a);
        Thread t2 = new Thread(b);

        t1.start();
        t2.start();
    }

}

线程间的通信

示例

/**
 * COPYRIGHT (C) 2018 BY HOKI SOFTWARE. ALL RIGHTS RESERVED.
 * 
 * @author: Hoki_Lin
 * @since: 1.0
 * @date: 2018年4月10日 上午9:14:02
 * @description: 线程间的通讯 多个线程在处理同一资源,但任务却不同
 */
class Resource {
    String name;
    String sex;

}

class Input implements Runnable {
    Resource r;

    Input(Resource r) {
        this.r = r;
    }

    public void run() {
        int x = 0;
        while (true) {
            synchronized (r) {
                if (x == 0) {
                    r.name = "Hoki";
                    r.sex = "male";
                } else {
                    r.name = "Jessie";
                    r.sex = "female";
                }
            }
            x = (x + 1) % 2;
        }
    }

}

class Output implements Runnable {
    Resource r;

    Output(Resource r) {
        this.r = r;
    }

    public void run() {
        while (true) {
            synchronized (r) {
                System.out.println(r.name + "......" + r.sex);
            }
        }
    }

}

public class TelecomDemo {
    public static void main(String[] args) {
        // 创建资源
        Resource r = new Resource();
        // 创建任务
        Input in = new Input(r);
        Output out = new Output(r);
        // 创建线程,执行路径
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        // 开启线程
        t1.start();
        t2.start();

    }
}

等待唤醒机制

/**
 * COPYRIGHT (C) 2018 BY HOKI SOFTWARE. ALL RIGHTS RESERVED.
 * 
 * @author: Hoki_Lin
 * @since: 1.0
 * @date: 2018年4月10日 上午10:13:19
 * @description: 等待唤醒机制 涉及的方法: 
 * 1. wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中 
 * 2. notify(): 唤醒线程池中的一个任意线程 
 * 3. notifyAll(): 唤醒线程池中的所有线程
 */

class Resource {
    String name;
    String sex;
    boolean flag = false;// false表示资源库里没有资源了
}

class Input implements Runnable {
    Resource r;

    Input(Resource r) {
        this.r = r;
    }

    public void run() {
        int x = 0;
        while (true) {
            synchronized (r) {
                if (r.flag)
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                    }// 有资源就等待
                if (x == 0) {
                    r.name = "Hoki";
                    r.sex = "male";
                } else {
                    r.name = "Jessie";
                    r.sex = "female";
                }

                r.flag = true;
                r.notify();
            }
            x = (x + 1) % 2;
        }
    }

}

class Output implements Runnable {
    Resource r;

    Output(Resource r) {
        this.r = r;
    }

    public void run() {
        while (true) {
            synchronized (r) {
                if (!r.flag)
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                    }// 确定没有资源就等待
                System.out.println(r.name + "......" + r.sex);
                r.flag = false;
                r.notify();
            }
        }
    }

}

public class TelecomDemo2 {
    public static void main(String[] args) {
        // 创建资源
        Resource r = new Resource();

        // 创建任务
        Input in = new Input(r);
        Output out = new Output(r);
        // 创建线程,执行路径
        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);
        // 开启线程
        t1.start();
        t2.start();

    }
}

wait()、notify()、notifyAll()都是用来操作线程的,但为什么定义在了Object类中?

  1. 这些方法都必须定义在同步中
  2. 使用这些方法时必须要标识所属的同步的锁
  3. 锁可以是任意对象,所以任意对象调用的方法一定定义在Object类中

1.5新特性-Lock

同步代码块对于锁的操作是隐式的;jdk1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显式动作。

/*
 * COPYRIGHT (C) 2018 BY HOKI SOFTWARE. ALL RIGHTS RESERVED.
 */
package com.hoki.javase.multithread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多生产者-多消费者经典的线程案例
 * 
 * @author: Hoki_Lin
 * @since: 1.0
 */
public class ClassicThreadDemo {
    // 遗留问题: 没必要唤醒所有线程,这会影响效率,那么如何只唤醒对方的线程呢?
    public static void main(String[] args) {
        Resource r = new Resource();

        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);
        // 单生产-单消费不会出现问题,但是多生产-多消费却会出现问题
        Thread t0 = new Thread(pro);// 线程本来就是从0开始计算的
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);

        t0.start();
        t1.start();
        t2.start();
        t3.start();

    }

}

class Resource {
    private String name;
    private int count = 1;
    private boolean flag = false;
    // 创建一个锁对象
    Lock lock = new ReentrantLock();
    // 通过已有的锁获取该锁上的监视器对象
//  Condition con = lock.newCondition();
    // 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
    Condition producer_con = lock.newCondition();
    Condition consumer_con = lock.newCondition();

    public void set(String name) {
        lock.lock();
        try {
            while (flag) {
                try {
                    // this.wait();
                    producer_con.await();
                } catch (InterruptedException e) {
                }
            }

            this.name = name + count;
            count++;

            System.out.println(Thread.currentThread().getName() + "...生产者..."
                    + this.name);

            flag = true;
            // notifyAll();
//          con.signalAll();
            consumer_con.signal();// 只唤醒一个

        } finally {
            lock.unlock();
        }
    }

    public void out() {
        lock.lock();
        try {
            while (!flag) {
                try {
                    // this.wait();
                    consumer_con.await();
                } catch (InterruptedException e) {
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + "...消费者........" + this.name);

            flag = false;
            // notifyAll();
//          con.signalAll();
            producer_con.signal();
        } finally {
            lock.unlock();
        }
    }
}

class Producer implements Runnable {

    private Resource r;

    Producer(Resource r) {
        this.r = r;
    }

    public void run() {
        while (true) {
            r.set("烤鸭");
        }
    }

}

class Consumer implements Runnable {

    private Resource r;

    Consumer(Resource r) {
        this.r = r;
    }

    public void run() {
        while (true) {
            r.out();
        }
    }

}

wait()与sleep()的区别

  • wait():可以指定时间,也可以不指定时间
  • sleep():必须指定时间
  • wait(): 释放CPU执行权,且释放锁
  • sleep(): 释放CPU执行权,但不释放锁

停止线程

stop方法已经过时不再使用

  1. 定义循环结束标记(有局限性)
  2. 使用interrupt方法结束线程的冻结状态,使线程强制恢复到运行状态来,让线程具备CPU的执行资格,但是强制动作会发生InterruptedException,记得要处理
/*
 * COPYRIGHT (C) 2018 BY HOKI SOFTWARE. ALL RIGHTS RESERVED.
 */
package com.hoki.javase.multithread;

/**
 * 停止线程
 * 
 * @author Hoki_Lin
 * @since 1.0
 */
public class InterruptDemo {
    public static void main(String[] args) {
        StopThread st = new StopThread();

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);

        t1.start();
        t2.start();

        int num = 1;
        for (;;) {
            if (++num == 50) {
                // st.setFlag();
                t1.interrupt();
                t2.interrupt();
                break;
            }
            System.out.println("main..." + num);
        }
        System.out.println("over");
    }

}

class StopThread implements Runnable {
    private boolean flag = true;

    public synchronized void run() {
        while (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                System.out
                        .println(Thread.currentThread().getName() + "..." + e);
                flag = false;
            }

            System.out.println(Thread.currentThread().getName() + "...");
        }
    }

    public void setFlag() {
        flag = false;
    }
}

守护进程

setDaemon():将该线程定义为守护线程或用户线程,当正在运行的线程都是守护线程时,Java虚拟机退出,该方法必须在启动前调用

/*
 * COPYRIGHT (C) 2018 BY HOKI SOFTWARE. ALL RIGHTS RESERVED.
 */
package com.hoki.javase.multithread;

/**
 * 停止线程
 * 
 * @author Hoki_Lin
 * @since 1.0
 */
public class InterruptDemo {
    public static void main(String[] args) {
        StopThread st = new StopThread();

        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);

        t1.start();
        t2.setDaemon(true);// 将t2定义为守护线程
        t2.start();

        int num = 1;
        for (;;) {
            if (++num == 50) {
                // st.setFlag();
                t1.interrupt();
//              t2.interrupt();// 未中断t2
                break;
            }
            System.out.println("main..." + num);
        }
        System.out.println("over");
    }

}

class StopThread implements Runnable {
    private boolean flag = true;

    public synchronized void run() {
        while (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                System.out
                        .println(Thread.currentThread().getName() + "..." + e);
                flag = false;
            }

            System.out.println(Thread.currentThread().getName() + "...");
        }
    }

    public void setFlag() {
        flag = false;
    }
}

线程类的其他方法

  • join:当一个线程A调用join方法时,主线程就会停止执行,直到A线程执行完毕
  • setPriority:设置优先级,MAX_PRIORITY, MIN_PRIORITY, NORM_PRIORITY
  • yield: 暂停当前正在执行的线程对象,并执行其他线程
public class ThreadTest {

    public static void main(String[] args) {

        new Thread(new Runnable(){
            public void run(){
                System.out.println("runnable run");
            }
        })
        {
            public void run(){
                System.out.println("subThread run");// subThread run
            }
        }.start();
    }
}
阅读更多
文章标签: java se 多线程
个人分类: JavaSE
上一篇$PHP_THREE
下一篇JavaScript_00
想对作者说点什么? 我来说一句

C#多线程 C#多线程

2011年05月08日 28KB 下载

java多线程

2011年10月07日 338KB 下载

免费VC多线程实例源码

2010年09月27日 153KB 下载

c_多线程 c_多线程

2010年11月14日 539KB 下载

多线程 C#多线程 多线程机制

2011年04月11日 138KB 下载

多线程编程指南 多线程编程指南

2010年11月14日 1.55MB 下载

C#__多线程

2011年11月01日 82KB 下载

http多线程 多线程

2009年05月08日 242KB 下载

没有更多推荐了,返回首页

关闭
关闭