1. 多线程基础
1.1 线程和进程
进程:
是指一个内存中运行的应用程序,系统运行的程序的基本单位,一个进程可以包含多个线程
线程:
进程内部的一个独立执行单元
进程与线程的区别:
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
注意:
- 因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于CPU 的调度,程序员是不能完全控制的(可以设置线程优先级)。而这也就造成的多线程的随机性。
- Java 程序的进程里面至少包含两个线程,主线程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
- 由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。
1.2多线程的创建
1.2.1继承Thread类
重写run()方法
public class MyThread {
public static void main(String[] args) {
CreateThread thread = new CreateThread();
CreateThread thread2 = new CreateThread();
thread.start();
thread2.start();
}
}
class CreateThread extends Thread {
private int work = 15;
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
if (work > 0) {
System.out.println(name + ":" + (work--));
}
}
}
}
1.2.2实现Runnable重写run方法
public class MyRunnable {
public static void main(String[] args) {
CreateRunnable c = new CreateRunnable();
Thread t =new Thread(c,"我是1号");
Thread t2 =new Thread(c,"我是2号");
t.start();
t2.start();
}
}
class CreateRunnable implements Runnable{
private int work = 15;
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
if (work>0){
System.out.println(name+":"+(work--));
}
}
}
}
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
- 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
1.2.3匿名内部类
public class Dome {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
},"小明").start();
}
}
1.2.4守护线程
用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止。
守护线程当进程不存在或主线程停止,守护线程也会被停止
1.3线程安全
1.3.1卖票案例
synchronized同步方法
/*
* 我们的学习是基于企业级的开发进行的;
* 1、架构:高内聚,低耦合
* 2、套路:线程操作资源类,资源类是单独的
*/
public class DemoTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.saleTicket();
}
},"窗口一").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.saleTicket();
}
},"窗口二").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.saleTicket();
}
},"窗口三").start();
}
}
class Ticket{
private int ticketNum = 30;
synchronized void saleTicket(){
if (ticketNum>0){
System.out.println(Thread.currentThread().getName()+"卖出"+(ticketNum--)+"张票还剩:"+ticketNum);
}
}
}
lock锁
public class DemoLock {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.saleTicket();
}
},"窗口一").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.saleTicket();
}
},"窗口二").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.saleTicket();
}
},"窗口三").start();
}
}
class Ticket2{
private int ticketNum = 30;
private Lock lock =new ReentrantLock();
void saleTicket(){
lock.lock();//枷锁
try {
if (ticketNum>0){
System.out.println(Thread.currentThread().getName()+"卖出"+(ticketNum--)+"张票还剩:"+ticketNum);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
Synchronized 和 Lock 区别
1、Synchronized 是一个关键字、Lock 是一个对象
2、Synchronized 无法尝试获取锁,Lock 可以尝试获取锁,判断;
3、Synchronized 会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),lock锁是手动释放锁!如果你不释放就会死锁。
4、Synchronized (线程A(获得锁,如果阻塞),线程B(等待,一直等待);)lock,可以尝试获取锁,失败了之后就放弃
5、Synchronized 一定是非公平的,但是 Lock 锁可以是公平的,通过参数设置;
6、代码量特别大的时候,我们一般使用Lock实现精准控制,Synchronized 适合代码量比较小的同步问题;
1.3.2死锁
多线程死锁:同步中嵌套同步,导致锁无法释放。
死锁解决办法:不要在同步中嵌套同步
1.4 线程状态
1.4.1 线程状态介绍
NEW(新建)
线程刚被创建,但是并未启动。
RUNNABLE(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
BLOCKED(锁阻塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
WAITING(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TIMED_WAITING(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
TERMINATED(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
1.4.2图
1.4,3、生产者消费者问题
线程和线程之间本来是不能通信的,但是有时候我们需要线程之间可以协调操作:
/*
目的: 有两个线程:A B ,还有一个值初始为0,
实现两个线程交替执行,对该变量 + 1,-1;交替10次
*/
public class Demo01 {
public static void main(String[] args) {
Data data= new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"路飞").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"索隆").start();
}
}
// 资源类
// 线程之间的通信: 判断 执行 通知
class Data{
private int number = 0;
//
synchronized void incr() throws InterruptedException {
//判断
if (number!=0){
this.wait();
}
number++;//执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();//通知
}
synchronized void decr() throws InterruptedException {
if (number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();
}
}
四条线程可以实现交替吗?不能,会产生虚假唤醒问题!
注意if与whil
public class Demo02 {
public static void main(String[] args) {
Data02 data= new Data02();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"路飞1").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"索隆2").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"山治3").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"乔巴4").start();
}
}
//资源类
// 线程之间的通信: 判断 执行 通知
class Data02{
private int number = 0;
//
synchronized void incr() throws InterruptedException {
//判断
while (number!=0){
this.wait();
}
number++;//执行
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();//通知
}
synchronized void decr() throws InterruptedException {
while (number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();
}
}
如何精准唤醒
/*
实现线程交替执行!
主要的实现目标:精准的唤醒线程!
三个线程:A B C
三个方法:A p5 B p10 C p15 依次循环
*/
public class Demo03 {
public static void main(String[] args) {
Data03 data= new Data03();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.print();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"路飞1").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.print10();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"索隆2").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.print15();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"山治3").start();
}
}
class Data03{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
void print() throws InterruptedException {
lock.lock();
try {
//判断
while (number!=0){
condition.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number = 1;
condition2.signal();//通知
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void print10() throws InterruptedException {
lock.lock();
try {
while (number!=1){
condition2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number = 2;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void print15() throws InterruptedException {
lock.lock();
try {
while(number != 2){
condition3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number = 0;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
wait/Sleep区别
1、类不同!
wait : Obejct 类 Sleep Thread
在juc编程中,线程休眠怎么实现!Thread.Sleep
// 时间单位
TimeUnit.SECONDS.sleep(3);
2、会不会释放资源!
sleep:抱着锁睡得,不会释放锁!wait 会释放锁!
3、使用的范围是不同的;
wait 和 notify 是一组,一般在线程通信的时候使用!
sleep 就是一个单独的方法,在那里都可以用!
4、关于异常;
sleep 需要捕获异常
注意:
只要方法被 static synchronized 修饰,锁的对象就是 Class模板对象,这个则全局唯一
方法被 synchronized 修饰的方式,锁的对象是方法的调用者
1.5 线程停止
结束线程有以下三种方法:
(1)设置退出标志,使线程正常退出。
(2)使用interrupt()方法中断线程。
(3)使用stop方法强行终止线程(不推荐使用Thread.stop, 这种终止线程运行的方法已经被废弃,使用它们是极端不安全的!
public class Demo04 {
public static boolean exit =true;
public static void main(String[] args) {
new Thread(()->{
while (exit){
try {
System.out.println("开启线程");
TimeUnit.SECONDS.sleep(5);
for (int i = 0; i < 10; i++) {
System.out.println("这可以吗");
}
} catch (Exception e) {
e.printStackTrace();
}
}
},"小明").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
exit = false;
System.out.println("退出线程");
}
}
1.5.2 使用interrupt()方法
使用interrupt()方法来中断线程有两种情况:
1)线程处于阻塞状态
如使用了sleep,同步锁的wait,socket中的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出InterruptException异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,从而让我们有机会结束这个线程的执行。
2)线程未处于阻塞状态
使用isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。
public class Demo04 {
public static boolean exit = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
public void run() {
while (exit) {
try {
System.out.println("线程执行!");
//判断线程的中断标志来退出循环
if (Thread.currentThread().isInterrupted()) {
break;
}
Thread.sleep(100l);
} catch (InterruptedException e) {
e.printStackTrace();
//线程处于阻塞状态,当调用线程的interrupt()方法时,
//会抛出InterruptException异常,跳出循环
break;
}
}
}
});
t.start();
Thread.sleep(1000l);
//中断线程
t.interrupt();
System.out.println("线程中断了");
}
}
1.6 线程优先级
1.6.1 优先级priority
现今操作系统基本采用分时的形式调度运行的线程,线程分配得到时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。
在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。
public class Demo05 {
public static void main(String[] args) {
PrioritytThread prioritytThread = new PrioritytThread();
// 如果8核CPU处理3线程,无论优先级高低,每个线程都是单独一个CPU执行,就无法体现优先级
// 开启10个线程,让8个CPU处理,这里线程就需要竞争CPU资源,优先级高的能分配更多的CPU资源
for (int i = 0; i < 10; i++) {
Thread t = new Thread(prioritytThread, "线程" + i);
if (i == 1) {
t.setPriority(10);
}
if (i == 2) {
t.setPriority(1);
}
t.setDaemon(true);
t.start();
}
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1总计:" + PrioritytThread.count1);
System.out.println("线程2总计:" + PrioritytThread.count2);
}
static class PrioritytThread implements Runnable {
public static Integer count1 = 0;
public static Integer count2 = 0;
public void run() {
while (true) {
if ("线程1".equals(Thread.currentThread().getName())) {
count1++;
}
if ("线程2".equals(Thread.currentThread().getName())) {
count2++;
}
if (Thread.currentThread().isInterrupted()) {
break;
}
}
}
}
}
1.6.2 join()方法
join作用是让其他线程变为等待。thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
1.6.3 yield方法
Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
yield()让当前正在运行的线程回到可运行状态,以允 许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。