进程、线程、多线程(初学)

进程和线程的区别:

进程:它是系统分配资源的最小单位。
线程:它时系统调度的最小单位。

1.主线程:主要执行业务的线程
2.子线程:在主线程中创建线程就叫子线程

1.进程是系统分配资源的最小单位,线程是系统调度的最小单位。
2.一个进程中可以包含多个线程。
3.进程的执行单位就是线程。
4.进程间不可以像线程间资源共享。
5.一个进程至少包含一个线程,且至少有一个主线程。

线程的三种休眠方式:

import java.util.concurrent.TimeUnit;

public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        String content = "留声机在光影里飘来悠悠的乐声," +
                "夏日的午后,靠在藤椅上,让摇椅随乐声晃动,绿色的虎皮蕉在脚边肆意蔓延微醺着,半梦半醒。";
        for (char item : content.toCharArray()) {
            System.out.print(item);
//            // 方式1:线程休眠
//            Thread.sleep(60 * 1000);
//
//            // 方式2:线程休眠
//            TimeUnit.SECONDS.sleep(1); // 休眠 1 秒
//            TimeUnit.HOURS.sleep(1); // 休眠 1 小时

            // 方式3:线程休眠
            Thread.sleep(TimeUnit.SECONDS.toMillis(1));
        }
    }
}

线程的6种创建:

public class ThreadDemo3 {
    //线程创建方式一
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println(Thread.currentThread().getName());
    }
}
public class ThreadDemo4 {
    public static void main(String[] args) {
        //线程创建方式二
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        thread.start();
    }
}
public class ThreadDemo5 {
    //线程创建方式三
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}
public class ThreadDemo6 {
    //线程创建方式四
    public static void main(String[] args) {
        //创建匿名Runnable类
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        thread.start();
    }
}
public class ThreadDemo7 {
    public static void main(String[] args) {//线程创建方式五
        //lambda + runnable  只有jdk1.8之后才能支持
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
        });
        thread.start();
    }
}
public class ThreadDemo8 {
    //线程创建方式六
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable callable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(callable);
        Thread thread = new Thread(task);
        thread.start();
        int num = task.get();
        System.out.println(num);
    }
    static class MyCallable implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            int num = new Random().nextInt(10);
            System.out.println("线程随机数:"+num);
            return num;
        }
    }
}

线程是不是创建的越多越好?

不是 对于计算密集型任务来说:线程数=cpu核数最好 对于读写文件操作来说:理论上线程越多越好

线程分类:

1.用户线程:(默认创建的线程就是用户线程)。
2.守护线程:为用户线程服务。(守护线程的经典使用场景:垃圾回收器)。

守护线程需要注意的事项?
1.守护线程的设置必须在开始(start())之前。
2.在守护线程中默认创建的线程就是守护线程

Thread thread = new Thread(() -> {
            // 新创建了一个线程
            Thread t1 = new Thread(() -> {
            }, "t1");
            System.out.println("t1 守护线程:" + t1.isDaemon());

            System.out.println("线程名:" +
                    Thread.currentThread().getName());
        });
        System.out.println("thread 守护线程:" + thread.isDaemon());
        // 设置守护线程
        thread.setDaemon(true);
        thread.start();

        System.out.println("thread 守护线程:" + thread.isDaemon());

在这里插入图片描述

run()vs start()?

1.run方法是一个对象的普通方法,它的使的是主线程来执行任务的。
2.start是线程开始的方法,它使用新的线程来执行任务
3.run方法可以被调用多次,而start方法只能被执行一次

线程的二种终止方式:

1.自定义全局表示来中断线程:
public class ThreadDemo17 {
    // 全局变量
    private static boolean flag = false;

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

        Thread t1 = new Thread(() -> {
            while (!flag) {
                System.out.println("我正在转账...");
//                try {
//                    // 休眠线程
//                    Thread.sleep(100);
//                    System.out.println("我正在转账...");
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                    break;
//                }
            }
            System.out.println("啊?差点误了大事。");
        }, "张三");
        // 开启任务
        t1.start();

