Java多线程

1.初识多线程

1.概述

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理” 。

2.原理

  • 实现多线程是采用一种并发执行机制

  • 并发执行机制原理:简单地说就是把一个处理器划分为若干个短的时间片,每个时间片依次轮流地执行处理各个应用程序,由于一个时间片很短,相对于一个应用程序来说,就好像是处理器在为自己单独服务一样,从而达到多个应用程序在同时进行的效果

  • 多线程就是把操作系统中的这种并发执行机制原理运用在一个程序中,把一个程序划分为若干个子任务,多个子任务并发执行,每一个任务就是一个线程。这就是多线程程序

3.优缺点

优点

  • 使用线程可以把占据时间长的程序中的任务放到后台去处理。
  • 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
  • 程序的运行速度可能加快。
  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等 。
  • 多线程技术在IOS软件开发中也有举足轻重的作用。

缺点

  • 如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换 。

  • 更多的线程需要更多的内存空间 。

  • 线程可能会给程序带来更多“bug”,因此要小心使用。

  • 线程的中止需要考虑其对程序运行的影响 。

  • 通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生 。

4.优势

​ 多进程程序结构和多线程程序结构有很大的不同,多线程程序结构相对于多进程程序结构有以下的优势:

  • 方便的通信和数据交换

​ 线程间有方便的通信和数据交换机制。对于不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。

  • 更高效地利用CPU

​ 使用多线程可以加快应用程序的响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作置于一个新的线程,就可以避免这种尴尬的情况。

同时,多线程使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上 。

5.应用

​ 无论是过去还是现在,世界上大多数计算机仍然采用的是冯·诺依曼结构,这种结构的特点就是顺序处理,一个处理器在同个时刻只能处理一件事情。 Windows 95/NT采用一种全新的任务调度策略,它把一个进程划分为多个线程,每个线程轮流占用CPU的运算时间,操作系统不断地把线程挂起、唤醒、再挂起、再唤程,如此反复,由于现在CPU的速度比较快,给人的感觉是多个线程在同时执行,就好像有多个CPU存在于计算机中一样。

多线程的一个典型例子是:用资源管理器复制文件时,一方面在进行磁盘读写操作,同时一张纸不停地从一个文件夹飘到另一个文件夹,这个飘的动作实际上是一段视频剪辑,也就是说,资源管理器能够同时进行磁盘读写和播放视频剪辑 。

2.线程与多线程

线程是系统对代码的执行进程,如果将系统当做一个员工,被安排执行某个任务的时候,他不会对任何其他的任务作出响应。只有当这个任务执行完毕,才可以重新给他分配任务。一个程序都有一个主线程,负责执行程序必要的任务。

​ 当我们处理一个消耗大的任务(如上传或下载图片),如果让主线程执行这个任务,它会等到动作完成,才继续后面的代码。在这段时间之内,主线程处于“忙碌”状态,也就是无法执行任何其他功能。体现在界面上就是,用户的界面完全“卡死” 。

多线程是指,将原本线性执行的任务分开成若干个子任务同步执行,这样做的优点是防止线程“堵塞”,增强用户体验和程序的效率。缺点是代码的复杂程度会大大提高,而且对于硬件的要求也相应地提高 。

3.线程的创建

1.继承Thread类

/**
 * 创建线程方式一:
 * 1.继承Thread类  
 * 2.重写run方法  
 * 3.调用start开启线程
 * 注意:线程不一定开启就立即执行,有cpu调度执行
 */
public class TestThread1 extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码"+i);
        }
    }

    public static void main(String[] args) {

        //创建一个线程对象,并调用start()方法开启线程
        TestThread1 testThread1 = new TestThread1();
        testThread1.start();

        //main线程,主线程
        for (int i = 0; i < 2000; i++) {
            System.out.println("我在学习多线程"+i);
        }
    }
}

练习Thread,实现多线程同步下载图片

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

