1、进程和线程
1:进程:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程就是运行起来的程序,程序运行起来需要被加载在内存中(这是站在用户角度进行描述),类似电脑QQ运行,浏览器的运行等等。
2:线程:线程是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可有一个或多个线程,各个线程之间共享程序内存空间。
3:进程和线程区别:
1、线程是程序执行的最小单位,而进程是操作系统分配的最小单位。
2、一个进程可以由一个或多个线程组成,线程是一个进程中代码的不同执行路线。
3、进程之间相互是独立的,但是同一进程之间各个线程之间共享程序内存空间。
4、线程上下文调度和切换要比进程上下文切换快的多。
2、并行和并发:
并行:指同一时刻内,两个或多个事件同时发生;并行是在不同实体上发生的多个事件。
并发:并发是指在相同的时间间隔内发生两个或者多个事件;并发是指在同一实体内发生的多个事件。
并行是针对进程;并发是针对线程。
3、线程创建
多线程共有三种创建方式,分别为:继承Thread类,实现Runable接口和实现Callable接口。
1:继承Thread类,重写run()方法,调用start方法启动线程。
package com.ygl.demo01;
/**
* @Author: 闫高岭同志
* @Date: 2020/11/22 16:38
* @Version 1.0
*/
//创建线程方式一:继承Thread类,重写run方法,调用start方法开启线程
//线程开启不一定立即执行,由CPU进行调度
public class TestThread1 extends Thread{
//run方法线程体
@Override
public void run() {
for (int i = 0; i <20; i++) {
System.out.println("我在看代码:"+i);
}
}
//main方法主线程
public static void main(String[] args) {
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
TestThread1 testThread2 = new TestThread1();
//调用start,调用线程体
testThread1.start();
testThread2.start();
for (int i =0 ; i<20 ; i++){
System.out.println("我在学习多线程:"+i);
}
}
}
Thread 类源码:
package java.lang;
public class Thread implements Runnable {
// 构造方法
public Thread(Runnable target);
public Thread(Runnable target, String name);
public synchronized void start();
}
Runnable 接口源码:
package java.lang;
@FunctionalInterface
public interface Runnable {
pubic abstract void run();
}
2:实现Runable接口,重写Run方法,调用start()方法启动。
package com.ygl.demo01;
/**
* @Author: 闫高岭同志
* @Date: 2020/11/22 17:28
* @Version 1.0
*/
//创建线程方式二 :实现Runnable接口,重写run方法,执行线程需要丢入runnable接口的实现类,调用start方法
public class TestThread3 implements Runnable{
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("看代码:"+i);
}
}
public static void main(String[] args) {
TestThread3 testThread3 = new TestThread3();
Thread thread = new Thread(testThread3,"线程1");
thread.start();
for (int i = 0; i < 2000; i++) {
System.out.println("学习duo:"+i);
}
}
}
3:实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask,再然后包装成Thread。
public class Main {
public static void main(String[] args) throws Exception {
// 将Callable包装成FutureTask,FutureTask也是一种Runnable
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// get方法会阻塞调用的线程
Integer sum = futureTask.get();
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
return sum;
}
}
Callable 也是一种函数式接口:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
FutureTask源码:
public class FutureTask<V> implements RunnableFuture<V> {
// 构造函数
public FutureTask(Callable<V> callable);
// 取消线程
public boolean cancel(boolean mayInterruptIfRunning);
// 判断线程
public boolean isDone();
// 获取线程执行结果
public V get() throws InterruptedException, ExecutionException;
}
RunnableFuture源码:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
三种创建方式比较:
1、继承Thread类:不建议使用,因为继承是单继承,继承过Thread类后就没法再去继承其他类,不够灵活。
2、实现Runable接口:比Thread类灵活,避免单继承的限制。
3、重写Callable接口:Thread类和Runable接口都是重写run()方法,没有返回值,Callable是重写call方法,并且有返回值,而且还可以借助FutureTask来判断线程是否已经执行完毕或者取消线程执行。

