了解多线程与并发

前言

📋前言📋
💝博客:【无聊大侠hello word】💝
✍有一点思考,有一点想法,有一点理性!✍
✍本文由在下【无聊大侠hello word】原创,首发于CSDN✍

继承Thread类

使用Thread类创建并执行线程的具体步骤如下:

package cmo.basis.Demo;

/**
 * 描述:Thread使用介绍,继承Thread类
 *
 * 总结:
 * 使用Thread类创建并执行线程的具体步骤如下:
 * 1.创建Thread类的子类
 * 2.重写Thread类中run()方法
 * 3.创建Thread子类对象,即创建一个线程对象
 * 4.调用线程对象的start()方法启动线程,之后系统会自动调用重写的run()方法中的具体实现
 */
public class ThreadDemo extends Thread
{
    private Thread t;
    private String threadName;

    public ThreadDemo(String name)
    {
        this.threadName = name;
        System.out.println("创建线程,名称为:"+ threadName);
    }

    @Override
    public void run()
    {
        System.out.println("运行线程 "+ threadName);
        try
        {
            for (int i = 4; i > 0; i--)
            {
                System.out.println("线程名称为:"+ threadName + ","+ i);
                System.out.println("线程休息,时间为:100ms");
                Thread.sleep(100);
            }
        } catch (InterruptedException e)
        {
            System.out.println("线程 "+threadName+" 中断...");
        }
        System.out.println("线程 "+threadName+" 退出,终止...");
    }

    @Override
    public void start()
    {
        System.out.println("启动线程 "+threadName);
        // 避免NPE
        if(t == null)
        {
            t = new Thread(this, threadName);
            t.start();
            return;
        }
        this.start();
    }

    public static void main(String[] args)
    {
        ThreadDemo thread1 = new ThreadDemo("线程-1");
        thread1.start();

        ThreadDemo thread2 = new ThreadDemo("线程-2");
        thread2.start();
    }
}

实现Runnable接口

package cmo.basis.Demo;

/**
 * 描述:实现Runnable接口
 *
 * 总结:
 * 1.定义实现Runnable接口的类,实现run()方法
 * 2.创建Runnable对象并作为Thread类的target参数来创建Thread对象
 * 3.调用start()方法启动线程
 */
public class RunnableDemo implements Runnable
{
    private Thread t;
    private String threadName;

    public RunnableDemo(String name)
    {
        threadName = name;
        System.out.println("创建线程,名称为:"+ threadName);
    }

    @Override
    public void run()
    {
        System.out.println("运行线程 "+ threadName);
        try
        {
            for (int i = 4; i > 0; i--)
            {
                System.out.println("线程名称为:"+ threadName + ","+ i);
                System.out.println("线程休息,时间为:100ms");
                Thread.sleep(100);
            }
        } catch (InterruptedException e)
        {
            System.out.println("线程 "+threadName+" 中断...");
        }
        System.out.println("线程 "+threadName+" 退出,终止...");
    }

    public void start()
    {
        System.out.println("Starting "+threadName);
        // 避免NPE
        if(t == null)
        {
            t = new Thread(this, threadName);
            t.start();
            return;
        }
        this.start();
    }

    public static void main(String[] args)
    {
        RunnableDemo thread1 = new RunnableDemo("线程-1");
        thread1.start();

        RunnableDemo thread2 = new RunnableDemo("线程-2");
        thread2.start();
    }
}

实现Callable和Future接口

package cmo.basis.Demo;

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

/**
 * 描述:实现Callable接口
 *
 * 总结:
 * 1.声明实现Callable接口的类,实现call()方法,并定义反回值类型
 * 2.创建实现Callable类对象,使用FutureTask类包装Callable对象
 * 3.创建Thread对象,使用FutureTask对象作为Thread对象的target入参,并启动线程
 * 4.使用FutureTask对象的get()方法来获取线程结束后的反回值
 */