//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread {

    private String url;
    private String name;

    public TestThread2(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) {
        TestThread2 t1 = new TestThread2("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","1.jpeg");
        TestThread2 t2 = new TestThread2("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","2.jpeg");
        TestThread2 t3 = new TestThread2("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","3.jpeg");
        //多线程的执行不一定按顺序,顺序由cpu调度
        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方法出现问题");
        }
    }
}

2.实现Runnable接口,重写run()方法

/**
 * 创建线程方式二: 
 * 1.实现Runnable接口 
 * 2.重写run()方法  
 * 3.执行线程需要丢入runnable接口实现类,调用start()方法开启线程
 */

public class TestTheadRunnable1 implements Runnable {

    public void run() {
        //线程执行体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码-----"+i);
        }
    }

    public static void main(String[] args) {
        TestTheadRunnable1 testTheadRunnable = new TestTheadRunnable1();
//        Thread thread = new Thread(testTheadRunnable);
//        thread.start();
        new Thread(testTheadRunnable).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("我在学习多线程*****"+i);
        }
    }
}

练习Thread,实现多线程同步下载图片

import com.dzj.thread.TestThread2;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//练习Thread,实现多线程同步下载图片
public class TestThreadRunnable2 implements Runnable {

    private String url;
    private String name;

    public TestThreadRunnable2(String url,String name){
        this.url = url;
        this.name = name;
    }

    //下载图片线程的执行体
    public void run() {
        WebDownLoader2 webDownLoader = new WebDownLoader2();
        webDownLoader.downloader(url,name);
        System.out.println("下载了文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","1.jpeg");
        TestThread2 t2 = new TestThread2("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","2.jpeg");
        TestThread2 t3 = new TestThread2("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","3.jpeg");
        //多线程的执行不一定按顺序,顺序由cpu调度
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }
}

class WebDownLoader2{
    //下载方法
    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方法出现问题");
        }
    }
}

3.实现Callable接口,重写call()接口

import org.apache.commons.io.FileUtils;

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

/**
 * 创建线程方式三:
 *  1.实现Callable接口 
 *  2.重写call方法 
 *  3.创建服务  
 *  4.提交执行 
 *  5.获取结果 
 *  6.关闭服务
 * callable的好处
 *  1.可以定义返回的值
 *  2.可以抛出异常
 */
//利用callable下载图片案例
public class ThreadCallable implements Callable<Boolean> {
    private String url;
    private String name;

    public ThreadCallable(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 {
        ThreadCallable t1 = new ThreadCallable("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","1.jpeg");
        ThreadCallable t2 = new ThreadCallable("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","2.jpeg");
        ThreadCallable t3 = new ThreadCallable("https://img2.baidu.com/it/u=2299066483,869409599&fm=253&fmt=auto&app=120&f=JPEG?w=1000&h=500","3.jpeg");
        //多线程的执行不一定按顺序,顺序由cpu调度
        //创建执行服务
        ExecutorService service = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> r1 = service.submit(t1);
        Future<Boolean> r2 = service.submit(t2);
        Future<Boolean> r3 = service.submit(t3);

        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        //关闭服务
        service.shutdownNow();

        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
    }
}

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.多线程案例

1.模拟购票

//发现问题,多个线程操作同一个资源的情况下,线程不安全,数据紊乱,这就是线程的并发问题?
public class TestThreadTicket implements Runnable {
    private int ticket = 10;
    public void run() {
        while(true){

            if(ticket<=0)
                break;

            //模拟延时,放大问题的发生性
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

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

    public static void main(String[] args) {
        TestThreadTicket ticket = new TestThreadTicket();
        new Thread(ticket,"小明").start();
        new Thread(ticket,"小江").start();
        new Thread(ticket,"小红").start();
    }
}

2.模拟龟兔赛跑

//模拟龟兔赛跑
public class Race implements Runnable {

    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if(Thread.currentThread().getName().equals("兔子") && i%10==1){
                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;
        }{
            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();
    }
}

5.接口实现类(lambda)

1.实现类的多种写法

package com.dzj.lambda;

public class TestLambda1 {
    //3.写法二:静态内部类
    static class Like2 implements ILike{
        public void lambda() {
            System.out.println("I like lambda2");
        }
    }
    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();
        System.out.println("方式1:"+like.getClass().getClassLoader());

        like = new Like2();
        like.lambda();
        System.out.println("方式2:"+like.getClass().getClassLoader());

        //4.写法三:局部内部类
        class Like3 implements ILike{
            public void lambda() {
                System.out.println("I like lambda3");
            }
        }
        like = new Like3();
        like.lambda();
        System.out.println("方式3:"+like.getClass().getClassLoader());

        //5.写法四:匿名内部类,没有类的名称,必须借助接口或者父类
        like = new ILike(){
            @Override
            public void lambda() {
                System.out.println("I like lambda4");
            }
        };
        like.lambda();
        System.out.println("方式4:"+like.getClass().getClassLoader());

        //6.写法五:用lambda简化
        like = ()-> System.out.println("I like lambda5");
        like.lambda();
        System.out.println("方式5:"+like.getClass().getClassLoader());
    }
}

//1.定义一个函数式接口
interface ILike{
    void lambda();
}

//2.写法一:实现类
class Like implements ILike{
    public void lambda() {
        System.out.println("I like lambda1");
    }
}

2.带参的实现类写法

package com.dzj.lambda;

public class TestLambda2 {
    //3.静态内部类
    static class Love2 implements ILove{
        //注意:这里加上static修饰Love2是因为main是静态方法,它不能访问类中得非静态方法、变量
        @Override
        public void love(int a) {
            System.out.println("i love you -->"+a);
        }
    }