        // 休眠主线程一段时间
        Thread.sleep(310);

        // 终止线程
        System.out.println("停止交易,有内鬼.");
        flag = true;
    }
}
2.使用Thread的interrupt()去终止:
public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (!Thread.interrupted()) {
                try {
                    // 休眠线程
                    Thread.sleep(100);
                    System.out.println("我正在转账...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
            System.out.println("啊?差点误了大事。");
        }, "张三");
        // 开启任务
        t1.start();

        // 休眠主线程一段时间
        Thread.sleep(310);

        // 终止线程
        System.out.println("停止交易,有内鬼.");
        t1.interrupt();

    }
}

使用系统的 Intrruput()可以及时的终止线程,而使用自定义全局变量终止线程的方式,不能里面终止。
判断线程是否终止的方式:
1.Thread.interrupted():第一次接收到的终止状态方式是true,之后状态会复位为false

public class ThreadDemo20 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.interrupted());
//                System.out.println(Thread.currentThread().isInterrupted());
            }
        });
        thread.start();
        // 终止线程
        thread.interrupt();
    }
}

在这里插入图片描述

2.Thread.currentThread ( ).isInterrupted ():只用来得到线程的状态,不会进行复位。

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

        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
//                System.out.println(Thread.interrupted());
                System.out.println(Thread.currentThread().isInterrupted());
            }
        });
        thread.start();
        // 终止线程
        thread.interrupt();
    }
}

在这里插入图片描述

等待一个线程:

public class ThreadDemo21 {
    public static void main(String[] args) throws InterruptedException {

        // 定义统一的任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName + "上班");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(threadName + "下班");
            }
        };

        Thread t1 = new Thread(runnable, "张三");
        t1.start();

        // 等待线程 t1 执行完成
        t1.join(1200);

        Thread t2 = new Thread(runnable, "李四");
        t2.start();

    }
}

在这里插入图片描述

线程的状态:

在这里插入图片描述
在这里插入图片描述

public class ThreadDemo23 {
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        System.out.println("执行之前:" + t1.getState());
        // 开启线程
        t1.start();
        System.out.println("start 之后:" + t1.getState());
        Thread.sleep(100);
        System.out.println("休眠了100毫秒之后的状态:" + t1.getState());
        // 等待执行完成
        t1.join();
        System.out.println("线程最终状态:" + t1.getState());
    }
}

在这里插入图片描述

线程 yield()方法

用来让出cpu的执行权的。yield分配执行权不一定成功,要看cpu的最终选择,但总体来说还是基本符合预期。

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

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                // 出让 cpu 的执行权
                Thread.yield();
                System.out.println("我是线程1");
            }
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("我是线程2");
            }
        });
        t2.start();


    }
}

在这里插入图片描述

多线程带来的问题

非线程安全:使用多线程执行任务,最终得到的结果和预期不一致。

class Counter {
    // 私有变量 count
    private int count = 0;
    // 执行循环次数
    private final int maxSize = 10000000;

    // 执行加法
    public void incrment() {
        for (int i = 0; i < maxSize; i++) {
            count++;
        }
    }

    // 执行减法
    public void decrment() {
        for (int i = 0; i < maxSize; i++) {
            count--;
        }
    }

    // 提供变量 count 的查询
    public int getCount() {
        return count;
    }
}
public class ThreadDemo26 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            counter.incrment();
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            counter.decrment();
        });
        t2.start();

        // 等待执行完成
        t1.join();
        t2.join();

        System.out.println("最终结果:" + counter.getCount());
    }
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/2021043015150079.png
在这里插入图片描述

线程不安全的因素:

