15.1 概念
以前写的程序都是单线程,main方法程序称为主线程,主线程的结束所有的子线程都会跟着结束。多线程就代表着一个程序可以去做多件事情。
线程:一个程序去做多件事情,每件事情由一个线程去完成。
进程:一个进程由多个线程组成,一个进程至少有一个线程。一个进程就是一个应用程序
多线程并发问题:
串行:一次只能取得一个任务并执行这个任务,这个任务执行完后面的任务才能继续
并行:一次可以取得多个任务,并且可以同时执行这几个任务
并发:一次可以取得多个任务,但是只能同时执行一个任务
代码语言:javascript复制并发和并行是两个非常容易混淆的概念。它们都可以表示两个或多个任务一起执行,但是偏重点有点不
同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。并发是逻辑上的同时发生
(simultaneous),而并行是物理上的同时发生。然而并行的偏重点在于”同时执行”。
严格意义上来说,并行的多个任务是真实的同时执行,而对于并发来说,这个过程只是交替的,一会运
行任务一,一会儿又运行任务二,系统会不停地在两者间切换。但对于外部观察者来说,即使多个任务是
串行并发的,也会造成是多个任务并行执行的错觉。
实际上,如果系统内只有一个CPU,而现在而使用多线程或者多线程任务,那么真实环境中这些任务不
可能真实并行的,毕竟一个CPU一次只能执行一条指令,这种情况下多线程或者多线程任务就是并发
的,而不是并行,操作系统会不停的切换任务。真正的并发也只能够出现在拥有多个CPU的系统中(多
核CPU)。
并发的动机:在计算能力恒定的情况下处理更多的任务, 就像我们的大脑, 计算能力相对恒定, 要在一天中
处理更多的问题, 我们就必须具备多任务的能力. 现实工作中有很多事情可能会中断你的当前任务, 处理这
种多任务的能力就是你的并发能力。
并行的动机:用更多的CPU核心更快的完成任务. 就像一个团队, 一个脑袋不够用了, 一个团队来一起处理
一个任务。
15.2 体验案例
语法:
创建线程对象:
Thread thread=new Thread(Runnable run);
启动线程
thread.start(); //告知CPU当前线程已经准备就绪,可以执行。
线程从来都不是由java代码启动的,java代码只能通过thread.start();告知CPU当前线程已经准备就绪,可以执行。什么时候真正的去调用这个线程,由CPU说了算。
创建一个线程不要慌着去启动,因为每一个线程你都得明确的告知它要去完成什么事情。
Runnable接口-->线程任务接口,定义一个类去实现这个接口,告知要做的事情
面试问题:线程有几种创建方式。
第一种实现线程的方式:创建一个任务类去实现Runnable接口
代码语言:javascript复制//任务类:
package com.qf.threadDemo.entitys;
public class ShowNum implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10; i ) {
System.out.println("子线程:" i);
}
}
}
//调用类
package com.qf.threadDemo;
import com.qf.threadDemo.entitys.ShowNum;
public class Demo1 {
public static void main(String[] args) {
Runnable showNum=new ShowNum();
//创建了一个线程对象
Thread thread=new Thread(showNum);
//告知cup线程已经准备就绪 cup
thread.start();//main主线程到这句代码时并不会去等待子线程的执行,自己执行main后面的代码,这个现象叫做 异步sync
for (int i = 1; i <= 10 ; i ) {
System.out.println("主线程:" i);
}
}
}
//该程序多运行几次你会发现每次的结果都不太一样
代码语言:javascript复制Thread.currentThread().getName() //获取当前线程的名称
代码语言:javascript复制Thread thread=new Thread(showNum,"PrintNumThread");//第一个参数是执行任务对象,第二个参数是设置当前的线程名
Thread.sleep(毫秒值),让当前线程沉睡(与处阻塞状态),时间由毫秒值决定,sleep方法需要异常处理
代码语言:javascript复制package com.qf.threadDemo;
import com.qf.threadDemo.entitys.ShowNum;
public class Demo1 {
public static void main(String[] args) {
Runnable showNum=new ShowNum();
//创建了一个线程对象
Thread thread=new Thread(showNum,"PrintNumThread");
//告知cup线程已经准备就绪 cup
thread.start();
for (int i = 1; i <= 10 ; i ) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() "-----------" i);
}
}
}
package com.qf.threadDemo.entitys;
public class ShowNum implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10; i ) {
try {
Thread.sleep(500);//让当前线程暂停500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() "-----------" i);
}
}
}
//因为是两个线程,打印结果时就是500毫秒同时打印两个线程的一次结果。
思考问题:
代码语言:javascript复制/**
规则:龟兔同时起步,兔每100毫秒秒跑1米,龟每500毫秒秒跑1米,终点为100米,兔子跑步的能力强,乌龟跑步的能力弱
途中: 1.兔子跑到10米的时候,谦让睡觉1秒乌龟一下,接着跑
2.兔子跑到50米的时候,再让龟2秒毫秒,接着跑
3.兔子跑到80米的时候,睡了5秒,接着跑
分析: 兔子跑步的能力强,乌龟跑步的能力弱(优先级的设置)
4.乌龟全程没有停留
5.只要有一个先跑完,另外的线程停止运行
*/
代码语言:javascript复制//动物类
package com.qf.threadDemo.entitys;
//父类,定义出一个相同的行为match
public abstract class Animal {
public abstract void match();
}
//兔子类,为什么不直接使用兔子或乌龟类去实现Runnable 因为兔子类就是平时写的类,(资源类),Runnable是多线程中的任务类
//在后期的代码中可能这个任务类根本就不存在, 线程去调用资源类,这样才符合面向对象的编程思想。约定俗成的东西。
package com.qf.threadDemo.entitys;
import java.util.concurrent.TimeUnit;
public class Rabbit extends Animal {
@Override
public void match() {
try {
for (int i = 1; i <= 100; i ) {
if(Match.isMatch){
//线程的停止唯一的办法就是让当前线程执行的方法提前结束
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() "跑了" i "米");
if (i == 10) {
TimeUnit.SECONDS.sleep(1); //简化 Thread.sleep(100); 暂停时间的计算
} else if (i == 50) {
TimeUnit.SECONDS.sleep(2);
} else if (i == 80) {
TimeUnit.SECONDS.sleep(5);
}
}
}catch (InterruptedException ex){
ex.printStackTrace();
}
//两个线程哪个先自然的出循环就把这个标识变量设置为true,true就代表不另一个线程该停止了
Match.isMatch=true;
}
}
//乌龟类
package com.qf.threadDemo.entitys;
public class Tortoise extends Animal {
@Override
public void match() {
try {
for (int i = 1; i <= 100; i ) {
if(Match.isMatch){
return;
}
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() "跑了" i "米");
}
}catch (InterruptedException ex){
ex.printStackTrace();
}
Match.isMatch=true;
}
}
//任务类
package com.qf.threadDemo.entitys;
public class AnimalRun implements Runnable {
//使用oop中的多态
private Animal animal;
//在创建这个任务类的时候,要启动哪种动物的跑的过程就传哪个动物的对象进来
public AnimalRun(Animal animal){
this.animal=animal;
}
@Override
public void run() {
//执行线程任务
animal.match();
}
}
//标识类
package com.qf.threadDemo.entitys;
public class Match {
//标识型变量,静态的在多线程中保证内存可见性,就是说在不同的线程中都可以看得见这个变量中的值
public static boolean isMatch=false;
}
//调用类
package com.qf.threadDemo;
import com.qf.threadDemo.entitys.Animal;
import com.qf.threadDemo.entitys.AnimalRun;
import com.qf.threadDemo.entitys.Rabbit;
import com.qf.threadDemo.entitys.Tortoise;
public class Demo2 {
public static void main(String[] args) {
//创建不同的动物
Animal rabbit=new Rabbit();
Animal tortoise=new Tortoise();
//为不同的动物创建比赛的任务
AnimalRun rabbitRun=new AnimalRun(rabbit);
AnimalRun tortoiseRun=new AnimalRun(tortoise);
//创建不同的线程
Thread rabbitThread=new Thread(rabbitRun,"兔子");
Thread tortoiseThread=new Thread(tortoiseRun,"乌龟");
//开始准备执行了
rabbitThread.start();
tortoiseThread.start();
}
}
15.3 线程状态
线程有几种状态:
thread.getState() 可以查看当前此时的线程状态
新建状态: NEW
执行状态: RUNNABLE 关键词。 1、 执行就绪 READY 执行开始 RUNING
阻塞状态: BLOCKED 关键词。在阻塞状态这里有可能会回到就绪状态。
1、WAITING 等待状态 处于阻塞状态之后等待线程启动 , 会一直等待
2、TIMED_WAITING具有指定等待时间的等待线程的线程状态 有时间的限制
终止状态: TERMINATED
注意:
Thread中的start方法 ,真正调用的是
代码语言:javascript复制private native void start0();
所以start()并不是启动线程执行,而是调用了本地方法start0()(c语言的方法)去告诉cpu已经准备好了
Thread类中的run()方法,因为Thread类实现了Runnable接口,所以它必然实现run()方法,而Thread类中的run()是直接使用Runnable对象进行调用,与普通对象.方法()调用形式一样,没有什么特殊性。所以使用thread.run()并不是准备开始一个线程,而就是普通的对象调用方法()
代码语言:javascript复制@Override
public void run() {
if (target != null) {
target.run();
}
}
15.4 并发问题
问题: 三个售票员开始出售100张票。
Runnable使用lambda表达式进行简化写法
代码语言:javascript复制//这是实现的匿名内部类
Runnable target=new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
匿名内部类也比较复杂,所以使用lambda表达式来进行简化
代码语言:javascript复制Runnable target=()->{System.out.println("hello")};
()对应的是run方法后面的参数列表,run方法没有参数,所以lambda表达式的小括号也没有参数,如果参数只有一个参数小括号可以省略
-> 指向run方法的方法体(具体的代码块)
{}代表的是方法的实现,如果方法的实现只有一句代码,大括号可以省略
Runnable target=()->System.out.println("hello");
只有函数式接口可以使用lambda表达式进行简化
如果是定义一个线程任务类来启动线程的话Runnable对象完全可以不用定义
代码语言:javascript复制Thread thread=new Thread(()->System.out.println("hello"));
thread.start();
代码语言:javascript复制package com.qf.threadDemo1;
//票的类
public class Ticket {
//票的总量
private int num=100;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public void diffTicket(){
if(num==0){
System.out.println("票已卖完");
return;
}
num--;
System.out.println(Thread.currentThread().getName() "卖第" (100-num) "张票,还剩" num "张票");
}
}
package com.qf.threadDemo1;
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) {
//
Ticket ticket=new Ticket();
//有三个售票员去卖票
new Thread(()-> {
for (int i = 1; i <= 100; i ) {
if(ticket.getNum()==0){
break;
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket.diffTicket();
}
},"A").start();
new Thread(()-> {
for (int i = 1; i <= 100; i ) {
if(ticket.getNum()==0){
break;
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket.diffTicket();
}
},"B").start();
new Thread(()-> {
for (int i = 1; i <= 100; i ) {
if(ticket.getNum()==0){
break;
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket.diffTicket();
}
},"C").start();
}
}
//现在这个代码有并发冲突。
三个线程同时去操作一个对象中的资源中会出现争抢的情况,这就是并发问题,并发问题是怎么出现的呢?
代码语言:javascript复制//A B C
public void diffTicket(){
if(num==0){
System.out.println("票已卖完");
return;
}
//A num=100 num=99 1 B num=99
num--;
//A num=99 正要打印出结果 B num=98
// A 打印的结果 A卖第2张票,还剩98张票 原来上A应该A卖第1张票,还剩99张票
// B 打印的结果 B卖第2张票,还剩98张票
System.out.println(Thread.currentThread().getName() "卖第" (100-num) "张票,还剩" num "张票");
}
这里的代码没有保证原子性,代码段不可再分隔(不能让两个或多个线程来进来交替执行),代码段是一个整体,每个进来的线程要吗就从头到尾执行完,要吗就不要进来执行。
代码语言:javascript复制public synchronized void diffTicket(){
if(num==0){
System.out.println("票已卖完");
return;
}
num--;
System.out.println(Thread.currentThread().getName() "卖第" (100-num) "张票,还剩" num "张票");
}
synchronized这样加锁加到方法上会将整体代码的效率降低。使用多线程主要目的就是为了快,提高效率。
synchronized(对象){ }可以将一部份关键代码给锁住,从而提高效率,并发问题一定是出现在多个线程去同时修改公共资源上。
代码语言:javascript复制public void diffTicket(){
//线程已经进入到方法中了
synchronized(this) {
if(num==0){
System.out.println("票已卖完");
return;
}
num--;
System.out.println(Thread.currentThread().getName() "卖第" (100-num) "张票,还剩" num "张票");
}
}
代码语言:javascript复制synchronized上锁,上锁用完之后就要解锁,不解锁就是变死锁。如果获取的是多把锁要根据先后顺序对锁进行释放,释放锁的是由jvm自动完成,先锁的后释放,后锁的先释放
多线程中上完锁必须解锁,不然就是死锁
15.5 Lock锁
Lock来自于java.util.concurrent.locks.Lock;包中 java.util.concurrent江湖人称juc专门用于解决多线程并发问题的工具包 .Lock的实现类是ReentrantLock这一个类中提供lock()用于上锁 unlock()方法用于解锁。
只需要在要上锁的地方 lock.lock() 上锁 在解锁的地方 lock.unlock();解锁。不要单独使用配合try...catch...finally来使用,防止死锁的发生。
代码语言:javascript复制public void diffTicket(){
//线程已经进入到方法中了
lock.lock();
try {
if (num == 0) {
System.out.println("票已卖完");
return;
}
num--;
if(num==50){
//没有使用catch进行异常处理,当出现异常的时候jvm处理方案就结束当前线程的执行如果这时不在finally中锁解将会造成死锁
//死锁是多线程最可怕的情况,自己已经宕掉了,其他线程也进不来,程序就是一直卡住。
int i=1/0;//用于模拟代码出现了异常后没有使用try..catch造成死锁的状态
}
System.out.println(Thread.currentThread().getName() "卖第" (100 - num) "张票,还剩" num "张票");
}catch (RuntimeException ex){
ex.printStackTrace();
}
finally {
lock.unlock();
}
}
ReentrantLock 构造方法有两种:
new ReentrantLock (); 非公平锁
new ReentrantLock(boolean); 公平锁
代码语言:javascript复制public ReentrantLock(boolean fair) {
// 公平锁 非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:
概念:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就
到锁。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
公平锁 : 多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
synchronized和Lock的区别:
- 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁; lock中的 tryLock() 如果可用,则获取锁定,并立即返回值为true 。 如果锁不可用,则此方法将立即返回值为false 。
- synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放 锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1 阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以 不用一直等待就结束了;
- synchronized的锁可重入(在一个有锁的方法中去调用另一个有锁的方法的时候,另一个有锁的方法就不用再上锁)、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。// 同步
15.6 多线程中的消费者和生产者问题
消费者和生产者问题,有一个线程负责生产一个线程负责消费
代码语言:javascript复制/*** 题目:现在两个线程,可以操作初始值为0的一个变量
* * 实现一个线程对该变量 1,一个线程对该变量 -1 * 实现交替10次 A 1 B -1
* */
Object类中的
wait() 让当前线程等待(阻塞)状态,一直阻塞直到另一条线程将它唤醒为止
wait(long timeout) 设置沉睡时间
wait(long timeout, int nanos)加时间 timeout是沉睡时间毫秒 nanos 纳秒为单位是额外沉睡时间
wait(5000,500);
notifyAll()唤醒这个类上的所有沉睡线程
notify() 随机唤醒一条沉睡线程
代码语言:javascript复制public class CalcNum {
private int num=0;
public synchronized void add(){
// A C num=0
if(num!=0) {
//A num=1;
try {
this.wait(); //当前线程阻塞,等待 C
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num ;
System.out.println(Thread.currentThread().getName() "-------" num);
this.notify(); //随机唤醒一条线程
}
public synchronized void diff(){
//B D num=0;
if(num!=1) {
try {
this.wait();//B D睡觉 B线程会卡在这里
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//B 走到了这步
num--;
System.out.println(Thread.currentThread().getName() "-------" num);
this.notify();//随机唤醒一条线程
}
}
代码语言:javascript复制public class Demo3 {
public static void main(String[] args) {
CalcNum calcNum=new CalcNum();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.add();
}
},"生产者1:").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.diff();
}
},"消费者1:").start();
}
}
两个线程时可以完美的运行结果
代码语言:javascript复制public class Demo3 {
public static void main(String[] args) {
CalcNum calcNum=new CalcNum();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.add();
}
},"生产者1:").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.diff();
}
},"消费者1:").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.add();
}
},"生产者2:").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.diff();
}
},"消费者2:").start();
}
}
当到了四个线程时就会出现问题了
这里出现的问题叫做虚假唤醒。解决虚假唤醒的方案是线程中的等待应该一直在循环中进行,每次唤醒之后都要再次进行条件判断
代码语言:javascript复制package com.qf.threadDemo1;
import java.util.concurrent.TimeUnit;
public class CalcNum {
private int num=0;
public synchronized void add(){
// A C num=0
while(num!=0) {
//A num=1;
try {
this.wait(); //当前线程阻塞,等待 C
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num ;
System.out.println(Thread.currentThread().getName() "-------" num);
this.notifyAll(); //随机唤醒一条线程
}
public synchronized void diff(){
//B D num=0;
while(num!=1) {
try {
this.wait();//B D睡觉 B线程会卡在这里
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//B 走到了这步
num--;
System.out.println(Thread.currentThread().getName() "-------" num);
this.notifyAll();//随机唤醒一条线程
}
}
15.7 Thread类
API
方法名 | 说明 |
---|---|
static currentThread() | 在线程去获取当前运行线程的对象 |
getName() | 获取当前线程的名称 Thread.currentThread().getName() |
getState() | 返回此线程的状态 |
getPriority() | 返回此线程的优先级 |
interrupt() | 中断当前线程,只能改变线程状态,这个方法将线程中的状态标记true,false,调用这个方式会将这个标识设置为true,当线程执行sleep(),wait(),join()方法的时候报错,报错后呢你需要根据这个异常去决定到底要不要结束线程,try...catch 当catch捕捉完成后会自动将这个标记设置回false |
isInterrupted() | 判断当前线程是否中断这里所谓的中断不是结束线程的运行,只是说的状态是不是中断状态。 |
join() | 等待这个线程结束,在哪里调用join(),哪里开始等待 |
setName(String name) | 设置线程名称 |
setPriority(int newPriority) | 线程优先级越高1-10,默认5,获得 CPU 时间片的概率就越大,但线程优先级的高低与线程的执行顺序并没有必然联系,优先级低的线程也有可能比优先级高的线程先执行 |
setDaemon(boolean on) | 设置当前线程为守护线程 |
isDaemon() | 判断当前线程是否为一个守护线程 |
yield() | 当前线程礼让其他线程,将当前线程从正在执行的状态,改为可执行状态,但是可执行状态依然会被执行,又有可能在其他线程之前抢到cup资源进行执行,从而yield()可能不会有效果 |
yieId()案例:
代码语言:javascript复制public class Demo6 {
public static void main(String[] args) {
Thread thread1=new Thread(()->{
for (int i = 0; i < 100; i ) {
System.out.println(Thread.currentThread().getName() "-----hello-----" i);
Thread.yield();
}
},"A");
Thread thread2=new Thread(()->{
for (int i = 0; i < 100; i ) {
System.out.println(Thread.currentThread().getName() "-----world-----" i);
}
},"B");
thread1.start();
thread2.start();
}
}
//结果运行和没礼让差不多
守护线程 :
main主线程,main结束所有的子线程都得结束,无论子线程是否正在运行。 jvm会检测程序中是否还有子线程在执行,如果在执行会等待子线程执行完成再结束。守护线程为其他普通用户提供服务的线程,它会等待所有线程都结束以后它自动结束。GC就是一个典型在守护线程,当所有线程都结束之后GC也就没有垃圾可回收了,所以所有线程结束以后,GC会自动结束
代码语言:javascript复制public class Demo5 {
public static void main(String[] args) {
Thread threadDaemon=new Thread(()->{
for (int i = 0; true ; i ) {
System.out.println(i);
}
});
Thread thread=new Thread(()->{
for (int i = 0; i< 100 ; i ) {
System.out.println("hello");
}
});
threadDaemon.setDaemon(true);//将当前线程设置为了一个守护线程,一定要再start之前设置
threadDaemon.start();
thread.start();
System.out.println("程序结束");
}
}
//运行结果thread和main线程执行结束以后 threadDaemon自动结束
join() 案例
代码语言:javascript复制public static void main(String[] args) {
Thread thread=new Thread(()->{
for (int i = 0; i < 10 ; i ) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("helloworld");
}
});
thread.start();
try {
thread.join();//程序在这里阻塞,相当于main线程暂停
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束");
}
interrupt()案例
案例1没有问题的原因是catch捕捉异常后直接就退出了
代码语言:javascript复制public class Demo1 {
public static void main(String[] args) {
Thread thread=new Thread(()->{
while (true){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
//释放资源
return;//直接return结束线程方法的执行
}
System.out.println("hello");
}
});
thread.start();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
案例2没出问题的原因是线程中没使用阻塞方法
代码语言:javascript复制public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while (true){
if (Thread.currentThread().isInterrupted()){
return;
}
System.out.println("hello");
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt();//将线程的标记状态设置为true
}
}
案例3: 循环结束不了的原因是当thread.interrupt()后将标记设置为了true,线程中对异常进行捕捉,捕捉之后又将标识改回了false,所以
if (Thread.currentThread().isInterrupted())就会永远不成立,并且设置回了false,Thread.sleep()的调用也不会再引发异常,所以在捕捉代码需要再次的调用一遍Thread.currentThread().interrupt();
代码语言:javascript复制public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while (true){
if (Thread.currentThread().isInterrupted()){
//释放资源 解锁
return;
}
try {
Thread.sleep(300);
System.out.println("hello");
} catch (InterruptedException e) {
e.printStackTrace();
// Thread.currentThread().interrupt();
}
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt();//将线程的标记状态设置为true
}
}
第二种创建线程的方式:
继承Thread类
代码语言:javascript复制public class ShowInfo extends Thread {
//ShowInfo就拥有了Thread所有的功能
//必须要重写run方法,你要在多线程中完成的任务
@Override
public void run() {
for (int i = 0; i < 10; i ) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
}
}
public class Demo7 {
public static void main(String[] args) {
ShowInfo showInfo=new ShowInfo();
showInfo.start();
}
}
15.8 使用juc修改消费者和生产者问题
在消费者生产者问题中 wait() notify() notifyAll() 必须和synchronized配合使用,如果没有了synchronized 这些来自于Object中的方法将不能使用。如果要用juc包解决这个问题使用Lock 替换 synchronized , Condition 去替换 Object中的三个方法。
Condition 指定唤醒哪一个线程。
Condition 对象的创建要依靠Lock锁对象
private Condition conditionA=lock.newCondition();
Object wait()------------Condition await()
Object wait(timeout)------------Condition await(时间值,时间单位) conditionA.await(1,TimeUnit.SECONDS)
Object notify() --------- Condition signal()
Object notifyAll()-------Condition signalAll()
代码语言:javascript复制package com.qf.demo7;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CalcNum {
private int num=0;
private Lock lock=new ReentrantLock();
private Condition provider1=lock.newCondition();
private Condition provider2=lock.newCondition();
private Condition consumer1=lock.newCondition();
private Condition consumer2=lock.newCondition();
public void add(){
try {
lock.lock();
while (num==1){
if("生产者1".equals(Thread.currentThread().getName())){
provider1.await();
}else {
provider2.await();
}
}
num ;
System.out.println(Thread.currentThread().getName() ":" num);
//还是要两个消费者都要唤醒,不然在最后一次的时候有一对生产者和消费者没人去对他们进行唤醒操作,所以程序会一直卡住
consumer1.signal();
consumer2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void diff(){
try {
lock.lock();
while (num==0){
if("消费者1".equals(Thread.currentThread().getName())){
consumer1.await();
}else {
consumer2.await();
}
}
num--;
System.out.println(Thread.currentThread().getName() ":" num);
provider1.signal();
provider2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class Demo3 {
public static void main(String[] args) {
CalcNum calcNum=new CalcNum();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.add();
}
},"A:").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.diff();
}
},"B:").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.add();
}
},"C:").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i ) {
calcNum.diff();
}
},"D:").start();
}
}
15.9 线程中的集合
ArrayList LinkedList Set HashMap,这些集合统统线程不安全。
ArrayList 不安全的案例
代码语言:javascript复制public class Demo1 {
public static void main(String[] args) {
//30条线程第一条向这个集合中填入一个数,最后集合中是1-30全部填完
List<Integer> list=new ArrayList<>();
for (int i = 1; i <= 30; i ) {
final int j=i;
new Thread(()->{
list.add(j);
System.out.println(list);
}).start();
}
}
}
引发ConcurrentModificationException 中文意思就是并发修改异常,多个线程去向同一个空间添入自己所带的值时引发的异常
修改方案:
代码语言:javascript复制List<Integer> list=new Vector<>();//Vector中的方法都加了synchronized所以安全
List<Integer> list= Collections.synchronizedList(new ArrayList<>()); //得到的是一个SynchronizedList 它的add方法也是使用synchronized结构锁对象,这种效率比Vector高
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
修改方案二:
代码语言:javascript复制public class Demo1 {
public static void main(String[] args) {
//30条线程第一条向这个集合中填入一个数,最后集合中是1-30全部填完
List<Integer> list=new ArrayList<>();
for (int i = 1; i <= 30; i ) {
final int j=i;
new Thread(()->{
synchronized (list) { //将list在线程中锁住
list.add(j);
System.out.println(list);
}
}).start();
}
}
}
- CopyOnWriteArrayList 来自于JUC中的一个集合类,专门用于解决ArrayList不安全的问题,写时复制技术,效率更高。
线程安全的变体ArrayList ,其中所有可变操作(
add
,set
,等等)通过对底层数组的最新副本实现 add set 都会对List底层的数组进行修改,在多线程中就容易触发并发问题。读取的时候,在写入的时候会复制一个副本出来让线程去修改,修改完成以后再将修改后的内容来替换原本的内容
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); //上锁
try {
Object[] elements = getArray();//获取原数组
int len = elements.length; //获取原数组长度
//复制一个新数组出来,长度是原数组长度 1
Object[] newElements = Arrays.copyOf(elements, len 1);
newElements[len] = e;//先把添加的值存入新数组的最后一个上
setArray(newElements);//再把新数组去替换原数组
return true;
} finally {
lock.unlock();
}
}
读写分离的思想
HashSet也是线程不安全
Set<Integer> list=new HashSet<>();
Set<Integer> list=Collections.synchronizedSet(new HashSet<>()); 这种方式创建可以解决线程安全问题,这种不是人家想要的方式
Set<Integer> list=new CopyOnWriteArraySet<>(); 这是JUC中的专门用于解决Set线程安全的类
代码语言:javascript复制public class Demo1 {
public static void main(String[] args) {
//30条线程第一条向这个集合中填入一个数,最后集合中是1-30全部填完
Set<Integer> list=new CopyOnWriteArraySet<>();
for (int i = 1; i <= 30; i ) {
final int j=i;
new Thread(()->{
list.add(j);
System.out.println(list);
}).start();
}
}
}
HashMap也是线程不安全
Map<String,Integer> maps=new HashMap<>();不安全
Map<String,Integer> list=Collections.synchronizedMap(new HashMap<>());
Map<String,Integer> list=new ConcurrentHashMap<>();
代码语言:javascript复制public class Demo1 {
public static void main(String[] args) {
//30条线程第一条向这个集合中填入一个数,最后集合中是1-30全部填完
Map<String,Integer> list=new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i ) {
final int j=i;
new Thread(()->{
list.put(String.valueOf(j),j);
System.out.println(list);
}).start();
}
}
}
Spring IOC 容器用什么实现 Map--> ()
15.10 锁
synchronized 锁
只能锁住两种东西,一个对象,一个类
资源类中的属性和方法有两种形式进行调用对象.(属性|方法) 类名.(属性|方法),所以锁,就锁对象和类
代码语言:javascript复制public class Demo1 {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.playCall();
},"A").start();
new Thread(()->{
phone.sendSMS();
},"B").start();
}
}
第一种现象: 没有上锁时,两个线程之间调用互不干扰。
代码语言:javascript复制public class Phone {
public void playCall(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() ":打电话");
}
public void sendSMS(){
System.out.println(Thread.currentThread().getName() ":发短信");
}
}
第二种现象:在方法上加锁,如果A线程先抢到资源执行,发短信必须要等待3秒,因为synchronized在这里锁住的是对象
代码语言:javascript复制public class Phone {
public synchronized void playCall(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() ":打电话");
}
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName() ":发短信");
}
}
第三种现象:增加一个普通方法,用三个线程调用,如果A线程先抢到资源执行,hello不需要等待,因为hello没有上锁
代码语言:javascript复制package com.qf.threadLock;
public class Phone {
public synchronized void playCall(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() ":打电话");
}
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName() ":发短信");
}
//增加一个普通方法
public void hello(){
System.out.println("hello");
}
}
public class Demo1 {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.playCall();
},"A").start();
new Thread(()->{
phone.sendSMS();
},"B").start();
//再开一个线程去调用普通方法
new Thread(()->{
phone.hello();
},"C").start();
}
}
第四种现象: 创建两个Phone对象(两个手机),一个调用打电话,一个调用发短信,如果A线程先抢到资源执行,发短信不需要暂停3秒,因为是两个不同的对象,不影响
代码语言:javascript复制public class Demo1 {
public static void main(String[] args) {
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{
phone1.playCall();
},"A").start();
new Thread(()->{
phone2.sendSMS();
},"B").start();
}
}
第五种现象: 将Phone类中的方法改为静态方法 ,两个手机对象,去调用打电话和发短信,如果A线程先抢到资源执行,发短信需要暂停3秒钟,加上了静态方法就是与类相关了,synchronized锁住的也就是类,所以无论多少对象,只要类不变,就要暂停
代码语言:javascript复制public class Phone {
public static synchronized void playCall(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() ":打电话");
}
public static synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName() ":发短信");
}
}
public class Demo1 {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.playCall();
},"A").start();
new Thread(()->{
phone.sendSMS();
},"B").start();
}
}
第六种现象,和第五种现象同理
代码语言:javascript复制public class Demo1 {
public static void main(String[] args) {
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{
phone1.playCall();
},"A").start();
new Thread(()->{
phone2.sendSMS();
},"B").start();
}
}
第七种现象: 一个静态同步方法,一个同步方法,一个手机对象,如果A线程先抢到资源执行,短信不用等待,锁住的是两个不一样的东西
代码语言:javascript复制package com.qf.threadLock;
public class Phone {
public static synchronized void playCall(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() ":打电话");
}
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName() ":发短信");
}
}
public class Demo1 {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.playCall();
},"A").start();
new Thread(()->{
phone.sendSMS();
},"B").start();
}
}
第八种现象:一个普通同步方法,一个静态同步方法,2部手机,发短信不用等,注意:锁一定是在多个线程中锁同一个东西才有效果。
代码语言:javascript复制public class Phone {
public static synchronized void playCall(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() ":打电话");
}
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName() ":发短信");
}
}
public class Demo1 {
public static void main(String[] args) {
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{
phone1.playCall();
},"A").start();
new Thread(()->{
phone2.sendSMS();
},"B").start();
}
}
总结:
new this 具体的一个手机 static class 唯一的一个模板 一个对象里面如果有多个synchronized方法,某个时刻内,只要一个线程去调用其中一个synchronized 方法了,其他的线程都要等待,换句话说,在某个时刻内,只能有唯一一个线程去访问这些 synchronized方法,锁的是当前对象this,被锁定后,其他的线程都不能进入到当前对象的其他的 synchronized方法 加个普通方法后发现和同步锁无关,换成两个对象后,不是同一把锁,情况立刻变化 都换成静态同步方法后,情况又变化了。所有的非静态的同步方法用的都是同一把锁----实例对象本身 synchronized实现同步的基础:java中的每一个对象都可以作为锁 具体的表现为以下三种形式: 对于普通同步方法,锁的是当前实例对象 对于静态同步方法,锁的是当前的Class对象。 对于同步方法块,锁是synchronized括号里面的配置对象 synchronized (mutex) {return c.add(e);} 当一个线程试图访问同步代码块时,他首先必须得到锁,退出或者是抛出异常时必须释放锁,也就是说 如果一个实例对象同步方法获取锁后,该实例对象的其他同步方法必须等待获取锁的方 法释放锁后才能获取锁,两个不同的对象,锁住的是两个不同的东西,他们的锁互不影响。 所有的静态同步方法用的也是同一把锁----类本身
Lock ReentrantLock () 公平锁和非公平锁
Lock 锁的是一段代码 lock() unlock()之间的代码,实现原由采用的自旋锁--> CAS
代码语言:javascript复制public class ShowInfo {
private volatile boolean lock=false;
public void show1(){
lock();
for (int i = 0; i < 10; i ) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() "=======" i);
}
unlock();
}
private void lock(){
//如果lock是true的时候,就代表已经被其他某个线程抢到锁,剩下的线程,在这里就要进入死循环
while (lock){
}
lock=true;
}
private void unlock(){
//将标记设置为false以后
lock=false;
}
}
public class Demo2 {
public static void main(String[] args) {
ShowInfo showInfo=new ShowInfo();
new Thread(()->{
showInfo.show1();
},"A").start();
new Thread(()->{
showInfo.show1();
},"B").start();
}
}
volatile 保证当前这个变量的内存可见性,让变量在其他线程中也是可见的
使用原子类解决上面多个线程锁不住的bug
代码语言:javascript复制package com.qf.threadLock;
import java.util.concurrent.atomic.AtomicBoolean;
public class ShowInfo {
public volatile AtomicBoolean lock=new AtomicBoolean(false);
public void show1(){
lock();
for (int i = 0; i < 10; i ) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() "=======" i);
}
unlock();
}
private void lock(){
//lock.compareAndSet(false,true) 如果lock中的值与第一个参数(预期的值)一样,就可以将lock的值改为第二个参数
//默认值是false A,B,C,D四个线程同时进来,总有一个先后顺序 假定A对lock进行修改,Lock值就变成了true了,这时B,C,D再用这个预期的值就修改不了数据,原子类保证的就是代码操作的原子性所以,这里对lock的修改以及返回值的操作是一个不可分割的整体。
do {
}while(!lock.compareAndSet(false,true));
}
private void unlock(){
//将标记设置为false以后
lock.compareAndSet(true,false);
}
}
do { } while(!lock.compareAndSet(false,true)); 自旋锁是一种乐观锁(这个世界没有并发问题),没有获取到锁的线程就用一个循环让它转圈圈 。