    public static void main(String[] args) {
        ILove like= new Love();
        like.love(1);

        like = new Love2();
        like.love(2);

        //4.局部内部类
        class Love3 implements ILove{
            @Override
            public void love(int a) {
                System.out.println("i love you -->"+a);
            }
        }
        like = new Love3();
        like.love(4);

        //5.匿名内部类
        like = new ILove() {
            @Override
            public void love(int a) {
                System.out.println("i love you -->"+a);
            }
        };
        like.love(5);

        //6.lambda简化
        like = a -> System.out.println("i love you -->"+a);
        like.love(6);
    }
}

//1.定义一个函数式接口
interface ILove{
    void love(int a);
}

//2.实现类
class Love implements ILove{
    @Override
    public void love(int a) {
        System.out.println("i love you -->"+a);
    }
}

3.lambda表达式实现接口

前提:

  • JDK8的新特性

  • 必须为函数式接口:接口中只有一个方法

package com.dzj.lambda;

public class TestLambda3 {
    public static void main(String[] args) {
        ILoveU like = null;
        //使用lambda表达式
//        like = (int a) ->{
//            System.out.println("i love you-->"+a);
//        };
//        like.Love(520);

        /**
         * lambda的简化
         * 1.参数类型可以简化
         * 2.只有一个参数的时,()也可以省略
         * 3.方法体里只有一条语句时,{}也可以省略
         */
        like = (a,b,c) ->{
            System.out.println("i love you-->"+a+b+c);
        };
        like.Love(520,502,"dengzhijiang");

    }

}

//1.定义一个函数式接口
interface ILoveU{
    void Love(int a,int b,String c);
}

6.静态代理模式

/**
 * 静态代理模式
 *      真实对象和代理对象都要实现同一个接口,
 *      代理对象要代理真实对象
 * 好处:
 *      真实对象只需专注自己要干的事情
 *      代理对象可以帮助处理一些杂事,比如布置现场、主持婚礼等
 */
public class StaticProxy {

    public static void main(String[] args) {
        //真实角色
        You you = new You();

        //把需要代理的真实角色传给代理对象
        WeddingCompany weddingCompany = new WeddingCompany(you);

        //代理对象执行业务
        weddingCompany.HappyMarry();

        //使用lambda表达式实现线程
        new WeddingCompany(new You()).HappyMarry();
        new Thread(()-> System.out.println("nihao")).start();

    }
}

//真实对象要干的事情
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() {
        BeforeMarry();//结婚前
        this.target.HappyMarry();//结婚,真正的执行对象其实还是真实角色
        AfterMarry();//结婚后
    }