1.cpu是抢占式执行的(万恶之源
2.通过操作的同一个变量(具体见上边图)
3.可见性
在这里插入图片描述

4.非原子性
(我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入
房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性
的)
5.编译器优化(在多线程时,就会发生线路紊乱,也叫做指令重排)
在这里插入图片描述

线程不安全解决方案分析:

1.cpu抢占式执行(不可控)
2.每个线程操作自己的私有变量(有可能可以,但代价太大)
3.只要在关键步骤上排队执行就行了(加锁

Volatile关键字(轻量级解决安全问题)

作用:解决内存不可见和指令重排的问题。但不能解决原子性问题

操作锁的流程:

尝试获取锁----->使用锁(具体业务)------>释放锁

java中加锁操作有两种

1.synchronized(jvm层的解决方案)
2.手动 lock 的方式

synchronized

synchronized的底层是使用操作系统的mutex lock(互斥锁)实现的。
在JVM层面是监视器锁
在java层面是将锁的信息存储在对象的头信息的偏向锁ID字段中
当线程释放锁时,JMM(java内存模型)会把该线程对应的工作内存中的共享变量刷新到主内存中
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码 必须从主内存中读取共享变量
synchronized用的锁是存在Java对象头里的
synchronizednei实现了获取锁以及释放锁的过程

synchronized如何实现线程安全

在这里插入图片描述

synchronized的3种使用场景

1.使用synchronized修饰代码块(可以给任意对象加锁)

public class ThreadDemo31 {
    // 全局变量
    private static int number = 0;
    // 循环的最大次数
    private static final int maxSize = 100000;

    public static void main(String[] args) throws InterruptedException {
        // 声明锁对象
        Object lock = new Object();

        Object lock2 = new Object();

        // ++
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    // 实现加锁
                    synchronized (ThreadDemo31.class) {
                        // 代码1
                        number++;
                    }
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    synchronized (ThreadDemo31.class) {
                        number--;
                    }
                }
            }
        });
        t2.start();

        // 等待两个线程执行完成
        t1.join();
        t2.join();

        System.out.println("最终执行结果:" + number);
    }
}

2.使用synchronized修饰静态方法(对当前的类进行加锁)

public class ThreadDemo32 {
    // 全局变量
    private static int number = 0;
    // 循环次数
    private static final int maxSize = 100000;

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

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                increment();
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                decrement();
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最终执行结果:" + number);

    }

    // 相加
    public synchronized static void increment() {
        for (int i = 0; i < maxSize; i++) {
            number++;
        }
    }

    // 相减
    public synchronized static void decrement() {
        for (int i = 0; i < maxSize; i++) {
            number--;
        }
    }
}

3.使用synchronized来修饰普通方法(对当前的类实例进行加锁)

public class ThreadDemo33 {
    // 全局变量
    private static int number = 0;
    // 循环次数
    private static final int maxSize = 100000;

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

        ThreadDemo33 threadDemo33 = new ThreadDemo33();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo33.increment();
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo33.decrement();
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最终执行结果:" + number);
    }

    // 相加
    public synchronized void increment() {
        for (int i = 0; i < maxSize; i++) {
            number++;
        }
    }

    // 相减
    public synchronized void decrement() {
        for (int i = 0; i < maxSize; i++) {
            number--;
        }
    }
}

注意:同一业务的多线程执行,一定要使用同一把锁;

锁升级的过程

