java多线程(一)

1.多线程简介

process(进程)&thread(线程)
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
进程是执行程序的一次执行过程,它是一个动态的概念,是系统分配资源的单位
一个进程中包含若干个线程,当然一个进程中至少有一个线程,不然没有其存在的意义,线程是CPU调度和执行的单位
注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器,如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错局
核心

  1. 线程就是独立的执行路径
  2. 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
  3. main()称之为主线程,是程序的入口,用于执行整个程序
  4. 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统密切相关的,先后顺序是不能人为干预的
  5. 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  6. 线程会带来额外的开销,如cpu调度时间,并发控制开销
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

2.线程创建

三种创建方式:继承Thread类;实现Runnable接口;实现Callable接口
第一种
继承Thread类

package com.company.test1;
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
//调用run方法只有主线程一条执行路径
//调用start方法,多条执行路径,主线程和子线程并行交替执行
//线程开启不一定立即执行,由cpu调度执行
public class ThreadTest01 extends Thread{
    @Override
    public void run(){
        //run方法线程体
        for (int i = 0; i <200 ; i++) {
            System.out.println("我在打游戏----"+i);
        }
    }

    public static void main(String[] args) {
        //main线程,主线程
        //创建一个线程对象
        ThreadTest01 threadTest01 = new ThreadTest01();
        //调用start方法开启线程
        threadTest01.start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println("我在干饭---"+i);
        }
    }
}

运行效果

我在打游戏----159
我在打游戏----160
我在打游戏----161
我在打游戏----162
我在打游戏----163
我在打游戏----164
我在干饭---127
我在打游戏----165
我在打游戏----166
我在打游戏----167
我在打游戏----168
我在打游戏----169

第二种
实现Runnable接口
避免单继承的局限性,灵活方便,同一个对象被多个线程使用

package com.company.test1;
//创建线程第二种:实现runnable接口,重写run方法,执行线程需要丢人runnable接口实现类
public class ThreadTest03 implements Runnable{
    @Override
    public void run(){
        //run方法线程体
        for (int i = 0; i <200 ; i++) {
            System.out.println("我在打游戏----"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        ThreadTest03 threadTest03 = new ThreadTest03();
        //创建线程对象,通过线程对象来开启线程,代理
//        Thread thread = new Thread();
//        thread.start();
        new Thread(threadTest03).start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println("我在干饭---"+i);
        }
    }
}

案例–抢火车票
多个线程操作同一个资源时,线程不安全,数据紊乱

package com.company.test1;
//多个线程同时操作同一个对象
//抢火车票
//多个线程操作同一个资源时,线程不安全,数据紊乱
public class ThreadTest04 implements Runnable{
    //票数
    private int ticketNums=10;

    @Override
    public void run(){
        while (true){
            if (ticketNums<=0){
                break;
            }
            //模拟延时
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"票");
        }
    }

    public static void main(String[] args) {
        ThreadTest04 ticket = new ThreadTest04();
        new Thread(ticket,"学生").start();
        new Thread(ticket,"教师").start();
        new Thread(ticket,"领导").start();
    }
}

运行结果

领导-->拿到了第10票
学生-->拿到了第8票
教师-->拿到了第9票
学生-->拿到了第7票
教师-->拿到了第7票
领导-->拿到了第7票
教师-->拿到了第6票
领导-->拿到了第4票
学生-->拿到了第5票
教师-->拿到了第3票
学生-->拿到了第2票
领导-->拿到了第1

案例–龟兔赛跑

package com.company.test1;
//模拟龟兔赛跑