    private void AfterMarry() {
        System.out.println("结婚之后,洞房花烛夜");
    }

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

7.线程的状态

1.线程的五大状态

1. 新建状态(New): 线程对象被创建后,就进入了新建状态。

  • 例如,Thread thread = new Thread()。

2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。

  • 例如,thread.start(),处于就绪状态的线程,随时可能被CPU调度执行。

  • 不允许对一个线程多次使用start。

  • 线程执行完成之后,不能试图用start将其唤醒。

    其他状态 ---> 就绪

  • 线程调用start(),新建状态转化为就绪状态。

  • 线程sleep(long)时间到,等待状态转化为就绪状态。

  • 阻塞式IO操作结果返回,线程变为就绪状态。

  • 其他线程调用join()方法,结束之后转化为就绪状态。

  • 线程对象拿到对象锁之后,也会进入就绪状态。

3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

  • 处于就绪状态的线程获得了CPU之后,真正开始执行run()方法的线程执行体时,意味着该线程就已经处于运行状态。需要注意的是,对于单处理器,一个时刻只能有一个线程处于运行状态。对于抢占式策略的系统来说,系统会给每个线程一小段时间处理各自的任务。时间用完之后,系统负责夺回线程占用的资源。下一段时间里,系统会根据一定规则,再次进行调度。

    运行状态 ---> 就绪状态

  • 线程失去处理器资源。线程不一定完整执行的,执行到一半,说不定就被别的线程抢走了。

  • 调用yield()静态方法,暂时暂停当前线程,让系统的线程调度器重新调度一次,它自己完全有可能再次运行。

  • 提示调度程序,当前线程愿意放弃当前对处理器的使用。这时,当前线程将会被置为就绪状态,和其他线程一样等待调度,这时候根据不同优先级决定的概率,当前线程完全有可能再次抢到处理器资源。

4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

  • (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
  • (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
  • (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

  • run()和call()线程执行体中顺利执行完毕,线程正常终止
  • 线程抛出一个没有捕获的Exception或Error。
  • 需要注意的是:主线成和子线程互不影响,子线程并不会因为主线程结束就结束。

img

2.状态案例说明

1.观察线程状态

public class ThreadState{

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程结束了");
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);//new

        //观察启动后
        thread.start();//run
        state = thread.getState();
        System.out.println(state);

        while(state != Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);
        }

//        thread.start();
        //线程中断或者结束,一旦进入死亡状态,就不能再次启动
    }
}

2.模拟网络延时(sleep)

//模拟网络延时,放大问题的发生性
public class ThreadSleep implements Runnable {
    private int ticket = 10;
    public void run() {
        while(true){

            if(ticket<=0)
                break;

            //模拟延时
            try {
                Thread.sleep(100);//线程就如阻塞状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

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

    public static void main(String[] args) {
        ThreadSleep ticket = new ThreadSleep();
        new Thread(ticket,"小明-->").start();
        new Thread(ticket,"老师-->").start();
        new Thread(ticket,"黄牛党-->").start();
    }
}

3.模拟倒计时

import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadSleep2 {

    public static void main(String[] args) throws InterruptedException {
        tenDown();

        Date date = new Date(System.currentTimeMillis());//获取系统当前时间

        while(true){
            Thread.sleep(1000);//线程就如阻塞状态
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
            date = new Date(System.currentTimeMillis());//更新系统时间
        }

    }


    //模拟倒计时
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while(true){
            Thread.sleep(1000);
            System.out.println(num--);
            if(num<=0)
                break;
        }
    }
}

4.测试线程终止

/**
 * 测试线程停止
 * 1.建议线程正常停止--->利用次数,不建议死循环
 * 2.建议使用标志位--->设置一个标志位
 * 3.不要使用stop和destroy等过时或者JDK不建议使用的方法
 */
public class ThreadStop 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) {

