juc
jus:java util concuerent :java并发编程
多线程
- 实现线程的两种方式
- 继承Thread类:不建议使用,单继承局限性,
- 实现Runnable接口:建议使用
- 实现Callable接口
cpu调度线程,线程不一定立即执行
- 龟兔赛跑
static String winner;
public void run() {
for (int i = 0; i < 1000; i++) {
if(Thread.currentThread().getName().equals("兔子")&&i==88){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean w=isgameover(i);
if(w){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i);
}
}
public Boolean isgameover(int steps){
if(winner!=null){
return true;
}else if(steps>=100){
winner=Thread.currentThread().getName();
System.out.println("比赛结束winner"+Thread.currentThread().getName());
return true;
}
return false;
}
public static void main(String[] args) {
Race rabbit=new Race();
Race wugui=new Race();
Thread thread=new Thread(rabbit,"兔子");
Thread thread2=new Thread(wugui,"乌龟");
thread.start();
thread2.start();
}
-
线程状态:
-
New:新创建的线程,尚未执行;
-
Runnable:可运行,正在执行
run()
方法的Java代码; -
Blocked:被阻塞,因为某些操作被阻塞而挂起;(join)
-
Waiting:等待,因为某些操作在等待中;
-
Timed Waiting:计时等待,因为执行
sleep()
方法正在计时等待;- Terminated:线程已终止,因为
run()
方法执行完毕
- Terminated:线程已终止,因为
-
线程创建之后它将处于 初始状态(NEW),调用
start()
方法后开始运行,线程这时候处于 可运行状态(READY)。 -
可运行状态的线程获得了 CPU 时间片后就处于 运行状态(RUNNING)。
-
当线程执行
wait()
方法之后,线程进入 等待状态(WAITING),进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态【notify()】。 而 超时等待状态(TIME_WAITING)相当于在等待状态的基础上增加了超时限制,【sleep(long millis)/``wait(long millis)】,
当超时时间到达后 Java 线程将会返回到运行状态。 -
当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到****阻塞状态(BLOCKED)。
-
线程在执行 Runnable 的
run()
方法之后将会进入到 终止状态(TERMINATED)。 -
线程礼让 yield
yield 使线程从运行状态暂停,但不阻塞
将线程从运行状态变为就绪状态
让CPU重新调度,是否成功看CPU心情
守护线程
线程同步机制:synchonize
线程通信
线程池
死锁
public class DeadLock {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1() throws InterruptedException {
synchronized(lock1){
System.out.println(Thread.currentThread().getName() + "获取到lock1,请求获取lock2....");
Thread.sleep(1000);
synchronized (lock2){
System.out.println("获取到lock2....");
}
}
}
public void method2() throws InterruptedException {
synchronized(lock2){
System.out.println(Thread.currentThread().getName() + "获取到lock2,请求获取lock1....");
Thread.sleep(1000);
synchronized (lock1){
System.out.println("获取到lock1....");
}
}
}
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(()-> {
try {
deadLock.method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()-> {
try {
deadLock.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
6、sleep() 方法和 wait() 方法区别和共同点?(重要!)
相同点:
两者都可以暂停线程的执行,都会让线程进入等待状态。
不同点:
- sleep()方法没有释放锁,而 wait()方法释放了锁。
- sleep()方法属于Thread类的静态方法,作用于当前线程;而wait()方法是Object类的实例方法,作用于对象本身。
- 执行sleep()方法后,可以通过超时或者调用interrupt()方法唤醒休眠中的线程;执行wait()方法后,通过调用notify()或notifyAll()方法唤醒等待线程。
并发与并行
并发指的是多个任务交替进行,并行则是指真正意义上的“同时进行”。
实际上,如果系统内只有一个CPU,使用多线程时,在真实系统环境下不能并行,只能通过切换时间片的方式交替进行,从而并发执行任务。真正的并行只能出现在拥有多个CPU的系统中。
8、多线程开发带来的问题与解决方法?(重要)
1.安全问题
2性能问题
三)活跃性问题
1)死锁,假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。多个线程环形占用资源也是一样的会产生死锁问题。
解决方法:
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用 lock.tryLock(timeout) 来代替使用内部锁机制。
想要避免死锁,可以使用无锁函数(cas)或者使用重入锁(ReentrantLock),通过重入锁使线程中断或限时等待可以有效的规避死锁问题。
2)饥饿,饥饿指的是某一线程或多个线程因为某些原因一直获取不到资源,导致程序一直无法执行。如某一线程优先级太低导致一直分配不到资源,或者是某一线程一直占着某种资源不放,导致该线程无法执行等。
解决方法:
与死锁相比,饥饿现象还是有可能在一段时间之后恢复执行的。可以设置合适的线程优先级来尽量避免饥饿的产生。
3)活锁,活锁体现了一种谦让的美德,每个线程都想把资源让给对方,但是由于机器“智商”不够,可能会产生一直将资源让来让去,导致资源在两个线程间跳动而无法使某一线程真正的到资源并执行,这就是活锁的问题。
四)阻塞
阻塞是用来形容多线程的问题,**几个线程之间共享临界区资源,那么当一个线程占用了临界区资源后,所有需要使用
解决方法:
可以通过减少锁持有时间,读写锁分离,减小锁的粒度,锁分离,锁粗化等方式来优化锁的性能。
11、synchronized和 Lock 的区别?(重要
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)lock只有代码块锁,synchronize既有方法锁,又有代码块锁
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁(tryLock()方法:如果获取锁成功,则返回true),而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
参考https://blog.csdn.net/qq_38200548/article/details/82943222
多线程之间的通信
wait :等待
notifyall notify(); :唤醒,实际作用相当于通知。
消费者和生产者模型
大厂面试题:
一个空调,两个人控制温度,一个人控制加,一个人控制减使其保持温度一直为0,默认温度也为0,执行10轮
package JUC;
public class Kongtiao {
int wendu=0;
public synchronized void jia() throws InterruptedException {
if(wendu!=0){
//如果这个温度不等于0,等待另一个线程调度,
this.wait();
}
wendu++;
System.out.println(Thread.currentThread().getName()+"\t"+wendu);
this.notifyAll();
}
public synchronized void jian() throws InterruptedException {
if(wendu==0){
//如果这个温度等于0,等待另一个线程调度,
this.wait();
}
wendu--;
System.out.println(Thread.currentThread().getName()+"\t"+wendu);
this.notifyAll();
}
}
package JUC;
public class Test2 {
public static void main(String[] args) {
Kongtiao kt=new Kongtiao();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
kt.jian();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
},"a").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
kt.jia();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"b").start();
}
}
多线程开发中的模板和套路 解决开发和面试99%的问题:
- =高内聚,低耦合的类,操作类的属性的方法写在这个类中
- 高内聚低耦合的前提下,线程操作资源类
- 如何处理多线程交互 判断 干活 通知(处理多线程横向调度机制)例子在上面
- 多线程交互时防止虚假唤醒 if改为while
注意买票票,多个线程买票不属于多线程交互,买票,只要管里面的票卖没有卖完,多个线程之间没有相互通信。
如果在例子1代码中在加一个人控制加,一个人控制减,就会出现如下结果:
可以看出来:前面几次都没有问题,到后面问题就很大了。
b 1
a 0
b 1
a 0
b 1
a 0
c -1
c -2
c -3
c -4
c -5
c -6
c -7
c -8
c -9
c -10
a -11
a -12
a -13
a -14
a -15
a -16
a -17
b -16
解决办法改if为while
结果:
b 1
a 0
b 1
a 0
b 1
c 0
b 1
a 0
b 1
c 0
d 1
a 0
d 1
c 0
b 1
c 0
d 1
a 0
d 1
c 0
b 1
c 0
d 1
a 0
d 1
c 0
b 1
c 0
d 1
a 0
d 1
c 0
b 1
c 0
d 1
a 0
d 1
a 0
b 1
a 0
虚假唤醒:
判断条件为if,当只有一个加和一个减,线程永远只有两种情况,要么加,要么减
当有两个减和加时,先判断是否为0,当线程为0时,如果进来的是减,如果进来的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3HrUUaf7-1598775337765)(/Users/mac/Desktop/java知识框架/juc/未命名文件.jpg)]
新版方法:
使用lock代替synchronized
//方法2
public void jia() throws InterruptedException {
lock.lock();
try {
while (wendu!=0){
//如果这个温度不等于0,等待另一个线程调度,
// this.wait();
condition.await();
}
wendu++;
System.out.println(Thread.currentThread().getName()+"\t"+wendu);
// this.notifyAll();
condition.signalAll();
}catch (Exception e){
}finally {
lock.unlock();
}
}
public void jian() throws InterruptedException {
lock.lock();
try {
while (wendu==0){
//如果这个温度等于0,等待另一个线程调度,
// this.wait();
condition.await();
}
wendu--;
System.out.println(Thread.currentThread().getName()+"\t"+wendu);
// this.notifyAll();
condition.signalAll();
}catch (Exception e){
System.out.println("出错");
}finally {
lock.unlock();
}
}
使用condition 进行精准打击
这是新方法和旧方法的区别,新方法可以实现精准打击
题目:AA打印5次 -> BB打印10次 ->CC打印15次
package JUC;
/*
*
hello world
创建人:chenjun
公司:alibaba
创建时间: 2020/8/26
描述:
*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//编写资源类
class Print{
private Lock l=new ReentrantLock();
private Condition condition1=l.newCondition();
private Condition condition2=l.newCondition();
private Condition condition3=l.newCondition();
private int num=1;
public void printa() throws InterruptedException {
l.lock();
try {
//判断
while(num!=1){
condition1.await();
}
//干活
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
//num作为一个标志,当1操作完后num=2
num=2;
//通知
//当1操作完了唤醒2
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
public void printb() throws InterruptedException {
l.lock();
try {
//判断
while(num!=2){
condition2.await();
}
//干活
for (int i = 1; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
//num作为一个标志,当2操作完后num=3
num=3;
//通知
//当2操作完了唤醒3
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
public void printc() throws InterruptedException {
l.lock();
try {
//判断
while(num!=3){
condition3.await();
}
//干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
//num作为一个标志,当3操作完后num=1
num=1;
//通知
//当3操作完了唤醒1
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
package JUC;
/*
*
hello world
创建人:chenjun
公司:alibaba
创建时间: 2020/8/26
描述:精准打击
*/
public class Test4 {
public static void main(String[] args) {
Print print=new Print();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
print.printa();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
print.printb();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
print.printc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
使用condition对比之前的notify 和notifyall
notify只能唤醒一个
notifyall只能唤醒全部
private Lock l=new ReentrantLock();
private Condition condition1=l.newCondition();
private Condition condition2=l.newCondition();
private Condition condition3=l.newCondition();
//相当于一把锁配了3把钥匙
怎么证明你用过lock?
lock一般和condition搭配使用,精准唤醒,精准通知
别人一听就知道你玩过高并发
一遍不会,多敲几遍,敲个10遍,先玩会了,再能变强。
发现这是一个不错的学习方法。
Arraylist
线程不安全
多线程操作Arraylist:
使用Collections.synchronizedList(new ArrayList());
让ArrayList变成线程安全的
使用new CopyOnWriteArrayList();读写分离的一种变种
Callable
创建线程的第三种方式:
实现callable接口
package JUC;
/*
*
hello world
创建人:chenjun
公司:alibaba
创建时间: 2020/8/26
描述:
*/
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class TTTT {
public static void main(String[] args) {
FutureTask futureTask=new FutureTask(new myThead());
new Thread(futureTask,"aa").start();
}
}
class myThead implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("hhh");
return 1024;
}
}