JAVASE-27: 多线程概述及使用

多线程

要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。
什么是进程呢?通过任务管理器我们就可以看到进程的存在。
进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。

多进程的意义:
单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。

什么是线程:
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到CPU的执行权的概率应该比较单线程程序抢到的概率要大。那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率。但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性

大家注意两个词汇的区别:并行并发
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。

并发 : 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务。
并行 : 指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行

类 Thread 线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。


创建新执行线程的三种方法

方法一:

1.一种方法是将类声明为 Thread 的子类。
2.该子类应重写 Thread 类的 run 方法。
3.接下来可以分配并启动该子类的实例。

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
    }
    public void show(){
        System.out.println("abc");
    }
}

XXXX.run() 方法中的代码 将来由线程执行 一般会在其中放入一些耗时的代码

直接new对象调方法 并没有线程创建出来 这些代码还是运行在main中

正确开启线程方法–XXXX.start();

多次启动一个线程是非法的,特别是当线程结束执行后;想再开启可以再new一个新的对象

public class MyTest {
    public static void main(String[] args) {
        MyThread th= new MyThread();
        th.start();
        MyThread th2 = new MyThread();
        th2.start();
    }
}

this.getName(); 获取线程的名字

XXXX.setName(); 设置线程名字

多个线程同时 交替执行

Thread.currentThread(); 获取当前正在运行的线程对象

public class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }
    public MyThread() {
    }
    @Override
    public void run() {
        for (int i = 0; i <100; i++) {
           // System.out.println(this.getName()+"-子线程执行"+i);
            System.out.println(Thread.currentThread().getName()+ "-子线程执行" + i);
        }
    }
}

拿线程优先级 XXXX.getPriority();

设置线程优先级 XXXX.setPriority(); 最高MAX_PRIORITY=10;最低 MAX_PRIORITY=1;默认是5

public class MyTest {
    public static void main(String[] args) {
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        th1.setName("线程A");
        th2.setName("线程B");
        // 设置线程的优先级范围是 1---10  默认是5
        th1.setPriority(Thread.MAX_PRIORITY);
        th2.setPriority(Thread.MIN_PRIORITY);
        int priority1 = th1.getPriority();
        int priority2 = th2.getPriority();
        System.out.println(priority1);// 10
        System.out.println(priority2);// 1
        th1.start();
        th2.start();
    }
}

让当前线程的休眠 单位是毫秒 Thread.sleep( XXXX ms );

public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

让线程从并发执行变成串行的方法 XXXX.join();
在线程启动start() 后再使用

public static void main(String[] args) throws InterruptedException {
    MyThread th1 = new MyThread();
    MyThread th2 = new MyThread();
    MyThread th3 = new MyThread();
    // 效果就是刘关张三个线程先刘执行完 再让关执行完 最后让张执行完
    th1.setName("刘备");
    th2.setName("关羽");
    th3.setName("张飞");
    th1.start();
    th1.join();
    th2.start();
    th2.join();
    th3.start();
    th3.join();
}

线程礼让: Thread.yield(); 用在线程run()内

暂停当前执行的线程 并执行其他线程 。让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。使用目的是让具有相同优先级的线程能适当轮换。

守护线程 :如果主线程(用户线程)结束后,子线程也立马结束,把子线程设为守护线程,XXXX.setDaemon(true); 在start()方法之前

在主线程关闭后无需手动关闭守护线程,会自动关闭,Java垃圾回收线程就是一个典型的守护线程 ,所有为线程服务而不涉及资源的线程都能设置为守护线程

线程的中断 强制 XXXX.stop(); 存在不安全性

public static void main(String[] args) throws InterruptedException {
    System.out.println("主线程开始执行了");
    MyThread th = new MyThread();
    th.setName("张飞");
    th.start();
    Thread.sleep(10);
    th.stop(); //强制终止线程。
    System.out.println("主线程执行完毕");
}

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