        ThreadStop threadStop = new ThreadStop();
        new Thread(threadStop).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            if(i==500){  //调用方法,装换标志位,终止线程
                threadStop.stop();
                System.out.println("该线程停止了...");
            }
        }
    }
}

8.线程的方法

1.线程休眠(sleep)

​ 线程暂缓执行,等到预计时间再执行。线程休眠会交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

public class ThreadSleep2 {

    public static void main(String[] args) throws InterruptedException {
        tenDown();

        Date date = new Date(System.currentTimeMillis());//获取系统当前时间

        while(true){
            Thread.sleep(1000);//线程休眠
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
            date = new Date(System.currentTimeMillis());//更新系统时间
        }

    }
    
    //模拟倒计时
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while(true){
            Thread.sleep(1000);
            System.out.println(num--);
            if(num<=0)
                break;
        }
    }
}

2.线程让步(yield)

​ 暂停当前正在执行的线程对象,并执行其它线程。就是当前线程交出cpu权限,让cpu执行其他线程。

yield()和sleep()的比较

  • 相同点
    • 交出CPU,但是不会交出锁
  • 不同点
    • yield不能控制具体的交出cpu时间,并且yield()只能让相同优先级的线程有获取cpu执行的机会。
    • yield()不会进入阻塞,直接进入就绪状态。
/**
 * 测试线程礼让
 * 礼让不一定成功,看CPU调度
 */
public class ThreadYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        if(Thread.currentThread().getName().equals("a"))
            Thread.yield();//线程礼让,礼让不一定成功
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

3.线程插队(join)

​ 如果在当前线程(main或其他线程)中调用线程对象join,当前线程阻塞,直到线程对象的run执行完毕,当前线程阻塞才结束,继续执行。

public class ThreadJoin implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("VIP来了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadJoin threadJoin = new ThreadJoin();
        Thread thread = new Thread(threadJoin);
//        thread.start();
        for (int i = 0; i < 20; i++) {
            if (i==5){
                thread.start();//开启线程
                thread.join();//插队,但是如果“thread.start()”放在循环体的外部,在插队之前的200次里,两个线程还是并发的
            }

            System.out.println("main"+i);
        }
    }
}

9.线程优先级

/**
 * 线程优先级  1-10
 * 优先级低并意味着不会执行,只是先执行的概率低,
 * 优先级高也并不一定就优先执行,只是优先执行的概率高,这都是看CPU调度的
 */
public class ThreadPriority {
    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        //先设置优先级,再启动
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();

        t5.setPriority(7);
        t5.start();

        t6.setPriority(8);
        t6.start();
    }
}


class MyPriority implements Runnable{
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

10.守护线程

/**
 * 测试守护线程
 * 上帝守护你
 */
public class ThreadDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);//设置为守护线程,默认false表示是用户线程,正常的线程都是用户线程
        thread.start();//上帝守护线程启动,虚拟机不会等待守护线程执行完毕

        new Thread(you).start();// 你(默认用户线程)  用户线程启动
    }

}

//上帝
class God implements Runnable{

    @Override
    public void run() {
        while(true){
            System.out.println("上帝一直守护着你");
        }
    }
}

//你
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {  //人生不过三万天
            System.out.println("你一生都开心的活着...");
        }
        System.out.println("=====GoodBey!World!=====");
    }
}

11.锁机制

1.不安全的买票

/**
 * 不安全的买票
 * 线程不安全,有负数
 * 解决方法:加锁,实现线程同步
 */
public class UnsafeBuyTicket {
    public static void main(String[] args) {

        BuyTicket station = new BuyTicket();
        new Thread(station,"你们").start();
        new Thread(station,"我").start();
        new Thread(station,"黄牛党").start();
    }
}

class BuyTicket implements Runnable{

    //票
    private int ticketNum = 10;
    boolean flag = true;

