Java 多线程详解

基本概念:程序 - 进程 - 线程

程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
如:运行中的QQ,运行中的MP3播放器
       程序是静态的,进程是动态的
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个程序可同一时间执行多个线程,就是支持多线程的
何时需要多线程:
       1.程序需要同时执行两个或多个任务。
       2.程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
       3.需要一些后台运行的程序时。

线程的创建和启动

一.线程的创建(此程序非多线程)

public class Sample {
	public void method1(String str){
		System.out.println(str);
	}
	public void method2(String str){
		method1(str);
	}
	public static void main(String[] args) {
		Sample  s = new Sample();
		s.method2("hello!");
	}
}

二.单线程制造商品

1.定义商品(Goods)类

package com.gec.singleThread;

public class Goods {

    private int id;
    private String name;
    private int num;
    private double price;

    public Goods() {
    }

    public Goods(String name, int num, double price) {
        this.name = name;
        this.num = num;
        this.price = price;
    }

    public Goods(int id, String name, int num, double price) {
        this.id = id;
        this.name = name;
        this.num = num;
        this.price = price;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

2.测试类

import java.util.ArrayList;
import java.util.List;

public class Factory {
    //主线程
    public static void main(String[] args) throws InterruptedException {
        try {
            singleThreadCreateGoods();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void singleThreadCreateGoods() throws InterruptedException {
        List<Goods> goodses = new ArrayList <>();
        //currentThread 当前线程     getName  获取线程名称
        String name = Thread.currentThread().getName();
        //开始时间,获取制作商品的时间消耗
        long start = System.currentTimeMillis();
        //生产商品
        int i = 1;
        while (i<=100){
            //采用线程中的sleep方法,让主线程睡眠1秒,相当于造商品
            Thread.sleep(1000);
            goodses.add(new Goods(i,"辣条",10,5));
            System.out.println(name+"生产第"+i+"个商品");
            i++;
        }
        long end = System.currentTimeMillis();
        System.out.println(name+"生产100个商品需要消耗"+(end-start)+"毫秒");
    }
}

3.运行结果

main生产第90个商品
main生产第91个商品
main生产第92个商品
main生产第93个商品
main生产第94个商品
main生产第95个商品
main生产第96个商品
main生产第97个商品
main生产第98个商品
main生产第99个商品
main生产第100个商品
main生产100个商品需要消耗100054毫秒

三.多线程的创建和启动

1.通过继承Thread类,并重写run方法来实现多线程(采用上述Gooods类)

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来实现。

Thread类的特性

  • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
  • 通过该Thread对象的start()方法来调用这个线程

继承Thread的步骤:
1.创建一个类继承Thread
2.重写run方法,是线程体
3.创建测试类
4.创建线程对象
5.调用start方法,启动线程

1.生产线

import java.util.List;

/**
 * 生产线
 * 实现线程方式1:继承Thread类
 * 实现Thread类中的run方法(线程需要执行的内容)
 */
public class CreateLine extends Thread{
    //商品仓库
    public List<Goods> goodses;

    //带参构造方法
    public CreateLine(List <Goods> goodses) {
        this.goodses = goodses;
    }

    @Override
    public void run() {
        Goods goods = null;
        try {
            String name = Thread.currentThread().getName();
            //开始时间,获取制作商品的时间消耗
            long start = System.currentTimeMillis();
            //生产商品
            int i = 1;
            while (goodses.size()<=100){
                //采用线程中的sleep方法,让主线程睡眠1秒,相当于造商品
                Thread.sleep(1000);
                goods = new Goods(i,"辣条"+i,10,5);
                goodses.add(goods);
                System.out.println(name+"生产第"+i+"个商品");
                i++;
            }
            long end = System.currentTimeMillis();
            System.out.println(name+"生产"+i+"商品需要消耗"+(end-start)+"毫秒");
            System.out.println(name + "生产"+ goods.getName()+"商品总数量:"+goodses.size());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

2.测试类

import java.util.ArrayList;
import java.util.List;

public class Factory {
    //主线程
    public static void main(String[] args) throws InterruptedException {
        List<Goods> list = new ArrayList <>();
        //创建线程
        for (int i = 0; i < 4; i++) {
            //创建每一个线程
            CreateLine cl = new CreateLine(list);
            //启动线程
            cl.start();
            //使用join方法让主线程等待子线程执行完,然后再执行主线程
            //cl.join();
        }
        System.out.println("商品建造完成...");
    }
}

3.运行结果

Thread-3生产第48个商品
Thread-1生产第48个商品
Thread-3生产第49个商品
Thread-1生产第49个商品
Thread-3生产50商品需要消耗49020毫秒
Thread-1生产50商品需要消耗49020毫秒
Thread-1生产辣条49商品总数量:102
Thread-3生产辣条49商品总数量:102

2.实现Runnable接口的形式来实现多线程

实现Runnable接口步骤:
1.创建一个类实现Runnable接口
2.重写run方法
3.创建测试类
4.创建实现类对象
5.创建线程对象,将实现类对象放入线程中
6.调用start方法启动线程
MyRunnable.java

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
             try {
                 Thread.sleep(10);
             }catch (InterruptedException e){
                 e.printStackTrace();
             }
            System.out.println(Thread.currentThread().getName()+"==========>"+i);
        }
    }
}

TestRunnable.java

public class TestRunnable {

    public static void main(String[] args) {
        //1.实例化MyRunnable对象
        MyRunnable mr = new MyRunnable();
        //2.创建线程,并将Runnable的实例作为运行体运行
        Thread t = new Thread(mr,"线程1");
        Thread t1 = new Thread(mr,"线程2");
        Thread t2 = new Thread(mr,"线程3");
        t.start();
        t1.start();
        t2.start();
    }
}

运行结果

线程1==========>96
线程2==========>97
线程3==========>97
线程1==========>97
线程2==========>98
线程3==========>98
线程1==========>98
线程2==========>99
线程3==========>99
线程1==========>99
2.1 匿名内部类实现多线程

NoNameRunnable.java

public class NoNameRunnable {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "==========>" + i);

                }
            }
        }, "线程1");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "==========>" + i);

                }
            }
        }, "线程2");
        //线程的优先级控制
        t.setPriority(10);//设置线程优先级,但是并不代表线程一定先执行,只是提高该线程抢占的概率
        t1.setPriority(1);

        t.start();
        t1.start();
    }
}