public class Race implements Runnable{
    //胜利者
    private static String winner;
    @Override
    public void run(){
        for (int i = 1; i <=100 ; i++) {
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子")){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag=gameOver(i);
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }
    //判断是否完成比赛
    private boolean gameOver(int steps){
        //判断是否有胜利者
        if(winner!=null){
            return true;
        }else{
            if(steps==100){
                winner=Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

运行–兔子就跑了一步,乌龟就已经赢了?

乌龟跑了25步
乌龟跑了26步
乌龟跑了27步
乌龟跑了28步
兔子跑了1步
乌龟跑了29步
乌龟跑了30步
乌龟跑了31步
乌龟跑了32步
乌龟跑了33步
乌龟跑了34

第三种(了解)
实现Callable接口
可以定义返回值,可以抛出异常

package com.company.test1;
//实现Callable接口

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

public class CallableTest implements Callable<Boolean> {
    private String url;//网络图片地址
    private String name;//保存的文件名
    public CallableTest(String url,String name){
        this.url=url;
        this.name=name;
    }
    @Override
    public Boolean call(){
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名"+name);
        return true;
    }
    public static void main(String[] args) throws ExecutionException,InterruptedException {
        CallableTest t1 = new CallableTest("http://pic17.nipic.com/20111025/8665520_092922298171_2.jpg","2.jpg");
        CallableTest t2 = new CallableTest("http://pic17.nipic.com/20111025/8665520_092922298171_2.jpg","3.jpg");
        CallableTest t3 = new CallableTest("http://pic17.nipic.com/20111025/8665520_092922298171_2.jpg","4.jpg");
        //创建执行服务
        ExecutorService ser= Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1=ser.submit(t1);
        Future<Boolean> r2=ser.submit(t2);
        Future<Boolean> r3=ser.submit(t3);
        //获取结果
        boolean rs1=r1.get();
        boolean rs2=r2.get();
        boolean rs3=r3.get();
        //关闭服务
        ser.shutdownNow();
    }
}
//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,Downloader方法出现问题");
        }
    }
}

运行

下载的文件名2.jpg
下载的文件名3.jpg
下载的文件名4.jpg

3.网图下载(Thread应用)

下载commons-io.jar 下载

package com.company.test1;

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;

//Thread实现多线程同步下载图片
public class ThreadTest02 extends Thread{
    private String url;//网络图片地址
    private String name;//保存的文件名
    public ThreadTest02(String url,String name){
        this.url=url;
        this.name=name;
    }
    @Override
    public void run(){
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名"+name);
    }
    public static void main(String[] args) {
        ThreadTest02 t1 = new ThreadTest02("http://pic17.nipic.com/20111025/8665520_092922298171_2.jpg","2.jpg");
        ThreadTest02 t2 = new ThreadTest02("http://pic17.nipic.com/20111025/8665520_092922298171_2.jpg","3.jpg");
        ThreadTest02 t3 = new ThreadTest02("http://pic17.nipic.com/20111025/8665520_092922298171_2.jpg","4.jpg");
        t1.start();
        t2.start();
        t3.start();
    }
}
//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,Downloader方法出现问题");
        }
    }
}

执行

在这里插入图片描述

4.静态代理模式

真实对象和代理对象都要实现同一个接口,代理对象要代理真实角色
好处:
代理对象可以做很多真实对象做不了的事情
真实对象专注做自己的事情
案例–婚庆公司做代理,解决你结婚事宜


public class StaticProxy {
    public static void main(String[] args) {
        You you = new You();
        new Thread(()-> System.out.println("我爱你")).start();

        new WeddingCompany(new You()).HappyMarry();
//        WeddingCompany weddingCompany = new WeddingCompany(you);
//        weddingCompany.HappyMarry();
    }

}
interface Marry{
    void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry{
    @Override
    public void HappyMarry(){
        System.out.println("结婚了,你很开心");
    }
}
//代理角色,处理你结婚事宜
class WeddingCompany implements Marry{

    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarry(){
        before();
        this.target.HappyMarry();
        after();
    }

    private void after() {
        System.out.println("结婚之后,收尾款");
    }

    private void before() {
        System.out.println("结婚之前,布置现场");
    }
}

运行

我爱你
结婚之前,布置现场
结婚了,你很开心
结婚之后,收尾款

5.Lamda表达式

为什么要使用lamda表达式

