Java多线程?一篇就够了
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
一.认识线程及线程的创建
1.线程的概念
2.线程的特性
3.线程的创建方式
<1>继承Thread类
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("继承Thread类创建线程");
    }
}
 public static void main(String[] args) {
        //1.继承Thread类创建线程
        MyThread t=new MyThread();
        t.start();
        }
<2>实现Runnable接口
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("继承Runnable接口,创建描述任务对象,实现多线程");
    }
}
  public static void main(String[] args) {
     
        //2.实现Runnable接口
        Thread t1=new Thread(new MyRunnable());
        t1.start();
        }
 Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("使用Runnable接口,创建匿名内部类实现");
            }
        });
        t2.start();
<3>实现Callable接口
class MyCallable implements Callable<String> {
    //允许抛出异常,允许带有返回值,返回数据类型为接口上的泛型
    @Override
    public String call() throws Exception {
        System.out.println("实现了Callable接口");
        return "这不是一个线程类,而是一个任务类";
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
        //方法三:实现Callable接口,是一个任务类
        //FutureTask底层也实现了Runnable接口
        FutureTask<String> task=new FutureTask<>(new MyCallable());
        new Thread(task).start();
        System.out.println(task.get());
    }
二.线程的常用方法
1.构造方法和属性的获取方法
2.常用方法
<1>run()和start()
public class Thread_Run_VS_Start {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                }
            }
        }).run();
        /**
         * main线程直接调用Thread对象的run方法会直接在main线程
         * 运行Thread对象的run()方法---->传入的runnable对象.run()
         * 结果,main线程直接运行while(true)
         *
         * start()是启动一个线程,调用新线程的while(true)方法
         * 对比通过start()调用的结果区别
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                }
            }
        }).start();
    }
}
<2>interrupt()方法
public class Interrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                //...执行任务,执行时间可能比较长
               //运行到这里,在t的构造方法中不能引用t使用Thread.currentThread()方法,获取当前代码行所在线程的引用
                for (int i = 0; i <10000&&!Thread.currentThread().isInterrupted() ; i++) {
                    System.out.println(i);
                    //模拟中断线程
                    try {
                        Thread.sleep(1000);
                        //通过标志位自行实现,无法解决线程阻塞导致无法中断
                        //Thread,sleep(100000)
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();//线程启动,中断标志位=false
        System.out.println("t start");
        //模拟,t执行了5秒,进程没有结束,要中断,停止t线程
        Thread.sleep(5000);
        //未设置时,isInterrupt为false
        //如果t线程处于阻塞状态(休眠等),会抛出InterruptedException异常
        //并且会重置isInterrupt中断标志位位false
        t.interrupt();//告诉t线程,要中断(设置t线程的中断标志位为true),由t的代码自行决定是否要中断
        //isInterrupt设置为true
        //t.isInterrupted();  Interrupted是线程中的标志位
        System.out.println("t stop");
        //注:Thread.interrupted(); 返回当前线程的中断标志位,然后重置中断标志位
         
    }
}
<3>join方法
//join方法:实例方法:
// 1.无参:t.join:当前线程无条件等待,直到t线程运行完毕
//  2.有参:t.join(1000)等待1秒,或者t线程结束,哪个条件满足,当前线程继续往下执行
public class Join {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1");
            }
        });
        t.start();
        t.join();//当前线程main线程无条件等待,直到t线程执行完毕,当前线程再往后执行
       // t.join(1000);当前线程等到1秒,或者等t线程执行完毕
        System.out.println("ok");
    }
}
<4>获取当前线程的引用currentThread();方法
public class ThreadDemo { 
public static void main(String[] args) { 
Thread thread = Thread.currentThread(); 
System.out.println(thread.getName()); 
} 
}
<5>休眠当前线程sleep();方法
Thread.sleep(1000);
<6>线程让步yield();方法
public class Yield {
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            final int n=i;
            Thread t=new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(n);
                }
            });
            t.start();
        }
        //判断:如果活跃的线程数量大于1,main线程让步
        while (Thread.activeCount()>1){//记录活跃线程的数量
            Thread.yield();
        }//注意:要用debug方式,因为run方式,idea后台还会启动一个线程
        //实现ok在1到二十之后打印
        System.out.println("ok");
    }
}
三.线程的生命周期和状态转换
四.线程间的通信
public class SequencePrintHomeWork {
    //有三个线程,每个线程只能打印A,B或C
    //要求:同时执行三个线程,按ABC顺序打印,依次打印十次
    //ABC换行 ABC换行。。。。
    //考察知识点:代码设计,多线程通信
    public static void main(String[] args) {
        Thread a = new Thread(new Task("A"));
        Thread b = new Thread(new Task("B"));
        Thread c = new Thread(new Task("C"));
        c.start();
        b.start();
        a.start();
    }
    private static class Task implements Runnable{
        private String content;
        //顺序打印的内容:可以循环打印
        private static String[] ARR = {"A", "B", "C"};
        private static int INDEX;//从数组哪个索引打印
        public Task(String content) {
            this.content = content;
        }
        @Override
        public void run() {
            try {
                for(int i=0; i<10; i++){
                    synchronized (ARR){//三个线程使用同一把锁
                        //从数组索引位置打印,如果当前线程要打印的内容不一致,释放对象锁等待
                        while(!content.equals(ARR[INDEX])){
                            ARR.wait();
                        }
                        //如果数组要打印的内容和当前线程要打印的一致,
                        // 就打印,并把数组索引切换到一个位置,通知其他线程
                        System.out.print(content);
                        if(INDEX==ARR.length-1){
                            System.out.println();
                        }
                        INDEX = (INDEX+1)%ARR.length;
                        ARR.notifyAll();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
五.多线程的安全及解决
1.原子性
2.可见性
3.代码的顺序性
4.线程不安全问题的解决
<1>synchronized 关键字
<2>volatile 关键字
public class Test {
    private static boolean flag = true;
    public static void main(String[] args) {
        //创建一个线程并启动
        new Thread(new Runnable() {
            int i=0;
            @Override
            public void run() {
                while(flag){
                    //这个语句底层使用了synchronized,保证了可见性
                    //System.out.println("=============");
                    i++;
                }
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //即使改了,上面的线程flag也不会改,会一直循环
        flag = false;
    }
}
六.锁体系
1.Synchronized加锁方式
<1>Synchronized的加锁方式及语法基础
public class SafeThread {
    //有一个遍历COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环把COUNT++
    //等待二十个子线程执行完毕之后,再main中打印COUNT的值
    //(预期)count=20000
    private static int COUNT=0;
    //对当前类对象进行加锁,线程间同步互斥
//    public synchronized static void increment(){
//        COUNT++;
//    }
    //使用不同的对象加锁,没有同步互斥的效果,并发并行
//    public static void increment(){
//        synchronized (new SafeThread()){
//            COUNT++;
//        }
//    }
    public static void main(String[] args) throws InterruptedException {
        //尽量同时启动,不让new线程操作影响
        Class clazz=SafeThread.class;
      Thread[]threads=new Thread[20];
        for (int i = 0; i <20 ; i++) {
            threads[i]=new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j <1000 ; j++) {
                        //给SafeThread对象加一把锁
                        synchronized (clazz){
                            COUNT++;
                        }
                    }
                }
            });
        }
        for (int i = 0; i <20 ; i++) {
            threads[i].start();
        }
        //让main线程等待20个子线程运行完毕
        for (int i = 0; i <20 ; i++) {
            threads[i].join();
        }
        System.out.println(COUNT);
    }
}
<2>Synchronized的原理及实现
public class Test1 {
    public Test1() {
    }
    public static void main(String[] args) {
        Class var1 = Test1.class;
        synchronized(Test1.class) {
            System.out.println("hello");
        }
    }
}
<3>JVM对Synchronized的优化
(1).对锁的优化
(2).锁粗话
public class Test {
    private static StringBuffer sb;
    public static void main(String[] args) {
        sb.append("1").append("2").append("3");
    }
}
(3).锁消除
public class Test {
    public static void main(String[] args) {
        StringBuffer sb=new StringBuffer();
        sb.append("1");
        sb.append("2");
        sb.append("3");
    }
}
2.常见的锁策略及CAS
<1>.乐观锁和悲观锁
<2>自旋锁
while(抢锁(lock)==失败{}
<3>可重入锁
public class Test2 {
    public static synchronized void t1(){
        t2();
    }
    public static synchronized void t2(){
    }
    public static void main(String[] args) {
        t1();
    }
}
3.Lock体系
<1>Lock接口
(1)使用Lock锁实现线程同步
public class AccountRunnable implements  Runnable {
    private Account account = new Account();
    //买一把锁
    Lock lock = new ReentrantLock(); //Re-entrant-Lock  可重入锁
    @Override
    public void run() {
        //此处省略300句
        try{
//上锁
            lock.lock();
            //判断余额是否足够,够,取之;不够,不取之;
            if(account.getBalance()>=400){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                method1();
                //取之
                account.withDraw(400);
                //输出信息
                System.out.println(Thread.currentThread().getName()+
                   "取款成功,现在的余额是"+account.getBalance());
            }else{
                 System.out.println("余额不足,"+Thread.currentThread().getName()
                 +"取款失败,现在的余额是"   +account.getBalance());
            }
        }finally {
            //解锁
            lock.unlock();
        }
        //此处省略100句
    }
}
(2)Lock加锁的四种方式
<2>AQS简单认识
<3>ReentrantLock
(1)ReentrantLock基本概念
(2)自己实现一个简单的ReentrantLock
public class Test2 {
    volatile int status=0;
    Queue parkQueue;//集合 数组  list
    void lock(){
        while(!compareAndSet(0,1)){
            //这里不能用sleep或yield实现
            //sleep无法确定睡眠的时间
            //yield只能用于两个线程竞争,当有多个线程之后,t1抢不到锁,yield会让出cpu,但是可能下一次cpu还是调t1
            park();
        }
        unlock();
    }
    void unlock(){
        lock_notify();
    }
    void park(){
        //将当期线程加入到等待队列
        parkQueue.add(currentThread);
        //将当期线程释放cpu  阻塞   睡眠
        releaseCpu();
    }
    void lock_notify(){
        //status=0
        //得到要唤醒的线程头部线程
        Thread t=parkQueue.header();
        //唤醒等待线程
        unpark(t);
    }
}
(3)ReentrantLock部分源码分析
//非公平锁
 public ReentrantLock() {
        sync = new NonfairSync();
    }
//公平锁
 public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
final void lock() {
    if (compareAndSetState(0, 1))//首先用一个CAS操作,判断state是否是0(表示当前锁未被占用)
        setExclusiveOwnerThread(Thread.currentThread());//设置当前占有锁的线程为该线程
    else
        acquire(1);
}
public final void acquire(int arg) {
    //首先看看自己要不要排队,如果不用排队,获取锁,要排队,加入AQS队列 
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
tryAcquire(arg)
final boolean nonfairTryAcquire(int acquires) {
    //获取当前线程
    final Thread current = Thread.currentThread();
    //获取state变量值
    int c = getState();
    if (c == 0) { //没有线程占用锁
        if (compareAndSetState(0, acquires)) {
            //占用锁成功,设置独占线程为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) { //当前线程已经占用该锁 重入锁
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 更新state值为新的重入次数
        setState(nextc);
        return true;
    }
    //获取锁失败
    return false;
}
<4>ReadWriteLock锁
public   interface   ReadWriteLock { 
      Lock readLock();   
      Lock writeLock(); 
} 
public class TestLock {
    public static void main(String[] args) {
//默认也是非公平锁  也是可重入锁
        ReadWriteLock rwl = new ReentrantReadWriteLock();
        //多次返回的都是同一把读锁 同一把写锁
        Lock readLock = rwl.readLock();
        Lock readLock2 = rwl.readLock();
        Lock writeLock = rwl.writeLock();
        readLock.lock();
        readLock.unlock();
        System.out.println(readLock==readLock2);
    }
}
4.Lock锁和同步锁(synchronized)的区别
5.死锁
package threadadvanced.lesson1;
class Pen {
 private String pen = "笔" ; 
 public String getPen() {
  return pen;
 }
}
class Book {
 private String book = "本" ; 
 public String getBook() {
  return book;
 }
}
public class DeadLock {
 private static Pen pen = new Pen() ; 
 private static Book book = new Book() ; 
 public static void main(String[] args) {
  new DeadLock().deadLock();
 }
 public void deadLock() {
  Thread thread1 = new Thread(new Runnable() { // 笔线程
   @Override
   public void run() {
    synchronized (pen) {
     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
     System.out.println(Thread.currentThread()+" :我有笔,我就不给你");
     synchronized (book) {
      System.out.println(Thread.currentThread()+" :把你的本给我!");
     }
    }
   }
  },"Pen") ; 
  
  Thread thread2 = new Thread(new Runnable() { // 本子线程
   @Override
   public void run() {
    synchronized (book) {
     System.out.println(Thread.currentThread()+" :我有本子,我就不给你!");
     synchronized (pen) {
      System.out.println(Thread.currentThread()+" :把你的笔给我!");
     }
    }
    
   }
  },"Book") ; 
  thread1.start();
  thread2.start();
 }
}
七.多线程案例
1.生产者消费者问题
/**
* 面包店
* 10个生产者,每个每次生产3个
* 20个消费者,每个每次消费一个
*
* 进阶版需求
* 面包师傅每个最多生产30次,面包店每天生产10*30*3=900个面包
* 消费者也不是一直消费。把900个面包消费完结束
*
* 隐藏信息:面包店每天生产面包的最大数量为900个
* 消费者把900个面包消费完结束
*/
public class AdvancedBreadShop {
//面包店库存数
private static int COUNT;
//面包店生产面包的总数,不会消费的
private static int PRODUCE_NUMBER;
public static class Consumer implements Runnable{
private String name;
public Consumer(String name) {
this.name = name;
}
@Override
public void run() {
try {
while (true){
synchronized (AdvancedBreadShop.class){
if(PRODUCE_NUMBER==900&&COUNT==0){
System.out.println("今天面包已经卖完了");
break;
}else {
if(COUNT==0){
AdvancedBreadShop.class.wait();
}else {
System.out.printf("%s消费了一个面包\n",this.name);
COUNT--;
AdvancedBreadShop.class.notifyAll();
Thread.sleep(100);
}
}
}
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Producer implements Runnable{
private String name;
public Producer(String name) {
this.name = name;
}
@Override
public void run() {
try {
//生产者生产30次,结束循环
for(int i=0;i<=30;i++) {
synchronized (AdvancedBreadShop.class){
if(i==30){
System.out.println("今天面包生产完了");
break;
}else {
if(COUNT>97){
AdvancedBreadShop.class.wait();
}else {
COUNT=COUNT+3;
PRODUCE_NUMBER=PRODUCE_NUMBER+3;
System.out.printf("%s生产了三个面包\n",this.name);
AdvancedBreadShop.class.notifyAll();
Thread.sleep(100);
}
}
}
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread[] Consumers=new Thread[20];
Thread[] Producers=new Thread[10];
for (int i = 0; i <20 ; i++) {
Consumers[i]=new Thread(new Consumer(String.valueOf(i)));
}
for (int i = 0; i <10 ; i++) {
Producers[i]=new Thread(new Producer(String.valueOf(i)));
}
for (int i = 0; i <20 ; i++) {
Consumers[i].start();
}
for (int i = 0; i <10 ; i++) {
Producers[i].start();
}
}
}2.单例模式
public class Singleton {
    //volatile关键字修饰,保证的可见性和代码的顺序性
    private static volatile Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        //判断instance是否为空,竞争锁的条件
        if (instance == null) {
            //保证线程安全,为Singleton.class加锁
            synchronized (Singleton.class) {
                //再次判断instance是否为空,防止多个线程进入第一个if后
                //对synchronized锁竞争失败进入阻塞状态后,再次进入运行态时
                //new了多个Singleton,不符合单例模式
                //保证线程安全
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
        }
        }
3.阻塞式队列
/**
 * 实现阻塞队列
 * 1.线程安全问题:在多线程情况下,put,take不具有原子性,4个属性,不具有可见性
 * 2.put操作:如果存满了,需要阻塞等待。take操作:如果是空,阻塞等待
 * @param <T>
 */
public class MyBlockingQueue <T>{
    //使用数组实现循环队列
    private Object[] queue;
    //存放元素的索引
    private int putIndex ;
    //取元素的索引
    private int takeIndex;
    //当前存放元素的数量
    private int size;
    public MyBlockingQueue(int len){
        queue=new Object[len];
    }
    //存放元素,需要考虑:
    //1.putIndex超过数组长度
    //2.size达到数组最大长度
    public synchronized void put(T e) throws InterruptedException {
        //不满足执行条件时,一直阻塞等待
        //当阻塞等待都被唤醒并再次竞争成功对象锁,回复往下执行时,条件可能被其他线程修改
        while (size==queue.length){
            this.wait();
        }
        //存放到数组中放元素的索引位置
        queue[putIndex]=e;
        putIndex=(putIndex+1)%queue.length;
        size++;
        notifyAll();
    }
    //取元素
    public synchronized T take() throws InterruptedException {
       while (size==0){
            this.wait();
        }
        T t= (T) queue[takeIndex];
        queue[takeIndex]=null;
        takeIndex=(takeIndex+1)%queue.length;
        size--;
        notifyAll();
        return t;
    }
    public int size(){
        return size;
    }
    public static void main(String[] args) {
        MyBlockingQueue<Integer>queue=new MyBlockingQueue<>(10);
        //多线程的调试方式:1.写打印语句 2.jconsole
        for (int i = 0; i <3 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int j = 0; j <100 ; j++) {
                            queue.put(j);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        for (int i = 0; i <3 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                       while (true){
                          int t= queue.take();
                           System.out.println(Thread.currentThread().getName()+":"+t);
                       }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}
4.线程池
import java.util.concurrent.*;
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        //以快递公司,快递员,快递业务为模型
        ThreadPoolExecutor pool=new ThreadPoolExecutor(
                5,//核心线程数---->正式员工数
                10,//最大线程数-->正式员工+临时员工
                60,//临时工的最大等待时间
                TimeUnit.SECONDS,//idle线程的空闲时间-->临时工最大的存活时间,超过就解雇
                new LinkedBlockingQueue<>(),//阻塞队列,任务存放的地方--->快递仓库
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(new Runnable() {
                            @Override
                            public void run() {
                                //r对象是线程池内部封装过的工作任务类(Worker),会一直循环等待的方式从阻塞队列中拿取任务并执行
                                //所以不能调用r.run();方法
                                System.out.println(Thread.currentThread().getName()+"开始执行了");
                            }
                        });
                    }
                },//创建线程的工厂类  线程池创建线程时,调用该工厂类的方法创建线程(满足该工厂创建线程的要求)
                   //---->对应招聘员工的标准
                /**
                 * 拒绝策略:达到最大线程数且阻塞队列已满,采取拒绝策略
                 * AbortPolicy:直接抛出RejectedExecutionException(不提供handler时的默认策略)
                 * CallerRunsPolicy:谁(某个线程)交给我(线程池)的任务,我拒绝执行,由谁自己去执行
                 * DiscardPolicy:交给我的任务直接丢弃掉
                 * DiscardOldestPolicy:阻塞队列中最旧的任务丢弃
                 */
                new ThreadPoolExecutor.AbortPolicy()//拒绝策略-->达到最大线程数,且阻塞队列已满,采取的拒绝策略
        );//线程池创建以后,只要有任务们就会自动执行
        for (int i = 0; i <20 ; i++) {
            //线程池执行任务:execute方法,submit方法--->提交执行一个任务
            //区别:返回值不同
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        //线程池有4个快捷的创建方式(实际工作不使用,作为面试了解)
        //实际工作需要使用ThreadPoolExecutor,构造参数是我们自己指定,比较灵活
        ExecutorService pool2=Executors.newSingleThreadExecutor();//创建单线程池
        ExecutorService pool3=Executors.newCachedThreadPool();//缓存的线程池
        ExecutorService pool5=Executors.newFixedThreadPool(4);//固定大小线程池
        ScheduledExecutorService pool4=Executors.newScheduledThreadPool(4);//计划任务线程池
        //两秒中之后执行这个任务
        pool4.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        }, 2, TimeUnit.SECONDS);
        //一直执行任务
        pool4.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        }, 2, 1,TimeUnit.SECONDS);//比如一个脑子,两秒后开始叫我,然后每隔一秒叫我一次
    }
}
————————————————
版权声明:本文为CSDN博主「Serendipity  sn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45704528/article/details/117353110
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:
https://blog.csdn.net/qq_45704528/article/details/117353110


评论