运行结果

线程2==========>93
线程2==========>94
线程1==========>94
线程2==========>95
线程1==========>95
线程1==========>96
线程2==========>96
线程2==========>97
线程1==========>97
线程2==========>98
线程1==========>98
线程1==========>99
线程2==========>99

3.实现Callable接口的形式来实现多线程

实现Callable接口步骤:

  • 创建Callable接口实现类,并重写call方法,线程体执行后会返回一个结果
  • 创建测试类
  • 创建Callable接口子类实例,使用FutureTask类来包装Callable使用对象,自动封装Callable对象的返回值
  • 将FutureTask对象放入Thread中执行,并启动线程
  • 调用FutureTask中的get方法获取到封装的Callable返回值

CallableDemo.java

  • Callable 该接口的方法是对某一类型进行多线程执行,并最后返回结果
public class CallableDemo implements java.util.concurrent.Callable<Integer> {
    public int sum;
    @Override
    public Integer call() throws Exception {
        for (int i = 0; i < 20; i++) {
             sum += i;
        }
        return sum;
    }
}

TestCallable.java

import java.util.concurrent.FutureTask;

public class TestCallable {
    public static void main(String[] args) throws Exception {
        CallableDemo cd = new CallableDemo();
        FutureTask <Integer> ft = new FutureTask <>(cd);
        Thread t = new Thread(ft);
        t.start();
        Thread t1 = new Thread(ft);
        t1.start();
        //结果是从FutureTask中获取
        System.out.println("线程执行后的结果为:" + ft.get());
    }
}

运行结果

线程执行后的结果为:190

四.同步代码块及同步方法解决线程安全问题

1.同步代码块

利用同步代码块解决同一张票在多个窗口售出的问题

  • 利用Synchronized关键字实现头部处理
  • 当线程进入后,获得对象锁.修改其标志,进行占用,别的线程就无法进入
  • 当该线程执行完成后,释放同步锁,标志再次被修改,放行一个线程

Ticket1.java

public class Ticket1 implements Runnable{
    private int ticket = 100; //总票数
    @Override
    public void run() {
        while (true){
            /**
             * 设置同步代码块,使用任意对象(obj)去检测同步代码块的闭合,如果代码块中有线程,阻止其他线程进入
             * 该对象可以是任意对象
             */
            synchronized (this){
                if (ticket>0){
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售出车票,票号为:"+ticket--);
                }else {
                    break;
                }
            }
        }
    }
}

