Java核心知识---初识线程
Posted 一位懒得写博客的小学生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java核心知识---初识线程相关的知识,希望对你有一定的参考价值。
最早的并发编程是多进程并发编程。
多个进程不能共享资源。
目录
- 线程(Thread)
线程(Thread)
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。一个进程内的线程之间是可以共享资源的;
- 一个进程内最少包含一个线程,即主线程;
- 线程必须依附进程才能存活。
- 进程不可以共享资源,而线程可以。
-
线程可以共享的资源
- 打开的文件
- 内存(对象) 线程不可共享的资源
- 线程栈信息
- 优先级
注意:当线程的数量达到某个合适的值是最好的,如果太多的线程,就会出现线程之间的争抢和CPU的过度调度问题了,而CPU调度是需要消耗系统资源的。
合适线程数量
- 具体的应用场景
- 密集计算的 CPU 任务、IO(文件读写)型任务。
- 当使用的场景是计算行任务时,线程的数量等于CPU的数量是最好的。
进程 ->线程(轻量级的进程) -> 协程(轻量级的线程)golang(谷歌)
线程的创建方式
- 继承 Tread 类是实现线程的创建线程(2中方法);
- 实现 Runnable 接口的方法(4中方法);
- 实现 Callable 接口(1种方法)。
线程休眠
//方法一:
Thread.sleep(1000);//休眠一秒
//方法二:
TimeUnit.SECINDS.sleep(1);//休眠一秒
//方法三:
Thread.sleep(TimeUnit.SECINDS.toMillis(1));
使用线程打印"AABBCCDD"
public class ThreadMain
public static void main(String[] args)
Runnable runnable = new Runnable()
@Override
public void run()
String data = "ABCD";
for (char c : data.toCharArray())
System.out.print(c);
//使用休眠的额方式等待打印
try
Thread.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
;
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
//执行结果
AABBCCDD
线程优先级
新创建的线程默认的优先级为5.
优先级的取值是1~10,这个值越大那么它的(执行)权重就越高。
优先级越高执行的优先级也越高,执行权也就越大,但是 CPU 的调度是很复杂的,所以不会严格的按照优先级的排序去执行,但总体来看还是优先级越高执行的权重也越大。
代码
public class ThreadMain2
public static void main(String[] args)
// 定义方法
Runnable runnable = new Runnable()
@Override
public void run()
System.out.println("线程名:" +
Thread.currentThread().getName());
;
for (int i = 0; i < 10; i++)
Thread t1 = new Thread(runnable, "张三");
Thread t2 = new Thread(runnable, "李四");
Thread t3 = new Thread(runnable, "王五");
t1.setPriority(1); // 优先级最小
// t2.setPriority(Thread.MIN_PRIORITY); // 优先级最小
t3.setPriority(Thread.MAX_PRIORITY); // 最高的优先级
t1.start();
t2.start();
t3.start();
//执行结果
线程名:张三
线程名:李四
线程名:王五
线程名:王五
线程名:王五
线程名:李四
线程名:张三
线程名:王五
线程名:李四
线程名:王五
线程名:王五
线程名:李四
线程名:张三
线程名:李四
线程名:王五
线程名:王五
线程名:张三
线程名:张三
线程名:张三
线程名:李四
线程名:张三
线程名:李四
线程名:李四
线程名:王五
线程名:李四
线程名:李四
线程名:王五
线程名:张三
线程名:张三
线程名:张三
Process finished with exit code 0
线程分组(ThreadGroup)
可以将一类线程归为一组,并且进行信息的打印,查看一组线程的具体行为。
public class ThreadMain3
public static void main(String[] args)
// 定义任务
Runnable runnable = new Runnable()
@Override
public void run()
System.out.println("开始起跑:" +
Thread.currentThread().getName());
try
int num = 1 + new Random().nextInt(5);
Thread.sleep(num * 1000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("到终点了:" +
Thread.currentThread().getName());
;
// 定义分组
ThreadGroup group = new ThreadGroup("百米赛跑一组");
// 创建运动员
Thread t1 = new Thread(group, runnable, "张三");
Thread t2 = new Thread(group, runnable, "李四");
t1.start();
t2.start();
// 打印线程分组的详情
group.list();
// 等待所有选手到达终点
while (group.activeCount() != 0)
// 宣布成绩
System.out.println("宣布成绩");
//执行结果
java.lang.ThreadGroup[name=百米赛跑一组,maxpri=10]
开始起跑:张三
Thread[张三,5,百米赛跑一组]
开始起跑:李四
Thread[李四,5,百米赛跑一组]
到终点了:张三
到终点了:李四
宣布成绩
Process finished with exit code 0
线程分类
- 后台线程(守护线程);
- 用户线程(默认线程);
守护线程是用来服务用户线程的,用户线程就是上帝,守护线程就是服务员。
进程退出:没有用户线程运行的时候,进程就会结束。
守护线程使用场景:Java 垃圾回收器。
public class ThreadMain4
public static void main(String[] args)
Thread t1 = new Thread(() ->
// 在守护线程的内部创建的线程
Thread t2 = new Thread(() ->
);
System.out.println("t2 是:" +
(t2.isDaemon() == true ? "守护线程" : "用户线程"));
);
// 设置为守护线程
t1.setDaemon(false);
t1.start();
System.out.println("t1 是否为守护线程:" + t1.isDaemon());
//执行结果
t1 是否为守护线程:false
t2 是:用户线程
-
注意事项
- 守护线程设置必须调用 start();
- 如果设置守护线程在开始线程之后,那么程序就会报错,并且设置的守护线程值就不能生效。
- 在守护线程里面创建的线程,默认情况下全部都是守护线程。
Thread 几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称: | getName() |
状态: | getState() |
优先级: | getPriority() |
是否后台线程: | isDaemon() |
是否存活: | isAlive() |
是否被中断: | isInterrupted() |
代码
public class ThreadMain1
public static void main(String[] args)
Thread t1 = new Thread(() ->
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
, "张三");
System.out.println("线程状态前:" + t1.getState());
t1.start();
System.out.println("线程状态后:" + t1.getState());
System.out.println("线程ID:" + t1.getId());
System.out.println("线程的名称:" + t1.getName());
System.out.println("线程优先级:" + t1.getPriority());
System.out.println("线程是否为后台线程:" + t1.isDaemon());
System.out.println("线程是否存活" + t1.isAlive());
System.out.println("线程是否被中断" + t1.isInterrupted());
//执行结果
线程状态前:NEW
线程状态后:RUNNABLE
线程ID:12
线程的名称:张三
线程优先级:5
线程是否为后台线程:false
线程是否存活true
线程是否被中断false
线程中断
- 使用全局自定义的变量来终止线程;
- 使用线程提供的方法 interrupt 来终止线程;
- 使用线程提供的方法 stop 来终止线程。
注意:使用全局自定义标识终止线程执行的时候比较温柔,当他收到终止指令后,会把当前手头的任务执行完再终止;使用 interrupt 在收到终止指令之后会立马结束执行。
两种方法
public class Thread1
public static void main(String[] args)
Thread t1 = new Thread(new Runnable()
@Override
public void run()
for (int i = 0; i < 10; i++)
System.out.println(Thread.interrupted());
t1.start();
// 终止线程
t1.interrupt();
//执行结果
true
false
false
false
false
false
false
false
false
false
Process finished with exit code 0
public class Thread1
public static void main(String[] args)
Thread t1 = new Thread(new Runnable()
@Override
public void run()
for (int i = 0; i < 10; i++)
System.out.println(Thread.currentThread().isInterrupted());
);
t1.start();
// 终止线程
t1.interrupt();
//执行结果
true
true
true
true
true
true
true
true
true
true
Process finished with exit code 0
-
interrupted()与 isInterrupted()
- interrupted 是全局的方法,判断完之后会重置线程的状态。
- isInterrupted() 将线程中的终止状态从默认的 false 改为 true ;
等待线程 join()
方法 | 说明 |
---|---|
public void join(): | 等待线程结束 |
public void join(long millis): | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos): | 同理,但可以更高精度 |
线程状态
public class Thread1
public static void main(String[] args)
for (Thread.State state : Thread.State.values())
System.out.println(state);
//执行结果
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
- NEW 新建状态,没有调用start()之前的状态
- RUNNABLE 运行状态(Running 执行中);
- BLOCKED 阻塞状态;
- WAITING 等待,没有明确的等待时间
- TIMED_WAITING 超时等待时间,有明确的等待时间;
- TERMINATED 终止状态
线程非安全
多线程执行中,程序的执行结果和预期不相符就叫做线程不安全。
- CPU 抢占执行(源头);
- 非原子性;
- 编译器优化(指令重排序)(代码优化);
- 内存不可见;
- 多个线程同时修改了同一个变量。
编译器优化在单线程下没问题,可以提升程序的执行效率,但是在多线程的情况下就会出现混乱,导致线程不安全情况。
start() VS run()
- Start():用来启动线程,真正实现了多线程;
- Run():当作普通方法来调用。
代码对比
Start():
public class ThreadDemo5
public static final boolean flag=false;
public static void main(String[] args)
Thread t1=new Thread(new Runnable()
@Override
public void run()
System.out.println(Thread.currentThread().getName());
);
t1.start();
//执行结果
Thread-0
Run()
public class ThreadDemo5
public static final boolean flag=false;
public static void main(String[] args)
Thread t1=new Thread(new Runnable()
@Override
public void run()
System.out.println(Thread.currentThread().getName());
);
t1.run();
//执行结果
main
线程的工作方式:
- 先在自己的工作内存中找变量;
- 去主内存里面找变量
Volatile 轻量级解决“线程安全” 的方案;
-
作用:
- 进制指令重排序;
- 解决线程可见性问题(实际原理:当操作完变量之后,强制删除掉线程工作内存中的此变量);
- (注意)不能解决原子性问题。
线程安全:
- CPU 抢占调度(不能);
- 每个线程操作自己的变量(可能性),不通用,修改难度大;
- 在关键代码让所有的CPU排队执行,加锁。
锁操作的关键步骤:
- 尝试获取锁(如果成功拿到加锁,拿不到排队);
- 释放锁。
JAVA 中解决线程安全的问题的方案:
- synchronized 加锁和释放锁【JVM层面的解决方案,自动加锁和释放锁】;
- Lock 手动锁(程序员自己加锁和释放锁)
Synchronized
- 在进行加锁操作时,同一组业务必须是同一把锁对象。
- 锁机制是非公平锁。(公平锁可以按顺序进行执行,非公平锁执行的效率更高)。
在 JAVA 中所有锁默认的策略都是非公平锁。
使用场景:
- 修饰代码块(加锁对象可以自定义);
- 修饰静态方法(加锁对象是当前类对象);
- 可以用来修饰普通方法(加锁对象是当前类的实例)。
原理:
- 操作:互斥锁 mutex;
- JVM :帮忙实现的监视器锁的加锁和释放锁的操作;
- JAVA :(锁存在的地方:变量的对象头)。
JDK 6 对 synchronized 优化
lock.lock();
- 如果将lock()方法放在try里面,那么当try里面的代码出现异常之后,那么就会执行finally里面释放锁的代码,但这个时候加锁还没成功,就去释放锁。
- 执行finall里面释放锁的代码时就会报错(线程状态异常),释放锁的异常会覆盖掉业务代码的异常报错,从而增加了排除错误成本。
Lock 默认的锁策略是非公平锁,但是Lock可以显示的声明为公平锁。
Volatile 和synchronized 有什么区别
- volatile 可以解决内存可见性问题和禁止指令重排序,但volatile 不能解决原子性问题;synchronized 是用来保证线程安全,也就是synchronized 可以解决任何关于线程安全的问题(关键代码排队执行,始终只有一个线程会执行加锁操作;原子性问题);
synchronized 和Lock 之间的区别
- synchronized 既可以修饰代码快,又可以修饰静态方法与普通方法;而Lock智能修饰代码块;
- synchronized只有非公平的锁策略,而Lock既可以公平锁也可以非公平锁(ReentranLock默认是fgp锁,也可以通过构造函数设置 true 声明它为公平锁);
- ReentrantLock 更加的灵活(比如 tryLock);
- synchronized 是自动加锁和释放锁的,而 ReentrantLock 需要自己手动加锁和释放锁。
死锁
定义:在多线程中(两个或两个以上的线程),因为资源抢占而造成线程无限等待 的问题。
代码示例
public static void main(String[] args)
Object lockA = new Object();
Object lockB = new Object();
Thread t1 = new Thread(new Runnable()
@Override
public void run()
synchronized (lockA)
System.out.println("线程A");
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("等待B");
synchronized (lockB)
System.out.println("线程1得到了锁B");
,"t1");
t1.start();
Thread t2 = new Thread(new Runnable()
@Override
public void run()
synchronized (lockB以上是关于Java核心知识---初识线程的主要内容,如果未能解决你的问题,请参考以下文章