    @Override
    public void run() {
        while(flag){
            try {
                Buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //买票,synchronized,同步方法,默认锁的对象是this
    public synchronized void Buy() throws InterruptedException {

        //判断是否有票
        if(ticketNum <= 0){
            this.flag = false;
            return;
        }
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNum--+"票");
    }
}

2.不安全的取钱

/**
 * 不安全的取钱,同时对同一个账户取钱
 * 两个人去银行取钱,账户
 */
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(1000,"结婚基金");
        Drawing you = new Drawing(account,50, "You");
        Drawing girlFriend = new Drawing(account,100, "girlFriend");

        you.start();
        girlFriend.start();

    }
}

//账户
class Account{
    int money;//余额
    String name;//账户名

    public Account(int money,String name){
        this.money = money;
        this.name = name;
    }
}

//银行:模拟取钱
class Drawing extends Thread{

    Account account;//账户
    int drawingMoney;//取了多少钱
    int nowMoney;//现在手里有多少钱

    public Drawing(Account account,int drawingMoney,String name){
        super(name);//线程的名字
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public void run() {
        /**
         * synchronized默认锁的对象是this
         * 但是上锁的对象不能一定就是this,
         * 上锁的那个对象应该是变化的、需要修改的数据量、线程共同操作的资源、需要增删改的对象
         */
        synchronized(account){
            //判断有没有钱
            if(account.money-drawingMoney < 0){
                System.out.println(Thread.currentThread().getName()+"-->钱不够,取不了");
                return;
            }

            //模拟延时,放大问题的发生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //卡内余额 = 余额 - 你取的钱
            account.money = account.money - drawingMoney;
            //你手里的钱
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name+"余额为:"+account.money+"万");
            //Thread.currentThread().getName() = this.getName(),因为这个类继承了Thread,所以可以当前类调用Thread方法
            System.out.println(this.getName()+"手里的钱:"+nowMoney+"万");
        }
    }
}

3.线程不安全的集合

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

public class UnsafeList {

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        Thread.sleep(150);
        //注意:这里sleep()时间太短的话,其他线程还没执行完毕,线程里的add()还没执行完毕,
        //很可能主线程已经执行完毕(循环体),得到的size()就是主线程执行完毕那一瞬间的长度,
        //所以这里的sleep()要设置的大点,等其他线程执行完毕,再把size()输出
        System.out.println(list.size());
    }
}

4.线程安全的 JUC

import java.util.concurrent.CopyOnWriteArrayList;
/**
 * 因为本身就是线程安全的,所以无需外部再提供安全机制
 */
public class TestJUC {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(100);
        System.out.println(list.size());
    }
}

5.死锁

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

        MakeUp g1 = new MakeUp(0,"灰姑凉");
        MakeUp g2 = new MakeUp(1,"白雪公举");
        g1.start();
        g2.start();
    }
}

//口红
class LipStick{

}

//镜子
class Mirror{

}

class MakeUp extends Thread{

    //需要的资源只有一分,用static保证只有一份
    static LipStick lipStick = new LipStick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String girlName;//使用化妆品的女孩

    MakeUp(int choice,String girlName){
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void makeup() throws InterruptedException {
        /**
         * 死锁的产生:
         *     一个拿着lipStick锁不松开,还要mirror锁;
         *     另一个拿着mirror锁不松开,还要lipStick,形成僵持状态
         * 解决的方法:不能同时抱住多把锁,用完要及时松开
         */
        if(choice==0){
            synchronized (lipStick){
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);
//                //一秒钟后想获得镜子的锁,此时g2中的mirror锁还没有释放,所以获取不到
//                synchronized (mirror){
//                    System.out.println(this.girlName+"获得镜子的锁");
//                }
            }
            //一秒钟后想获得镜子的锁,此时lipStick锁已释放,等待g2释放mirror锁并获取
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
            }
        }else{
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
                Thread.sleep(2000);
//                //两秒钟后想获得镜子的锁,此时g1中的lipStick锁还没有释放,所以获取不到
//                synchronized (lipStick){
//                    System.out.println(this.girlName+"获得口红的锁");
//                }
            }
            //两秒钟后想获得镜子的锁,此时mirror锁已释放,等待g1释放lipStick锁并获取
            synchronized (lipStick){
                System.out.println(this.girlName+"获得口红的锁");
            }
        }
    }
}