2.同步方法

  • 利用Synchronized关键字修饰的方法叫做同步方法,整个方法都被同步化

Ticket2.java

public class Ticket2 implements Runnable{
    private int ticket = 100; //总票数
    @Override
    public void run() {
        boolean flag = true;
        while (true){
            flag = saleTicket();
        }
    }

    //每次该方法只能进入一个线程
    private synchronized boolean saleTicket() {
        if (ticket>0){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"售出车票,票号为:"+ticket--);
            return true;
        }
        return false;
    }
}

TestTicket.java

public class TestTicket {
    public static void main(String[] args) {
        //Ticket1 tic = new Ticket1();
        Ticket2 tic = new Ticket2();
        for (int i = 1; i < 4; i++) {
            Thread t = new Thread(tic,"窗口"+i);
            t.start();
        }
    }
}

运行结果

窗口1售出车票,票号为:7
窗口1售出车票,票号为:6
窗口1售出车票,票号为:5
窗口1售出车票,票号为:4
窗口1售出车票,票号为:3
窗口1售出车票,票号为:2
窗口1售出车票,票号为:1

五.单例设计模式之懒汉式

Singleton.java

public class Singleton {
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        synchronized(Singleton.class){
            if(instance==null){
                instance=new Singleton();
            }
        }
        return instance;
    }
}

SingleThread.java

import java.util.List;

public class SingleThread implements Runnable {

    List<Singleton> list = null;

    public SingleThread(List<Singleton> list) {
        this.list = list;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Singleton si = Singleton.getInstance();
        System.out.println(si);
        list.add(si);
    }

}

TestSingle.java

import java.util.ArrayList;
import java.util.List;

public class TestSingle {

    public static void main(String[] args) throws InterruptedException {
        List<Singleton> list = new ArrayList<>();
        SingleThread st = new SingleThread(list);
        Thread t = new Thread(st);
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        t.start();
        t1.start();
        t2.start();
    }
}

运行结果

com.gec.single.Singleton@543fe730
com.gec.single.Singleton@543fe730
com.gec.single.Singleton@543fe730

六.线程计数器(记录线程执行情况)

设计目标:

  • 使用多线程模拟团队集体活动
    • 有一个队长 20个队员
    • 所有队员在广场集合,开始自由活动
    • 10秒后所有队员返回全部集结
    • 统计活动时间最长的队员并输出

使用的方法:

  • CountDownLatch类表示线程执行计数器,用于监听线程的执行情况.
  • await() 检查线程是否执行完成
  • countDown() 该方法操作线程执行完成减少1个数量,直到清零

Captain.java

import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * 队长
 */
public class Captain extends Thread {
    private String captainName; //队长名称
    private List <Meber> mebers;

    public Captain(String captainName, List <Meber> mebers) {
        this.captainName = captainName;
        this.mebers = mebers;
    }

    public String getCaptainName(String captainName) {
        return captainName;
    }

    public void setCaptainName(String captainName) {
        this.captainName = captainName;
    }

    @Override
    public void run() {
        //创建计数器.数值为队员数量
        CountDownLatch count = new CountDownLatch(mebers.size());
        System.out.println("出去活动的队员数量为:" + count.getCount());
        System.out.println(captainName+": 所有队员在广场集合,开始自由活动,10秒后所有队员必须回来集合.");
        //1.开始时间
        long start = System.currentTimeMillis();
        System.out.println("开始时间为:"+start);
        for (Meber meber : mebers) {
             meber.setCount(count); //队员总数
             meber.start();
        }
        try {
            //使用await方法让主线程等待,等待count归0,然后再继续执行后续内容
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2.结束时间
        long end = System.currentTimeMillis();
        System.out.println("结束时间为:"+end);
        System.out.println("出去活动的队员数量:"+count.getCount());
        String message = count.getCount() == 0? "所有队员集结完毕" : "有队员掉队了,开小差去了";
        System.out.println(captainName+": "+message+",活动时间为:"+(end-start));
    }
}

Meber.java

import java.util.Random;
import java.util.concurrent.CountDownLatch;

/**
 * 队员
 */
public class Meber extends Thread{

    private String meberName;  //队员名称
    private CountDownLatch count; //线程计数器

    public Meber(String meberName) {
        this.meberName = meberName;
    }

    public String getMeberName() {
        return meberName;
    }

    public void setMeberName(String meberName) {
        this.meberName = meberName;
    }

    public void setCount(CountDownLatch count) {
        this.count = count;
    }