重量级的锁(1.6之前):用户态 ->内核态(有特别大的性能消耗
synchronized 锁升级的过程(jdk 1.6)
在这里插入图片描述

lock手动锁

模板:
在这里插入图片描述
注意事项:释放锁一定要放在finally里面,lock()一定要放在try()外边。

如果放在 try里面可能会造成两个问题:
1.如果try里而抛出异常了,还没有加锁成功就执行finally里面的释放锁的操作了。因为还没有得到锁就释放锁。
2.如果放在try里面,如果没有锁的情况下试图释放锁,这个时候产生的异常就会将业务代码(也就是try里面的异常)给吞噬掉(覆盖掉),增加了代码调试的难度。

公平锁和非公平锁
非公平锁:性能更高。
1.当一个线程释放锁之后
2.另一个线程刚好执行到获取锁的代码就可以直接获取锁
公平锁:
1,一个线程释放锁
2。唤醒“需要得到锁” 的队列来得到锁
在Java语言中所有锁的默认实现方式都是非公平锁
synchronized是非公平锁
ReentrantLock默认是非公平锁,但可以现实的声明为公平锁
在这里插入图片描述

synchronized和lock的区别
区别方面synchronizedlock
加锁 /解锁自动手动
层面实现jvm层面锁的实现java层面锁的实现
安全锁/非安全锁非公平锁默认是非公平锁,但可以现实的声明为公平锁
适用范围修饰代码块,修饰静态、普通方法修饰代码块
灵活性

死锁
定义:在两个或两个以上线程的线程运行中,因为资源抢占而造成线程一直等待的问题。
在这里插入图片描述

造成死锁的四个条件:

1.互斥条件:当资源被一个线程拥有,就不能 被其他线程拥有了。
2.请求拥有条件:当一个线程拥有一个资源之后又试图请求其他资源。
3.不可剥夺条件:当一个资源被一个线程拥有后,如果不是这个线程主动释放资源,其他线程不可拥有此资源
4.环路等待条件:两个或两个以上的线程拥有资源后,试图获取对方资源而形成的一个环路

如何解决死锁的问题?

解决造成死锁的四个条件中的请求拥有条件环路等待条件
最有效的解决方案是控制加锁的顺序,解决环路等待条件;

wait()方法

让线程进入休眠并释放资源。
不传参数或者参数为0时会一直等待被唤醒,此时状态是waiting
传入 大于0 的参数时,被指定时间内如果没被唤醒,将自己唤醒,休眠时状态为timed_waiting;

notify()

唤醒单个线程,不能指定唤醒某个线程。

notiftAll():

唤醒所有线程。
解决不能指定唤醒某个线程的问题:LockSupport park/unpark;

wait()注意事项:

使用前必须加锁,也就是说wait()一定要配合Synchronized使用;
wait()和notify()在配合synchronized使用时一定要使用同一把锁 wait()和notify()配合使用时,一定是同一把锁

wait()和 sleep()的区别
区别sleep()wait()
使线程 休眠
能否接收线程终止通知
配合synchronized使用不需要需要
休眠时是否释放锁
是否可以主动唤醒线程不能
方法所属类ThreadObject
sleep(0)和 wait(0)有什么区别?

sleep(0)表示0庙后继续执行线程,wait(0)表示一直休眠
sleep(0)表示重新出发一次cpu的竞争。

为什么wait会释放锁?而sleep不会释放锁?

答: sleep必须要传递一个最大等待时间的,也说sleep是可控的(对于时间层面来讲),而 wait 是可以不传递传输,从设计层面来讲如果让 wait这个没有超时等待时间的机制不释放锁的话,那么线程可能会一直阻塞,而sleep 就不存在这个问题。

为什么wait是Object 的方法,而sleep是 Thread 的方法?

答: wait需要操作锁,而锁是属于对象级别(所有的锁都是放在对象头当中),它不是线程级别,一个线程中可以有多把锁,为了灵活起见,所以就将 wait放在0bject当中。

线程池

定义:利用池化技术去更好的管理线程和使用线程的方式。

线程池的两个重要对象:

1.线程
2.工作队列

为什么要用线程池?

线程的缺点:线程的创建会为线程开辟,本地方法栈,虚拟机栈,程序计数器的私有内存,线程的结束会消耗以上三个区域,频繁的创建和销毁会消耗系统的资源,并且线程不会友好的去拒绝完成不了的任务。

线程池的优点:

1.不用频繁的创建和销毁线程;
2.可以更好地控制线程个数和资源个数;
3.拥有更多的功能,比如去进行定时任务的执行;
4.可以对完成不了的线程进行友好的拒绝;

线程池的7种创建方式

1.创建固定线程个数的线程数
2.创建带有缓存的线程池
3.创建可以执行定时任务的线程池
4.创建单个线程执行定时任务的线程池
5.创建单个线程的线程池
6.根据当前cpu核数创建相应的线程数的线程池
7.原始的创建线程池的方式:ThreadPoolExecutor(如下代码)

public class ThreadPoolDemo55 {
    static int count = 1;
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("myThreadPool-"+count++);
                return  thread;
            }
        };
        //原始的创建线程池的方式
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(2,2,60,
                        TimeUnit.SECONDS,new LinkedBlockingQueue<>(1),threadFactory);
        for (int i = 0; i < 3; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:"+Thread.currentThread().getName());
                }
            });
        }
    }
}