清除阻塞 线程休眠就是阻塞状态的一种 XXXX.interrupt();

不能中断在运行中的线程,只能改变中断的状态而已

如下,如果某个线程正在使用sleep()暂停着,要取消他的等待状态,可以在正在执行的线程里调用interrupt()

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始执行了");
        MyThread th = new MyThread();
        th.setName("张飞");
        th.start();
        th.interrupt();// 清除线程的阻塞
        System.out.println("主线程执行完毕");
    }
}

public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            // 线程休眠,使线程处于了一种阻塞的状态。
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

多线程复制文件:复制两个文件

public class MyTest {
    public static void main(String[] args) throws IOException {
        System.out.println("开始复制文件");
        new CopyMP4Thread().start();
        new CopyTextThread().start();
        System.out.println("文件复制完毕");
    }
}

public class CopyTextThread extends Thread {
    @Override
    public void run() {
        try {
            Files.copy(Paths.get("MyTest.java"), Paths.get("C:C:\\Users\\Administrator\\Desktop\\MyTest.java"), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class CopyMP4Thread extends Thread{
    @Override
    public void run() {
        try {
            Files.copy(Paths.get("Rec 2020-08-16 0002.mp4"), Paths.get("C:\\Users\\Administrator\\Desktop\\Rec 2020-08-16 0002.mp4"), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

作业:多个线程,来共同复制一个文件

package org.westos.test;
import java.io.File;
public class one {
    public static void main(String[] args) {
        // 将一个文件均分给每一个线程一部分---RandomAccessStream中的.seek()
        // 开启多线程复制方法:线程个数,文件长度,源文件,目标文件,以参数形式传入,方法里用一个for循环去开辟新线程
        File file = new File("C:\\Users\\韩晨光\\Desktop\\摘要写作.docx");
        startThread(10,file.length(),"C:\\Users\\韩晨光\\Desktop\\摘要写作.docx","C:\\Users\\韩晨光\\Desktop\\摘要写作2.docx");
    }
    /**
     * 开启多线程复制
     * @param threadnum
     *              线程数
     * @param fileLength
     *             文件大小(用于确认每个线程下载多少东西)
     * @param srcFilePath
     *             源文件目录
     * @param targerFilePath
     *            目标文件目录
     */
    private static void startThread(int threadnum,long fileLength,String srcFilePath,String targerFilePath) {
       long modLength=fileLength%threadnum;
        System.out.println(modLength);
        long targetLength=fileLength/threadnum;
        System.out.println(targetLength);

        for (int i = 0; i < threadnum; i++) {
            System.out.println((targetLength * i) + "-----" + (targetLength * (i + 1)));
            new FileWriteThread((targetLength * i), (targetLength * (i + 1)), srcFilePath, targerFilePath).start();
        }
        if(modLength!=0){
            System.out.println((targetLength * threadnum) + "-----" + (targetLength * threadnum + modLength));
            new FileWriteThread((targetLength * threadnum), targetLength * threadnum + modLength + 1, srcFilePath, targerFilePath).start();
        }
    }
}

线程

package org.westos.test;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class FileWriteThread extends Thread {
    private long begin;
    private long end;
    private RandomAccessFile srcFile;
    private RandomAccessFile targetFile;

    public FileWriteThread(long begin, long end, String srcFilePath, String targerFilePath) {
        this.begin=begin;
        this.end=end;
        try {
            this.srcFile=new RandomAccessFile(srcFilePath,"rw");
            this.targetFile=new RandomAccessFile(targerFilePath,"rw");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

    public void run() {
        try {
            srcFile.seek(begin);
            targetFile.seek(begin);
            int hasRead=0;
            byte[] bytes = new byte[1];
            while (begin<end&&-1!=(hasRead=srcFile.read(bytes))){
                begin+=hasRead;
                targetFile.write(bytes,0,hasRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                srcFile.close();
                targetFile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

创建线程的另外一种方式----implements Runnable

1.实现 Runnable接口的类;
2.该类实现run()方法;
3.分配该类的实例 new对象(创建任务);
4.newThread(将刚创建的对象作为参数传进去=把任务传递进来)。

常用方法:
获取名字 .currentThread().getName()

Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。

扩展性更好

public class MyTest {
    public static void main(String[] args) {
        // 创建任务
        MyRunnable myRunnable = new MyRunnable();
        // 把任务传递进来
        Thread th1 = new Thread(myRunnable,"刘亦菲");
        Thread th2 = new Thread(myRunnable,"范冰冰");
        th1.start();
        th2.start();
    }
}

public class MyRunnable implements Runnable {
        @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"==="+i);
        }
    }
}

创建线程的方式三:

1.创建一个类实现Callable<>接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类 将FutureTask对象作为参数传进去
4.开启线程

public class MyTest {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        Thread thread = new Thread(task);
        thread.start();

        MyCallable myCallable1 = new MyCallable();
        FutureTask<Integer> task1 = new FutureTask<>(myCallable1);
        Thread thread1 = new Thread(task1);
    }
}

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // call()将来是有线程来线程
        System.out.println("线程过来执行了");
        return null;
    }
}

获取异步执行完的结果 .get(); 有返回值

public class MyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable1 = new MyCallable(100);
        FutureTask<Integer> task1 = new FutureTask<>(myCallable1);
        Thread thread = new Thread(task1);
        thread.start();
        // 获取异步执行完的结果
        Integer integer1 = task1.get();
        System.out.println(integer1);


        MyCallable myCallable2= new MyCallable(50);
        FutureTask<Integer> task2= new FutureTask<>(myCallable2);
        Thread thread2 = new Thread(task2);
        thread2.start();
        Integer integer2 = task2.get();
        System.out.println(integer2);
    }
}

public class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 1; i <=num; i++) {
            sum+=i;
        }
        return sum;
    }
}

Runnable、Callable<>的区别:

Runnable重写run方法,没有返回值,无法获取异步执行完之后的结果,run方法无法抛出异常

Callable<>重写call方法,有返回值,可以获取异步执行完之后的结果。 call方法可以抛出异常。


线程安全问题

案例:买电影票 三个线程 卖100张票

当模拟网络延迟情况 会出现一些不合理的数据,也就是说出现了线程安全问题

1.出现重复票的问题—由于原子性(不可再分割性)所导致的 ++ --不是原子性操作,经过读改写操作

2.出现负票—由于线程的随机性导致的

出现线程安全方面问题的条件:

1.多线程环境

2.多个线程在操作共享数据

3.有没有多条语句在操作这个共享变量

我们可以使用同步代码块来解决线程安全方面的问题

synchronized 锁:就是java中任一个对象,多个线程要共享一把锁对象

synchronized (锁对象){
      出现线程安全问题代码
}

synchronized 形容词 同步的 同步化的 (sei n krue nai zi de)

public class MyTest {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable);
        Thread th2 = new Thread(cellRunnable);
        Thread th3 = new Thread(cellRunnable);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

public class CellRunnable implements Runnable{
    // 共享数据,被多个线程所共享。
    int piao = 1000000;
    static Object obj=new Object();
    @Override
    public void run() {
        while (true) {
            // 就是最后一张。piao=1;
            synchronized (obj){
                // 加锁
                try {
                    // 模拟真实情况中,网络延迟的现象。
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (piao > 0) {
                    System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
                }
            }
            // 释放锁
        }
    }
}

给方法加锁synchronized—同步方法,同步方法的默认锁对象是this

public  synchronized void maiPiao(){
        // 加锁
        try {
            // 模拟真实情况中,网络延迟的现象。
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (piao > 0) {
            System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
        }

}

静态同步方法 默认锁对象不是this 是当前类的字节码文件对象

//静态同步方法,用的锁对象是当前类的字节码文件对象
    public static synchronized void maiPiao2() {
        //就是最后一张。piao=1;
        //加锁
        try {
            //模拟真实情况中,网络延迟的现象。
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (piao > 0) {
            System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
        }
    }
}


Lock锁的使用:

new ReentrantLock(); reentrant 可重入

.lock() 获取锁(加锁)

.unlock() 释放锁

建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中

public class MyTest {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable);
        Thread th2 = new Thread(cellRunnable);
        Thread th3 = new Thread(cellRunnable);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}



public class CellRunnable implements Runnable {
    // 共享数据,被多个线程所共享。
    int piao = 100;
    static Object obj = new Object();
    static Lock lock=new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 加锁
            lock.lock(); 
            try{
                if (piao > 0) {
                    try {
                        // 模拟真实情况中,网络延迟的现象。
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                // 释放锁
                lock.unlock();
            }
        }
    }
}


死锁问题:

多个线程由于争抢对方的锁 产生一种互相等待状态

如果出现了同步嵌套,就容易产生死锁问题

public class MyTest {
    public static void main(String[] args) {
        // 死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
        /* 举例:
        中国人和美国人一起吃饭
                中国人使用的筷子
        美国人使用的刀和叉
                中国人获取到了美国人的刀
        美国人获取到了中国人的一根筷子*/
        MyThread th1 = new MyThread(true);
        MyThread th2= new MyThread(false);
        th1.start();
        th2.start();
    }
}


public interface ObjectUtils {
    //创建 两把锁对象
    Object objA=new Object();
    Object objB = new Object();
}
public class MyThread extends Thread{
    boolean flag;
    public MyThread(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
       // 两个或者两个以上的线程, 在抢占CPU的执行权的时候, 都处于等待状态
       if(flag){
           synchronized (ObjectUtils.objA){
               System.out.println("true 线程进来了 持有objA锁");
               synchronized (ObjectUtils.objB){
                   System.out.println("true 线程进来了 持有objB锁");
               }
           }// 释放锁
       }else{
           synchronized (ObjectUtils.objB) {
               System.out.println("false 线程进来了 持有objB锁");
               synchronized (ObjectUtils.objA) {
                   System.out.println("false 线程进来了 持有objA锁");
               }
           }// 释放锁
       }
    }
}

线程的等待唤醒机制

Object 类中
.wait(); 线程等待 一旦等待 就得释放锁 如果再次被唤醒 就从这里再次执行
.notify(); 通知线程 唤醒了正在等待的线程后 他们还要再次争抢

同步的窍门:如果加了同步还有线程安全问题,想两个前提:1,是不是两个或两个以上线程? 2.用的是不是同一把锁?
只同步了一个线程的代码是不行的,要将所有线程运行的代码都用同一把锁同步,这样一个执行同步代码,其他的判断同一把锁,才无法进入自己执行的代码。
即使在一个Runnable的操作代码中是一句, 要同步的也不是一句:是几个要同步的线程要操作的所有代码。

public class resource {
    public String club;
    public String number;
    public boolean flag=false;
}


public class producter extends Thread {
    private resource resource;
    int i = 0;

    public producter(resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (resource) {
                if (resource.flag) {
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (i % 2 == 0) {
                    resource.club = "Milan";
                    resource.number = "马尔蒂尼";
                } else {
                    resource.club = "Inter";
                    resource.number = "萨内蒂";
                }
                resource.flag = true;
                resource.notify();
            }
            i++;
        }
    }
}


public class consumer extends Thread {
    private resource resource;

    public consumer(resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (resource) {
                if (!resource.flag) {
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(resource.club+"==="+resource.number);
                resource.flag=false;
                resource.notify();
            }
        }
    }
}


//test
public class test {
    public static void main(String[] args) {
        resource resource = new resource();
        producter producter = new producter(resource);
        consumer consumer = new consumer(resource);
        producter.start();
        consumer.start();
    }
}
//Milan===马尔蒂尼 Inter===萨内蒂 交替

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值