public class CallableDemo implements Callable<Integer>
{
    @Override
    public Integer call() throws Exception
    {
        int i = 0;
        for (; i < 5; i++)
        {
            System.out.println(Thread.currentThread().getName()+ " "+ i);
        }
        return i;
    }


    public static void main(String[] args)
    {
        CallableDemo demo = new CallableDemo();
        FutureTask<Integer> ft = new FutureTask<>(demo);
        for (int i = 0; i < 5; i++)
        {
            System.out.println(Thread.currentThread().getName()+ "的循环变量i的值:"+ i);
            if (i == 2)
            {
                new Thread(ft, "有返回值的线程").start();
            }
        }

        try
        {
            System.out.println("子线程的返回值:"+ ft.get());
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

线程生命周期

1.创建状态:

  • 线程被创建时(使用newThread创建线程对象),系统会分配资源并初始化该线程。这只是一个暂态,会一直保持到调用start()方法、线程进入运行或阻塞阶段之前

2.就绪状态:

  • 对一个创建状态的线程调用了start()方法后,线程进入就绪状态。就绪状态的线程会处于队列中等待JVM调度,直到线程获取资源。

3.运行状态:

  • 处于就绪状态的线程获取系统内存资源时,会执行run()方法,此时线程处于运行状态。处于运行状态的线程可以变成阻塞状态或死亡状态。

4.阻塞状态:

  • 如果一个线程执行了sleep()方法、suspend()方法或试图获取另一个已被其他线程占有的锁时,会暂时失去系统资源而进入阻塞状态。当设置的睡眠时间到期或获取系统资源后,线程可以重新进入就绪状态。

5.死亡:

  • 当线程执行完毕、发生异常或错误时,线程会终止并进入死亡阶段,这个阶段的线程是不可调度的,即不可再运行。
package cmo.basis.Demo;

/**
 * 描述:查看线程状态
 */
public class ThreadStateDemo implements Runnable
{
    public synchronized void notifying() throws InterruptedException
    {
        notify();
    }

    public synchronized void waiting() throws InterruptedException
    {
        wait();
    }

    @Override
    public void run()
    {
        try
        {
            System.out.println("当前线程休息100ms");
            Thread.sleep(100);
            waiting();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException 
    {
        ThreadStateDemo demo = new ThreadStateDemo();
        Thread thread = new Thread(demo);
        System.out.println("创建线程后状态为:"+ thread.getState());

        thread.start();
        System.out.println("启动线程后装填为:"+ thread.getState());

        Thread.sleep(50);
        System.out.println("主线程睡眠50ms后状态为:"+ thread.getState());

        Thread.sleep(100);
        System.out.println("wait后状态为:"+ thread.getState());

        demo.notifying();
        System.out.println("返回同步方法前状态为:"+ thread.getState());

        thread.join();
        System.out.println("结束线程后状态为:"+ thread.getState());
    }
}

线程优先级

JVM中的每一个线程都是存在优先级的,Java中的线程优先级是一个整数,取值为Thread.MIN_PRIORITY~Thread.MAX_PRIORITY,即1 ~ 10

package cmo.basis.Demo;

/**
 * 描述:线程优先级
 *
 * 总结:
 * JVM中的每一个线程都是存在优先级的,Java中的线程优先级是一个整数,取值为
 * Thread.MIN_PRIORITY~Thread.MAX_PRIORITY,即1~10
 */
public class ThreadPriority implements Runnable
{
    @Override
    public void run()
    {
        for (int i = 0; i < 5; i++)
        {
            System.out.println(Thread.currentThread().getName() + "输出:"+ i);
        }
    }

    public static void main(String[] args)
    {
        Thread maxPriority = new Thread(new ThreadPriority(), "高优先级线程");
        // 高优先级线程
        maxPriority.setPriority(Thread.MAX_PRIORITY);

        Thread minPriority = new Thread(new ThreadPriority(), "低优先级线程");
        // 低优先级线程
        minPriority.setPriority(Thread.MIN_PRIORITY);

        maxPriority.start();
        minPriority.start();
    }
}

线程加入操作

package cmo.basis.entity;

public class ThreadA extends Thread
{
    public ThreadA(String name)
    {
        super(name);
    }

    @Override
    public void run()
    {
        System.out.printf("%s线程开始\n", this.getName());
        for (int i = 0; i < 1000000; i++);
        System.out.printf("%s线程开始\n", this.getName());
    }
}

package cmo.basis.Demo;

import cmo.basis.entity.ThreadA;

/**
 * 描述:线程加入操作
 *
 * 总结:
 * 1.使用Thread类中的join()方法来加入线程
 * 2.如果需要在线程X执行前插入线程Y,这时可以使用线程X的join()方法加入线程Y,线程X会等待线程Y执行完成后再继续执行。
 */
public class JoinDemo
{
    public static void main(String[] args)
    {
        try
        {
            // 新建线程
            ThreadA t1 = new ThreadA("t-1");
            // 启动线程
            t1.start();
            // 将线程加入到主线程中,并且主线程会等待t1完成
            t1.join();

            System.out.printf("%s线程完成\n", Thread.currentThread().getState());
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

}

线程休眠操作

package cmo.basis.entity;

public class ThreadA extends Thread
{
    public ThreadA(String name)
    {
        super(name);
    }

    @Override
    public void run()
    {
        try {
            System.out.printf("%s线程开始\n", this.getName());
            for (int i = 0; i < 1000000; i++)
            {
                if (i == 10000)
                {
                    System.out.println("i循环到10000时线程休眠100毫秒");
                    this.sleep(100);
                }
            }
            System.out.printf("%s线程开始\n", this.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

package cmo.basis.Demo;

import cmo.basis.entity.ThreadA;

/** 
 * 描述:线程休眠操作
 * 
 * 总结:
 * sleep()方法设置的休眠时间到期后,线程并不会马上进入运行状态,而是进入就绪状态等待JVM调度。
 */
public class SleepDemo
{
    public static void main(String[] args)
    {
        try
        {
            // 新建线程
            ThreadA t1 = new ThreadA("t-1");
            // 启动线程
            t1.start();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

中断线程

package cmo.basis.entity;

public class ThreadA extends Thread
{
    private boolean isContiune = true;

    public ThreadA(String name)
    {
        super(name);
    }
    
    @Override
    public void run()
    {
        try {
            System.out.printf("%s线程开始\n", this.getName());
            for (int i = 0; i < 1000000; i++)
            {
                if (i == 10000)
                {
                    isContiune = false;
                    System.out.printf("%s中断线程\n", this.getName());
                    break;
                }
            }
            System.out.printf("%s线程开始\n", this.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

package cmo.basis.Demo;

import cmo.basis.entity.ThreadA;

/**
 * 描述:中断线程
 *
 * 总结:
 * Thread类中的stop()方法已被废弃,目前中断线程的方法是在run()方法中使用循环语句,
 * 通过条件语句进行判断,当满足条件跳出循环,使线程在执行完run()代码后自动中断
 */
public class StopDemo
{
    public static void main(String[] args)
    {
        try
        {
            // 新建线程
            ThreadA t1 = new ThreadA("t-1");
            // 启动线程
            t1.start();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

线程安全问题

1.在多线程程序运行时,会发生多个线程同时访问同一个对象或资源的情况下,第一、第二、第三线程对该对象进行修改,这就会导致该对象最终结果的不统一,引发线程安全问题

2.多个线程对count值进行修改,但是每次结果都不相同,造成了线程安全问题

package cmo.basis.Demo;

/**
 * 描述:线程安全问题
 *
 * 总结:
 * 1.在多线程程序运行时,会发生多个线程同时访问同一个对象或资源的情况下,
 * 第一、第二、第三线程对该对象进行修改,这就会导致该对象最终结果的不统一,引发线程安全问题
 * 2.多个线程对count值进行修改,但是每次结果都不相同,造成了线程安全问题
 */
public class ThreadSafe implements Runnable
{
    /**
     * 设置当前变量数量
     */
    private int count = 19;

    @Override
    public void run() 
    {
        for (int i = 0; i < 100; i++)
        {
            count++;
        }
        System.out.println(Thread.currentThread().getName()+ "线程当前count值为:"+ count);
    }

    public static void main(String[] args)
    {
        ThreadSafe threadSafe = new ThreadSafe();
        for (int i = 0; i < 5; i++)
        {
            new Thread(threadSafe).start();
        }
    }
}

线程同步机制

Java中提供了线程同步机制来解决线程安全问题,使用多个线程访问同一个资源时不发生冲突。Java中提供了 ”锁“ ,用来防止不同的线程在同一时间访问同一个对象或同一个代码块。

1. 同步代码块

Java中使用synchronized关键字来声明同步代码块,也就是所谓的 ”锁“ 它可以有效地防止多个线程同时访问同一个代码块而造成的冲突。

语法:

synchronized(Object)
{
	// 代码块
}
package cmo.basis.Demo;

/**
 * 描述:线程安全
 *
 * 总结:
 * 1.线程在进入synchronized代码块之前会先获取key对象的锁,直到key的锁被释放才会执行下一个线程,
 * 此时会避免key自增线程安全导致的key值重复的情况发生。
 * 2.在使用synchronized时,要尽量避免使用sleep()和yield()方法,因为被锁住的程序占用着对象锁,
 * 当程序休眠时,其他线程只能等待代码块被执行完后才能开始执行,这样会大大降低程序运行效率,同时锁一直占有着,
 * 系统内存这样也一直在无意地消耗
 */
public class ThreadSafe implements Runnable
{
    private Integer key = 0;

    @Override
    public void run()
    {
        synchronized (key)
        {
            key ++;
            System.out.println(Thread.currentThread().getName()+ ":" + key);
            try
            {
                Thread.sleep(100);
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args)
    {
        ThreadSafe threadSafe = new ThreadSafe();
        for (int i = 0; i < 10; i++)
        {
            new Thread(threadSafe, "线程-"+ i).start();
        }
    }
}

2. 同步方法

同步方法是指在方法前面使用synchronized 关键字修饰

语法:

synchronized void method()
{
	// 代码块
}
package cmo.basis.Demo;

/**
 * 描述:线程安全
 *
 * 总结:
 * 与synchronized同步代码块结果相同,不再描述
 */
public class ThreadSafe implements Runnable
{
    private Integer key = 0;

    public synchronized Integer getKey()
    {
        key ++;
        return key;
    }

    @Override
    public void run()
    {
        System.out.println(Thread.currentThread().getName()+ ":" + getKey());
        try
        {
            Thread.sleep(10);
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        ThreadSafe st = new ThreadSafe();
        for (int i = 0; i < 10; i++)
        {
            new Thread(st, "线程"+ i).start();
        }
    }
}

线程暂停与恢复

Java中Object类提供了wait()方法和notify()方法,wait()方法用来暂停线程,notify()方法则用来恢复线程。基本上所有类都拥有这两个方法。

package cmo.basis.Demo;

/**
 * 描述:线程暂停与恢复
 */
public class Demo
{
    public static void main(String[] args)
    {
        final Object obj = new Object();
        Thread t1 = new Thread()
        {
            @Override
            public void run()
            {
                synchronized (obj)
                {
                    System.out.println("线程1开始...");
                    try
                    {
                        System.out.println("线程1暂停...");
                        obj.wait();
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    System.out.println("线程1结束...");
                }
            }
        };

        Thread t2 = new Thread()
        {
            @Override
            public void run()
            {
                synchronized (obj)
                {
                    System.out.println("线程2开始...");
                    System.out.println("线程1恢复...");
                    obj.notify();
                    System.out.println("线程2结束...");
                }
            }
        };

        // 启动线程
        t1.start();
        t2.start();
    }
}

知识拓展

死锁

一个对象可以使用synchronized方法或其他形式的加锁机制,让任务进入阻塞状态,此时会出现一种情况:一个任务在等待另一个任务,后者又在等待别的任务,不断循环下去,直到这条链路上的任务又在等待第一个任务释放锁。这时所有线程任务都无法继续执行,全都在等待任务解锁中不断循环下去,令程序进入死循环,也就是死锁。

package cmo.basis.Demo;

/**
 * 描述:死锁
 */
public class ThreadTest
{
    public static Object Lock1 = new Object();
    public static Object Lock2 = new Object();

    private static class ThreadA extends Thread
    {
        @Override
        public void run()
        {
            synchronized (Lock1)
            {
                System.out.println("线程1:持有Lock1对象锁...");

                try { Thread.sleep(10); }
                catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("线程1:等待Lock2对象锁释放...");

                synchronized (Lock2)
                {
                    System.out.println("线程1:同时持有Lock1和Lock2的锁...");
                }
            }
        }
    }

    private static class ThreadB extends Thread
    {
        @Override
        public void run()
        {
            synchronized (Lock2)
            {
                System.out.println("线程2:持有Lock2对象锁...");

                try { Thread.sleep(10); }
                catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("线程2:等待Lock1对象锁释放...");

                synchronized (Lock1)
                {
                    System.out.println("线程2:同时持有Lock1和Lock2的锁...");
                }
            }
        }
    }

    public static void main(String[] args)
    {
        ThreadA T1 = new ThreadA();
        ThreadB T2 = new ThreadB();
        T1.start();
        T2.start();
    }
}

在执行时会发生死锁,程序将永远挂起,两个线程都不能继续执行,一直在等待互相释放锁,其运行结果为:

在这里插入图片描述

总结:

死锁并不是一定只出现在两个线程间,多个线程之间也会出现互相等待的情况而发生死锁。出现死锁的几种条件如下所示:

  • 1 .互斥条件。任务使用的资源中至少有一个是不能共享的。
  • 2 .至少有一个任务必须持有一个资源且正在等待获取一个当前被别的任务持有资源。
  • 3 .资源不能被任务抢占,任务必须把资源释放当作普通事件。
  • 4 .必须有循环等待。

当这些条件满足时,就会发生死锁,因此解决死锁的方法就是破坏这四个条件中的任意一个

package cmo.basis.Demo;

/**
 * 描述:死锁
 */
public class ThreadTest
{
    public static Object Lock1 = new Object();
    public static Object Lock2 = new Object();

    private static class ThreadA extends Thread
    {
        @Override
        public void run()
        {
            synchronized (Lock1)
            {
                System.out.println("线程1:持有Lock1对象锁...");

                try { Thread.sleep(10); }
                catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("线程1:等待Lock2对象锁释放...");

                synchronized (Lock2)
                {
                    System.out.println("线程1:同时持有Lock1和Lock2的锁...");
                }
            }
        }
    }

    private static class ThreadB extends Thread
    {
        @Override
        public void run()
        {
            synchronized (Lock1)
            {
                System.out.println("线程2:持有Lock2对象锁...");

                try { Thread.sleep(10); }
                catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("线程2:等待Lock1对象锁释放...");

                synchronized (Lock2)
                {
                    System.out.println("线程2:同时持有Lock1和Lock2的锁...");
                }
            }
        }
    }

    public static void main(String[] args)
    {
        ThreadA T1 = new ThreadA();
        ThreadB T2 = new ThreadB();
        T1.start();
        T2.start();
    }
}

在这里插入图片描述

先赞后看,养成习惯!!!^ _ ^ ❤️ ❤️ ❤️
码字不易,大家的支持就是我的坚持下去的动力。点赞后不要忘了关注我哦!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值