关于ThreadPoolExecutor():

第一个参数:线程池的核心线程数;
第二个参数:线程池的最大线程数;
第三个参数:最大线程数存活时间;
第四个参数:最大存活时间的时间单位;
第五个参数:设置工作队列的大小;
第六个参数:线程工厂
第七个参数:进行拒绝策略的设置

ThreadPoolExecutor()的执行流程:

在这里插入图片描述

ThreadPoolExecutor()的五种拒绝策略:

1.ThreadPoolExecutor.AbortPolicy(默认拒绝策略,不执行任务时抛出异常)
2.ThreadPoolExecutor.callerRunsPolicy(交给主线程去执行任务)
3.ThreadPoolExecutor Discard0ldestPolicy(丢弃最老的任务)
4.ThreadPoolExecutor IDiscardPolicy (丢弃最新的任务)
5.自定义拒绝策略(可以写到日志里,存储到数据库,也可以什么都不干)

线程池的终止:

1.shutDown():等待任务执行完关闭;
2.shutDownNow():立即关闭,任务不会执行完;

线程池的状态:

在这里插入图片描述

单例模式:

全局唯一并且所有程序都可以使用的对象,就是单例模式。
下面两种方式进行实现。

1.饿汉模式:

public class Singleton {
    private Singleton() {

    }
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return  instance;
    }
}

2.懒汉模式(最终版):

public class Singleton {
    private Singleton() {

    }
    private static volatile Singleton instance = null;
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized (Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return  instance;
    }
}

为什么懒汉模式要加上volatile

cpu实例化对象分为三个步骤:
1.先分配内存空间;
2.初始化;
3.将instance对象指向内存;
加上volatile就防止了指令重排,防止线程拿到一个未初始化过的对象进行返回,而造成线程不安全;

ThreadLocal

ThreadLocal的经典使用场景

1.解决线程不安全的问题;
2.线程级的数据传递;

选择ThreadLocal还是锁?

看创建实例对象之后的复用率,复用率高就用ThreadLocal;

ThreadLocal的初始化方法在什么时候才会执行?

当ThreadLocal中出现set方法之后,初始化方法将不会在执行;
当ThreadLocal在使用get方法时,才会判断并调用初始化方法;

ThreadLocal的缺点?

1.不可继承:ThreadLocal是不可继承的,这是使用它的一个子类InheritableThreadLocal可以解决;
2.会出现脏读;
3.(重要):内存泄露问题:
泄露原因:

ThreadLocal的存储是在Thread下的一个ThreadLocalMap中以键值对的形式进行存储:key被定义成弱引用,value被定义为强引用,在垃圾回收的时候,key会被清理掉,而value不会,则这导致ThreadLocalMap中用很多的 key为null的entry,如若我们不做任何处理的话,value永远不会被处理掉,此时就发生了内存泄漏,解决方案很简单,使用ThreadLocal下的remove方法及时的删除就可以避免内存泄漏;

java引用的四种类型

1.强引用:即使发生OOM(内存溢出)也不会进行回收;
2.软引用:在内存足够的情况下,垃圾回收器可以考虑不回收此引用,当OOM时才会回收;
3.弱引用:不管内存是否足够,都要进行回收;
4.虚引用:创建即回收,可以触发一个垃圾回收的回调;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值