4、线程状态:
线程分为五种状态,分别为新建、就绪、运行、阻塞和死亡状态。
1、新建状态:当用new创建一个新线程时,如:new Thread();这个时候线程还没有开始运行,此时处于新建状态。程序还没有运行线程中代码。
2、就绪状态:线程创建过后,并没有进行立即进行运行,需要调用start()方法,才能进行运行run()方法;当线程调用start()方法,此时线程处于就绪状态,但是CPU还没有进行调度此线程,因此这个线程目前处于就绪状态,就绪状态也就是从调用start()方法之后到CPU调度此线程之前。
3、运行状态:处于就绪状态的线程,当获得CPU时间后,它才能进行运行状态,才是真正的执行run()方法。
4、阻塞状态:线程运行过程中,由于各种原因进入阻塞状态,
a:线程调用sleep()方法进入睡眠状态。
b:线程试图得到一个锁,但是该锁正在被其他线程所持有。
c:线程在等待某个出发条件等待
、、、、、、、、
5、死亡状态:
有两种原因导致死亡:
a:run()方法正常退出而自然死亡。
b:一个未捕获的异常终止了run方法而使线程猝死。
注意:死亡后的线程不能再次进行调用。
线程停止:
a、建议线程正常停止——》利用次数,不建议死循环。
b、建议使用标志位——》设置一个标志位。
c、不要使用stop或destroy等过时或者JDK不建议使用的方法。
线程睡眠:
调用Thread.sleep();的方法,一般使用倒计时等功能。
线程礼让:
调用线程Thread.yield()方法,只是提醒当前线程要进行释放其占有的资源,让其他线程使用,不保证当前线程一定释放出资源,释放资源后其他同等级的线程会进行竞争其资源,然后释放的该线程重新进行回到就绪状态。
线程join:可以想象成插队,当该线程执行完成后再去执行其他线程,其他线程阻塞。
5、线程同步:
同步就是协同步调,按预定的先后顺序进行执行。例如你说完后我再说。这里的同步千万不要理解成同时进行,应是协同、协助和相互配合。线程同步是指多线程通过特定的设置(如互斥量,临界区和事件对象),来控制线程之间的执行顺序(即所谓的同步)。同步也就是一种等待机制。如果没有线程同步,那就是各个线程之间各自运行各自的。
6、线程异步:
A线程请求某个资源,但是这个资源正在让B线程使用中,因为没有同步机制的存在,A线程仍然能够请求得到,A线程无需等待。
7、同步和异步区别:
同步是安全的,异步是不安全的,会导致死锁,那么真个系统就会崩溃掉,但是异步没有同步机制,那么性能就加粗样式会大大提升。
8、线程互斥:
线程互斥是指对于共享的进程系统资源,在各单个线程访问时具有排他性。也就是当有若干个线程都要使用某一共享进程资源时,任何时刻都只允许一个线程去使用,其他线程使用共享资源必须等该线程使用完释放后才能去竞争共享资源。线程互斥可以理解为特殊的线程同步。
9、锁和队列:
锁:锁的应用场景:当多个线程需要操作同一个文件或者数据的时候;当多个进程使用同一份数据资源的时候就会引发数据安全和顺序混乱问题。
为保证数据的安全性,多线程中操作共享资源时,只有加锁才能保证数据的安全性;加锁保证多个线程在操作同一个数据时,即串行的操作,对,速度肯定慢下来,但是保证了安全性。
为了保证线程的安全性,除了锁之外还有队列能够保证线程安全性,也就是大家常说的生产者消费者模式(这个模式可不是二十三中模式之一哈)。今天不做描述,改天再进行补充(2020/11/26 01:08分,狗头保命)
10、加锁机制:
咱们主要分享两种加锁机制,分别为synchronized和lock锁。
10.1、synchronized锁:
10.1.1:synchronized锁是什么?
synchronized是java的一个关键字,能够将方法和代码块给锁起来,实现线程同步,保护该操作资源的安全性;使用的话只需要在代码块或者方法上添加synchronized关键字即可,即可实现同步功能。synchronized是隐士锁,并且是非公平锁。所谓显示锁和隐士锁的判断就是根据是否进行手动获取锁和释放锁;所谓公平和非公平锁就是根据A线程在等待着B线程资源释放的时候,这个时候新加入C线程等待,当B线程资源释放时,这个时候按照线程的先后等待顺序来执行,则说明是公平锁,否则是非公平锁;公平锁则消耗其性能,就比如厕所排队,就一个厕所,当挨个排队的时候,大号的先进去,蹲了二十分钟,如果是不公平的话,让小号进去,这二十分钟都可能解决了40个人的小号问题(哈哈,采用夸张说法哈。)。
public synchronized void test(){
//来吧小老弟
}
此时一次只能一个线程进入被锁住的代码块。
10.1.2:synchronized锁用处什么?
synchronized保证线程原子性(被保护的代码块是一次被执行的,没有任何线程会同时访问)。
synchronized保证可见性(当变量被线程操作之后,变量的修改对其他线程是可见的)。
10.1.3:synchronized锁如何使用?
synchronized一般用来修饰三种东西:
1、修饰静态方法
2、修饰普通方法
3、修饰代码块
a、修饰普通方法代码如下:
public class JavaTest {
// 修饰普通方法 这个锁是对象锁,也就是JavaTest对象(内置锁)
public synchronized void test() {
// doSomething
}
}
b、修饰静态方法代码如下:
public class JavaTest {
// 修饰静态方法代码块,静态方法属于类方法,它属于这个类,获取到的锁是属于类的锁(类的字节码文件对象)-->JavaTest.class
public static synchronized void test() {
}
}
c、修饰代码块代码如下:
public class JavaTest {
public void test() {
// 修饰代码块,此时用的锁是JavaTest 对象(内置锁)--->this
synchronized (this){
}
}
}
当然了,我们使用synchronized修饰代码块时未必使用this,还可以使用其他的对象(随便一个对象都有一个内置锁)
public class JavaTest {
// 使用object作为锁(任何对象都有对应的锁标记,object也不例外)
private Object object = new Object();
public void test() {
// 修饰代码块,此时用的锁是自己创建的锁Object
synchronized (object){
}
}
}
10.1.4:类锁和对象锁
synchronized修饰静态方法上的是类锁(类的字节码文件对象),synchronized修饰普通方法和代码块获取的是对象锁。
10.1.5:可重入锁
话不多说,直接扔代码体会:
public class Widget {
// 锁住了
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
// 锁住了
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
}
1、当线程A进入到LoggingWidget 类中的doSomething()方法时,会拿到LoggingWidget实例对象的锁。
2、然后又要进入到父类的Widget 中的doSomething()方法,这父类的方法又被synchronized修饰。
3、现在LoggingWidget 类中的锁还没释放,进入到父类的doSomething()方法还需要再获取一把锁嘛?
不需要
因为锁的持有者是线程,而不是“调用”。永远记住锁的持有者是线程。
10.1.6:释放锁的时机
1、当方法执行完之后,会自动释放锁,不需要手动释放锁。
2、当一个线程执行过程中出现异常时,会自动释放锁。不会因为异常导致死锁。
10.2、lock锁:
lock锁是显式锁,它是一个接口,在jdk5以后才引入的类。下面进行简单总结下:
1、Lock方式来获取锁支持中断、超时不获取、是非阻塞的
2、Lock显式锁可以给我们带来很好的灵活性,但同时我们必须手动释放锁
3、允许多个 读线程 同时访问共享资源
下面来个例子来简单说下如何使用:
ReentrantLock l = new ReentrantLock();
l.lock();//获取锁 在try外面使用,防止出现异常而无法释放
try{
//上锁资源
}finally {
l.unlock();//释放锁
}
10.3、synchronized锁和lock锁区别:
1、synchronized是Java关键字,lock是接口
2、synchronized是隐式锁,lock是显示锁
3、synchronized不需要手动释放,而lock需要手动释放,一般结合try{}finally{}使用,释放l.unlock()都是在finally{}里释放。
4、synchronized是不可中断的,除非抛出异常或者正常运行完成。Lock可以中断的。
5、两者都是非公平锁。
暂时先写这么多,后期有新的知识点再添上与大家一起分享,随手点赞,评论和转发是美德哦,创作不易,请多多支持,对了,那些不对的和有疑惑的请大家在下方留言。