6.测试 lock

public class TestLock {

    public static void main(String[] args) {
        Buyticket buyticket = new Buyticket();
        new Thread(buyticket,"小明").start();
        new Thread(buyticket,"我").start();
        new Thread(buyticket,"黄牛党").start();
    }
}

class Buyticket implements Runnable{

    private int ticketNum = 10;
    private final ReentrantLock lock = new ReentrantLock();//ReentrantLock可重用锁

    @Override
    public void run() {
            while(true){
                try {
                    lock.lock();
                    if(ticketNum>0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNum--+"票");
                    }else{
                        break;
                    }
                }finally {
                    lock.unlock();//释放锁
                }
        }

    }
}

12.生产者消费者模型

1.管程法

/**
 * 测试:生产者消费者模型-->利用缓冲区解决:管程法
 * 生产者、消费者、产品、缓冲区
 */

public class TestPC {

    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }
}

//生产者
class Productor extends Thread{
    SynContainer container;

    public Productor(SynContainer container) {
        this.container = container;
    }

    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了第"+i+"只鸡");
        }
    }
}

//消费者
class Consumer extends Thread{
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }

    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了-->第"+container.pop().id+"只鸡");
        }
    }
}

//产品
class Chicken{
    int id;//产品编号

    public Chicken(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer{

    Chicken[] chickens = new Chicken[10]; //需要一个容器大小
    int count = 0;//容器计数器

    //生产者放入产品
    public synchronized void push(Chicken chicken){
        //如果容器满了,就需要等待消费者消费
        if(count==chickens.length){
            //通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,我们就需要就可以继续丢进产品
        chickens[count] = chicken;
        count++;
        //通知消费者消费,唤醒等待的消费者?
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Chicken pop(){
        //判断能否消费
        if(count==0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken = chickens[count];
        //吃完了,通知生产者生产,唤醒等待生产的生产者
        this.notifyAll();
        return chicken;
    }

}

2.信号灯法

/**
 * 测试生产者消费者问题2:信号灯法,标志位解决
 *
 */
public class TestPC2 {

    public static void main(String[] args) {

        TV tv = new TV();
        new Actor(tv).start();
        new Watcher(tv).start();
    }
}

//生产者-->演员
class Actor extends Thread{
    TV tv;
    public Actor(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2==0){
                this.tv.play("厉害了了我的国播放中...");
            }else{
                this.tv.play("抖音:记录美好生活");
            }
        }
    }
}

//消费者-->观众
class Watcher extends Thread{

    TV tv;
    public Watcher(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

//产品-->节目
class TV{
    //演员表演,观众等待 T
    //观众观看,演员等待 F
    String voice;//表演的节目
    boolean flag = true;

    //表演
    public synchronized void play(String voice){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:"+voice);
        //通知观众观看
        this.notifyAll();//通知唤醒
        this.voice = voice;
        this.flag = ! this.flag;
    }

    //观看
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:"+voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

13.线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPool {
    public static void main(String[] args) {
        
        //1.创建服务,创建线程池,newFixedThreadPool()参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2.关闭连接
        service.shutdown();

//        new Thread(new MyThread()).start();
//        new Thread(new MyThread()).start();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

14.回顾

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 回顾线程的创建方式
 */
public class ThreadNew {
    public static void main(String[] args){

        new MyThread1().start();//方式一
        new Thread(new MyThread2()).start();//方式二

        //方式三
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
        new Thread(futureTask).start();

        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//1.继承Thread
class MyThread1 extends Thread{

    @Override
    public void run() {
        System.out.println("MyThread1");
    }
}

//2.实现Runnable接口
class MyThread2 implements Runnable{

    @Override
    public void run() {
        System.out.println("MyThread2");
    }
}

//3.实现Callable接口
class MyThread3 implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("MyThread3");
        return 100;
    }
}
posted @ 2021-09-20 11:38  小公羊  阅读(96)  评论(0编辑  收藏  举报