  1. 避免匿名内部类定义过多
  2. 可以让代码看起来很简洁
  3. 去掉一堆没有意义的代码,只留下其核心的逻辑
package lamdaTest;

public class LamdaTest01 {
        //3.静态内部类
        static  class Like2 implements ILike {
            @Override
            public void lamda() {
                System.out.println("i like lamda2");
            }
        }
    public static void main(String[] args) {
        ILike like = new Like();
        like.lamda();

        like=new Like2();
        like.lamda();

        //4.局部内部类
        class Like3 implements ILike {
            @Override
            public void lamda() {
                System.out.println("i like lamda3");
            }
        }
        like=new Like3();
        like.lamda();

        //5.匿名内部类,没有类的名称,必须借助接口或者父类
        like=new ILike() {
            @Override
            public void lamda() {
                System.out.println("i like lamda4");
            }
        };
        like.lamda();
        //6.用lamda简化
        like=()->{
            System.out.println("i like lamda5");
        };
        like.lamda();
    }
}
//1.定义一个函数式接口
interface ILike{
    void lamda();
}//2.实现类
class Like implements ILike{
    @Override
    public void lamda(){
        System.out.println("i like lamda1");
    }

}

运行

i like lamda1
i like lamda2
i like lamda3
i like lamda4
i like lamda5

带参数类型

package lamdaTest;

public class LamdaTest02 {
    public static void main(String[] args) {
        ILove love =null;
        /*ILove love = (int a) -> {
            System.out.println("i love you--->" + a);
        };
        //简化1.去掉参数类型
        love = (a) -> {
            System.out.println("i love you--->" + a);
        };*/
        /*//简化2.简化括号
        love = a -> {
            System.out.println("i love you--->" + a);
        };*/
        //简化3.去掉花括号

        love = a ->System.out.println("i love you--->" + a);

        //总结
        //lamda表达式只能在有一行代码的情况下才能简化成为一行,如果有多行,就用代码块包裹
        //前提是接口为函数式接口
        //多个参数也可以去掉参数类型,必须加括号
        
        love.love(520);
    }
}
interface ILove{
    void love(int a);
}

6.线程状态

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞
(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自
运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
1. 新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配
内存,并初始化其成员变量的值

2. 就绪状态(RUNNABLE)
当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和
程序计数器,等待调度运行。
3. 运行状态(RUNNING)
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状
态。
4. 阻塞状态(BLOCKED)
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。
直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状
态。阻塞的情况分三种:
等待阻塞(o.wait->等待对列):
运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。
同步阻塞(lock->锁池)
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
其他阻塞(sleep/join)
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. 线程死亡(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。
正常结束
1.run()或 call()方法执行完成,线程正常结束。
异常结束
2. 线程抛出一个未捕获的 Exception 或 Error。
调用 stop
3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
在这里插入图片描述

7.线程终止

不推荐使用JDK提供的stop(),destroy()方法

程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关
闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:
thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子
线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用
thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈
现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因
此,并不推荐使用 stop 方法来终止线程。

推荐线程自己停止下来
建议使用一个标志位进行终止变量,当flag=false,则终止线程运行

package lamdaTest;

public class TestStop implements Runnable{
    //设置一个标识位
    private boolean flag=true;
    @Override
    public void run(){
        int i=0;
        while (flag){
            System.out.println("run--thread"+i++);
        }
    }
   //设置一个公开的方法停止线程,转换标志位
    public void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            if (i==900){
                //调用stop方法切换标志位,让线程停止
                testStop.stop();
                System.out.println("线程停止了");
            }
        }
    }
}

main897
main898
main899
main900
run--thread911
线程停止了
main901
main902
main903
main904

8.线程休眠

  1. sleep指定当前线程阻塞的毫秒数
  2. sleep存在异常InterruptedException
  3. sleep时间达到后线程进入就绪状态
  4. sleep可以模拟网络延时,倒计时等
  5. 每一个对象都有一个锁,sleep不会释放锁
    模拟延时参考前面抢票案例的代码
    模拟倒计时
package lamdaTest;
//模拟倒计时
public class TestSleep {
    public static void main(String[] args) {
        try {
            temDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void temDown() throws InterruptedException{
        int num=10;
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }
}

运行,每隔一秒输出一个数字

获取系统当前时间

Date date = new Date(System.currentTimeMillis());
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                date=new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值