    /**
     * 队员活动
     */
    @Override
    public void run() {
        //创建随机对象,随机活动时间
        Random ran = new Random();
        int millis = 10000+ran.nextInt(10000);
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("队员"+meberName+", 开始自由活动,时间为:"+millis);
        //将计数器减1  (模拟队员活动结束回到广场集合)
        count.countDown(); //每调用一次数量减1
    }
}

CaptainTest.java

package com.gec.count;

import java.util.ArrayList;
import java.util.List;
public class CaptainTest {
    public static void main(String[] args) {
        //队员列表
        List<Meber> list = new ArrayList <>();
        for (int i = 0; i < 20; i++) {
            list.add(new Meber((i+1)+"号队员"));
        }
        Captain captain = new Captain("中国好队长",list);
        captain.start();
    }
}

七.线程通信

1.wait() 与 notify() 和 notifyAll()

wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

Java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常

2.wait()方法

  • 在当前线程中调用方法: 对象名.wait()
  • 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
  • 调用此方法后,当前线程将释放对象监控权 ,然后进入等待
  • 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

3.notify()方法 / notifyAll()方法

  • 在当前线程中调用方法: 对象名.notify()
  • 功能:唤醒等待该对象监控权的一个线程。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

4.例题

4.1 生产包子,卖包子

包子工厂
WanZaiFactory.java

import java.util.List;

public class WanZaiFactory extends Thread {
    List <String> list = null;

    public WanZaiFactory(List <String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        //定义包子数量
        int num = 0;
        boolean flag = true;
        while (flag) {
            if (System.currentTimeMillis() - start <= 5000) {
                synchronized (list) {
                    //判断包子的数量
                    if (list.size() >= 10) {
                        //暂停生产
                        try {
                            list.wait(); //沉睡当前线程
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        list.add("湾仔流沙包" + (++num));
                        System.out.println(Thread.currentThread().getName() + "生产湾仔流沙包" + num);
                    }
                }
            }else {
                flag = false;
            }
        }
    }
}

包子店铺
WanZaiShop.java

import java.util.List;

public class WanZaiShop extends Thread {
    List <String> list = null;

    public WanZaiShop(List <String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        //包子的数量
        int num = 0;
        boolean flag = true;
        long start = System.currentTimeMillis();
        //卖包子
        while (flag) {
            if (System.currentTimeMillis() - start <= 2000) {
                synchronized (list) {
                    //判断包子的数量
                    if (list.size() <= 0) {
                        //暂停生产
                        try {
                            list.notify(); //唤醒包子工厂,生产包子
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } else {
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        list.remove("湾仔流沙包" + (++num));
                        System.out.println(Thread.currentThread().getName() + "卖出湾仔流沙包" + num);
                    }
                }
            } else {
                flag = false;
            }
        }
    }
}

测试类
TestWait.java

import java.util.ArrayList;
import java.util.List;

public class TestWait {
    public static void main(String[] args) {
        List<String> list = new ArrayList <>();
        WanZaiFactory wf = new WanZaiFactory(list);
        wf.setName("湾仔码头工厂");

        WanZaiShop ws = new WanZaiShop(list);
        ws.setName("湾仔店铺1号");

        wf.start();
        ws.start();
    }
}
4.2 交替打印数字、字母

Print.java

public class Print {
    int index = 1;
    //输出数字
    public synchronized void printNum(int a){
        while (index%2==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        index++;  //数量加1后会进入到另外一个方法
        System.out.println(a);
        notify();
    }
    public synchronized void printChar(char c){
        while (index%2!=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        index++;  //数量加1后会进入到另外一个方法
        System.out.println(c);
        notify();
    }
}

PrintNum.java

public class PrintNum implements Runnable{
    Print p = null;

    public PrintNum(Print p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 26; i++) {
            p.printNum(i);
        }
    }
}

PrintCharacter.java

public class PrintCharacter implements Runnable{
    Print p = null;

    public PrintCharacter(Print p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (char i = 'a';i<='z';i++){
            p.printChar(i);
        }
    }
}

TestPrint.java

public class TestPrint {
    public static void main(String[] args) {
        Print p = new Print();
        Thread t = new Thread(new PrintNum(p));
        Thread t1 = new Thread(new PrintCharacter(p));
        t.start();
        t1.start();
    }
}

总结

本文主要讲解了Java 多线程,希望浏览文章的读者们能够通过该文章有所提升。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值