潜入浅出,java多线程到底是个什么东东?面试中应该注意哪方面多线程的知识?
为了近期的面试,准备多线程的学习,这一部分十分重要,在我近期的面试中,问的十分多.尤其是创建线程三种方式,线程池的创建.
线程简介
主线程走主线程,子线程走子线程,main线程,gc线程(也可以称之为守护线程). 线程不能人为干预,可能会出现资源抢夺的问题,所以需要加上并发的控制.线程会带来额外的开销,比如cpu的调度时间
面试题:线程和进程的区别?
⼀个程序下⾄少有⼀个进程,⼀个进程下⾄少有⼀个线程,⼀个进程下也可以有多个线程来增加程序的执⾏
速度。举个例子,进程:包含视频,声音,弹幕,线程就是分别去执行视频,弹幕,线程。在这里可能会有并发跟并行的概念,并行,是多个cup同时工作,并发是一个cpu按照时间片的轮转,执行,尽管我们看着是同时进行的,但最终不是.
线程的创建
面试题:创建线程有哪⼏种⽅式?
创建线程有三种⽅式:
★继承 Thread 重写 run ⽅法;
★实现 Runnable 接⼝;
实现 Callable 接⼝。
一.继承 Thread 重写 run ⽅法;
因为java的单继承,所以不推荐使用.
入门实例
package com.xiucai;
// 继承Thread类,重写run方法,调用strat开启线程
public class TestThread extends Thread{@Overridepublic void run() {//run方法线程体super.run();for (int i = 0; i < 20; i++) {System.out.println("run方法执行的此时"+i);}}//main线程,主线程public static void main(String[] args) {//1.创建线程对象TestThread testThread=new TestThread();//2.调用start()发放开启线程,使用start方法,可以让两个线程都执行testThread.start();for (int i = 0; i < 20; i++) {System.out.println("主线程此时执行"+i);}}
}
这是调用start()方法,交替执行
使用run方法,肯定是先执行run
总结:线程开启不一定立即启动,需要等待cpu的调度
面试题 线程的 run()和 start()有什么区别?
start()⽅法⽤于启动线程,run()⽅法⽤于执⾏线程的运⾏时代码。run() 可以重复调⽤,⽽ start()只能调⽤
⼀次。
实例:多线程的图片下载
1.下载工具类:
链接:https://pan.baidu.com/s/1iDPhNoruFTpmHCxknAdcOQ
提取码:g5mo
2.编写工具类
//下载器方法
class WebDownloader{// 下载方法
public void donwloader(String url,String name){//这里的工具类是上一步执行的方法.传入第一个参数URL,再传入一个文件try {FileUtils.copyURLToFile(new URL(url),new File(name));} catch (IOException e) {e.printStackTrace();System.out.println("IO异常.downloader出现异常");}
}}
3,编写构造函数传入数据
public class TestThread2 extends Thread{private String url;private String name;//构造函数public TestThread2(String url,String name){this.url=url;this.name=name;}//多线程函数体@Overridepublic void run() {super.run();WebDownloader webDownloader=new WebDownloader();webDownloader.donwloader(url,name);System.out.println("下载的文件名"+name);}//主函数public static void main(String[] args) {//创建线程对象TestThread2 testThread2 =new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbj-yuantu.fotomore.com%2Fcreative%2Fvcg%2Fnew%2FVCG211356080686.jpg%3FExpires%3D1643621486%26OSSAccessKeyId%3DLTAI2pb9T0vkLPEC%26Signature%3DtLJzWT8OEpGQ4MHADL%252Fhz9SebeY%253D%26x-oss-process%3Dimage%252Fauto-orient%252C0%252Fsaveexif%252C1%252Fresize%252Cm_lfit%252Ch_1200%252Cw_1200%252Climit_1%252Fsharpen%252C100%252Fquality%252CQ_80%252Fwatermark%252Cg_se%252Cx_0%252Cy_0%252Cimage_d2F0ZXIvdmNnLXdhdGVyLTIwMDAucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLG1fbGZpdCxoXzE3MSx3XzE3MSxsaW1pdF8x%252F&refer=http%3A%2F%2Fbj-yuantu.fotomore.com&app=2002&size=f10000,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1645764262&t=e5f59f659774002781376801cd533c5b","2.jpg");testThread2.start();}}
二.实现接口Runable,推荐使用
灵活方便
package com.xiucai;/*** 实现runable接口,重写run方法,执行线程,需要丢入runable接口的实现类*/
public class TestThread3 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println("run方法执行的此时"+i);}}public static void main(String[] args) {//创建runnable接口的实现类对象TestThread3 testThread3=new TestThread3();//创建线程对象,通过线程对象来开启线程Thread thread=new Thread(testThread3);thread.start();//new Thread(testThread3).start();for (int i = 0; i < 20; i++) {System.out.println("主线程此时执行"+i);}}
}
实现上边run的方法那边,这里值得深究
package com.xiucai;/*** 实现runable接口,重写run方法,执行线程,需要丢入runable接口的实现类*/
public class TestThread3 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println("run方法执行的此时"+i);}}public static void main(String[] args) {//创建runnable接口的实现类对象TestThread3 testThread3=new TestThread3();//创建线程对象,通过线程对象来开启线程Thread thread=new Thread(testThread3);thread.start();//new Thread(testThread3).start();for (int i = 0; i < 20; i++) {System.out.println("主线程此时执行"+i);}}
}
实例:龟兔赛跑
package com.xiucai;
//1.创建多线程类
public class TestThread6 implements Runnable{//胜利者private static String winner;@Override//2.重写多线程run方法public void run() {// for是赛道,i是米数for (int i = 1; i <= 100; i++) {//模拟兔子休息,十步一休息.if (Thread.currentThread().getName().equals("兔子") && i%10==0){try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}//判断比赛是否结束,这是一个判断终点的方法.boolean flag=gameOver(i);//把返回值取回来.if (flag){break;}System.out.println(Thread.currentThread().getName()+":跑了"+i+"步");}}//3.判断是否完成比赛private boolean gameOver(int steps){if (winner!=null){//比赛结束return true;}if (steps>=100){winner=Thread.currentThread().getName();System.out.println("胜利者为:"+winner);return true;}return false;}//4.编写运行实例public static void main(String[] args) {//testThread6极为赛道TestThread6 testThread6=new TestThread6();new Thread(testThread6,"兔子").start();new Thread(testThread6,"乌龟").start();}
}
整个逻辑都是的实现都是在run方法里边的,用i作为资源,兔子和乌龟都去争夺.
三、线程不安全实例-买票
package com.xiucai;/*** 多个线程操作一个对象* 假设买火车票*/
public class TestThread5 implements Runnable {private int ticketNum = 10;@Overridepublic void run() {while (true) {//Thread.currentThread().getName() 得到名字if (ticketNum <= 0) {break;}//模拟延时try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}//输出拿到了多少张票System.out.println(Thread.currentThread().getName() + ":拿到了,第几张票" + ticketNum--);}}public static void main(String[] args) {//小牛,小蓝,小紫TestThread5 ticket =new TestThread5();//上边的 Thread.currentThread().getName() 得到名字new Thread(ticket,"小明").start();new Thread(ticket,"小牛").start();new Thread(ticket,"小紫").start();}
}
以上程序实现了以下效果,我们发现,在进行相同资源的时候.不好使了.也可以称之为线程不安全
小紫:拿到了,第几张票10
小明:拿到了,第几张票9
小牛:拿到了,第几张票8
小紫:拿到了,第几张票7
小明:拿到了,第几张票5
小牛:拿到了,第几张票6
小紫:拿到了,第几张票4
小牛:拿到了,第几张票3
小明:拿到了,第几张票2
小明:拿到了,第几张票1
小牛:拿到了,第几张票0
小紫:拿到了,第几张票1
四、callable接口
实现Callable接口,需要返回值类型,可以抛出异常
package com.xiucai;import org.apache.commons.io.FileUtils;import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;/*** 线程创建方式3,实现callable接口.*/
public class TestThread7 implements Callable {private String url;private String name;//构造函数public TestThread7(String url,String name){this.url=url;this.name=name;}//多线程函数体@Overridepublic Object call() throws Exception {WebDownloader webDownloader=new WebDownloader();webDownloader.donwloader(url,name);System.out.println("下载的文件名"+name);return true;}//主函数public static void main(String[] args) throws ExecutionException, InterruptedException {//创建线程对象TestThread7 testThread7 =new TestThread7("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbj-yuantu.fotomore.com%2Fcreative%2Fvcg%2Fnew%2FVCG211356080686.jpg%3FExpires%3D1643621486%26OSSAccessKeyId%3DLTAI2pb9T0vkLPEC%26Signature%3DtLJzWT8OEpGQ4MHADL%252Fhz9SebeY%253D%26x-oss-process%3Dimage%252Fauto-orient%252C0%252Fsaveexif%252C1%252Fresize%252Cm_lfit%252Ch_1200%252Cw_1200%252Climit_1%252Fsharpen%252C100%252Fquality%252CQ_80%252Fwatermark%252Cg_se%252Cx_0%252Cy_0%252Cimage_d2F0ZXIvdmNnLXdhdGVyLTIwMDAucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLG1fbGZpdCxoXzE3MSx3XzE3MSxsaW1pdF8x%252F&refer=http%3A%2F%2Fbj-yuantu.fotomore.com&app=2002&size=f10000,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1645764262&t=e5f59f659774002781376801cd533c5b","2.jpg");//创建执行服务,与线程池有关系ExecutorService ser= Executors.newFixedThreadPool(1);//提交执行Future<Boolean> r1=ser.submit(testThread7);
// 获取结果boolean rs1=r1.get();System.out.println(rs1);//关闭服务ser.shutdownNow();}}
底层原理
一.静态代理
找个房产代理,婚庆公司,都是代理.
package com.xiucai;/*** 静态代理类的实现* 真是对象和代理对象都要实现一个接口,都实现Marry.* 代理对象,要把代理对象传入.* 很形象的显示,真实对象专注做自己的事情*/
public class StaticProxy {public static void main(String[] args) {//创建我Marry marryMe =new MarryMe();//先搞出一个代理公司来,把我这个需要代理的传入Company company=new Company(marryMe);//调用的是婚庆公司的的结婚方法.company.HappyMarry();}}
interface Marry{void HappyMarry();
}
//真实角色,我结婚
class MarryMe implements Marry {@Overridepublic void HappyMarry() {System.out.println("我要结婚了");}
}
//公司代理
class Company implements Marry{//目标对象要结婚private Marry target;//构造函数,把代理对象传入.public Company(Marry target){this.target=target;}@Overridepublic void HappyMarry() {before();//调用目标对象的方法this.target.HappyMarry();after();}private void before() {System.out.println("结婚之前订车");}private void after() {System.out.println("结婚打扫卫生");}
}
二、Lamda 表达式,
为了简化代码,实现
函数式接口.任何接口只有一个抽象方法.
//1.定义一个函数式接口.
interface Ilike{void lamda();
}
2.常用的方式,
//2.实现类
class Like implements Ilike{@Overridepublic void lamda() {System.out.println("i like lamda");}
}
结果(正常输出):
3,进一步简化,写在测试类里边
//3.静态内部类,把实现类放在静态内部类中.static class Like2 implements Ilike{@Overridepublic void lamda() {System.out.println("i like lamda你静态内部类");}
}
4,局部内部类,写在main函数里边
//4.局部内部类,放在了方法里边了
class Like3 implements Ilike{@Overridepublic void lamda() {System.out.println("i like lamda局部内部类");}
}
//在进行局部内部类时,我把代码放在上边不好用,只能放在下边.like= new Like3();
like.lamda();
5,我们没有显性的去定义一个类没有类的名称必须借助接口或者父类,在main中
//5.匿名内部类,没有类的名称必须借助接口或者父类
like = new Ilike() {@Overridepublic void lamda() {System.out.println("这是匿名内部类");}};
//匿名内部类的测试.
like.lamda();
6.用lamda简化,我们实现了函数式接口 注意 Ilike like= new Like(); 在main中
like=()->{System.out.println("用lamda简化");
};
like.lamda();
以上的方法,可以避免写太多的匿名内部类
另一个实例
package com.xiucai;public class TestLamda {//
public static void main(String[] args) {Student student=(int i)->{System.out.println("lamada表达式");};student.study(1);//这就相当于student=new Student() {@Overridepublic void study(int a) {System.out.println("这是匿名内部类");}};student.study(1);}
}
interface Student{//函数式接口.任何接口只有一个抽象方法.void study(int a);
}
lamada表达式代码简化
Student student=(int i)->{System.out.println("lamada表达式");};// Student student=(int i)->{ 参数类型可以去掉,如果多个参数的时候,一去都去
// Student student=( i)->{ 一个参数的时候括号可以简化
// Student student= i->{ 当后边只有一行代码的时候花括号可以简化Student student= i->{System.out.println("lamada表达式");};student.study(1);
由于runable接口只有一个方法,所以可以叫函数式接口,进而可以使用lamada表达式
三、线程状态
线程的五种状态
观察线程状态
package com.xiucai;public class TestThreadState {public static void main(String[] args) throws InterruptedException {//此时已经把thread类 runable接口实现的new 丢进去了Thread thread=new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println("这里里边是线程的执行方法"); //执行try {Thread.sleep(100);//主动阻塞} catch (InterruptedException e) {e.printStackTrace();}}});System.out.println(thread.getState()); //输出new 创建thread.start();System.out.println(thread.getState());//输出 RUNNABLE 就绪while(thread.getState()!=Thread.State.TERMINATED){Thread.sleep(100);System.out.println(thread.getState());//TIMED_WAITING 线程内部主动阻塞}System.out.println(thread.getState()); //输出 TERMINATED}}
线程停止
package com.xiucai;public class ThreadStop implements Runnable{private boolean flag=true;@Overridepublic void run() {int i=0;while (flag){System.out.println("线程执行中"+ i++);}}//设置一个暂停方法public void stop(){this.flag=false;}public static void main(String[] args) {ThreadStop threadStop=new ThreadStop();new Thread(threadStop).start();//主线程执行,分线程也在执行.for (int i = 0; i < 100; i++) {System.out.println("main线程执行了多少次了"+i);if(i==90){//线程的停止.操作这个线程对象.threadStop.stop();System.out.println("线程该停止了");}}}
}
结果:
线程延时
:为了放大问题的发生性
每个对象都会有一把锁,sleep不会释放锁
package com.xiucai;public class ThreadSleep implements Runnable{private int ticketNum = 10;@Overridepublic void run() {while (true) {//Thread.currentThread().getName() 得到名字if (ticketNum <= 0) {break;}//模拟延时try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}//输出拿到了多少张票System.out.println(Thread.currentThread().getName() + ":拿到了,第几张票" + ticketNum--);}}public static void main(String[] args) {//小牛,小蓝,小紫ThreadSleep ticket =new ThreadSleep();//上边的 Thread.currentThread().getName() 得到名字new Thread(ticket,"小明").start();new Thread(ticket,"小牛").start();new Thread(ticket,"小紫").start();}}
模拟倒计时
public static void main(String[] args) throws InterruptedException {ThreadSleep.freetime();}
public static void freetime() throws InterruptedException {int num=10;while(true){Thread.sleep(1000);System.out.println(num--);if (num<=0){break;}}
}
打印当前时间
我再这里尝试了,不先创建一个时间对象,然后结果不太好.
//获取当前系统时间
public static void currTime() throws InterruptedException {//创建一个时间对象,显示当前的一个时间Date startTime=new Date(System.currentTimeMillis());//一直在循环输出一个时间while(true){Thread.sleep(1000);//格式化当前对象,这个应该是System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));startTime=new Date(System.currentTimeMillis());}
public static void main(String[] args) throws InterruptedException {ThreadSleep.currTime();}
线程礼让 yield()
礼让不一定成功,礼让只是让cpu中的东西出来,看cpu的心情,下图例子跟代码
package com.xiucai;public class TestYield {public static void main(String[] args) {MyYield myYield =new MyYield();new Thread(myYield,"a").start();new Thread(myYield,"b").start();}
}
class MyYield implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"线程开始执行");Thread.yield();System.out.println(Thread.currentThread().getName()+"线程停止执行");}}}
合并线程 Join
代此线程执行完成,其余的线程才可以执行.
如下分为子线程,以run操作,跟主线程,我采用子线程.join的形式进行完成了.
package com.xiucai;import java.text.SimpleDateFormat;
import java.util.Date;public class TestJosn implements Runnable{public static void main(String[] args) throws InterruptedException {MyYield myYield =new MyYield();Thread thread = new Thread(myYield,"a");thread.start();for (int i = 0; i < 10; i++) {thread.join();System.out.println("主线程"+i++);}}@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("线程开始执行");}}
}
线程的优先级
package com.xiucai;public class ThreadPriority {public static void main(String[] args) throws InterruptedException {//此时已经把thread类 runable接口实现的new 丢进去了,我用了Lam大表达式Thread thread=new Thread(()->{System.out.println("这是线程"+Thread.currentThread().getPriority());});Thread thread2=new Thread(()->{System.out.println("这是线程二"+Thread.currentThread().getPriority());});thread2.setPriority(2);thread.setPriority(1);thread.start();thread2.start();for (int i = 0; i < 10; i++) {System.out.println("这是主线程"+Thread.currentThread().getPriority());}}}
输出,
守护线程 daemon
线程分为用户线程和守护线程
jvm必须确保用户线程执行完成 (main),虚拟机不用等待守护线程执行完毕(gc)
package com.xiucai;public class ThreadDaemon {public static void main(String[] args) {Deamon deamon = new Deamon();Son son = new Son();Thread thread = new Thread(deamon);Thread thread_son = new Thread(son);thread.setDaemon(true);//true 表示是守护线程.false是用户线程thread.start();thread_son.start();}}
class Deamon implements Runnable{@Overridepublic void run() {while (true){System.out.println("守护线程在这执行");}}
}
class Son implements Runnable{@Overridepublic void run() {int i=0;while (i<100){System.out.println("用户线程在这执行");i++;}}
}
四、线程同步
并发:多个线程操作一个对象.上边的买票例子.
线程同步是一种等待机制
队列加锁才能保证线程的安全性,每个线程都有一把锁
银行不安全实例
package syn;import java.math.BigDecimal;/*** 不安全的银行取钱*/
public class Unsafeback implements Runnable{BigDecimal tallMoney =new BigDecimal("1000");/** 取了多少钱*/BigDecimal getmoney =new BigDecimal("500");//取钱在这里里边写@Overridepublic void run() {//执行取钱的指令getMonry();try {Thread.sleep(1000);//在这里加上sleep没用 输出的都是 0,0} catch (InterruptedException e) {e.printStackTrace();}}/**取钱的方法*/public boolean getMonry(){// -1小于,0相等,1大于 没钱了取钱失败if (tallMoney.compareTo(BigDecimal.ZERO)==-1){return false;}//有钱输出取完了的钱剩下多少钱tallMoney= tallMoney.subtract(getmoney);System.out.println(Thread.currentThread().getName()+"剩下多少钱"+tallMoney);return true;}//谁去取钱public static void main(String[] args) throws InterruptedException {Unsafeback account=new Unsafeback();Thread me= new Thread(account,"我");Thread wife= new Thread(account,"对象");me.start();Thread.sleep(1000);//在这里加上sleep就变成类似于单线程了wife.start();}
}
List不安全实例
经过测试list跟ArrayList都是线程不安全的.Hashtabale以及Vector是线程安全的,这也是面试中常考的
package syn;import java.util.ArrayList;
import java.util.List;/**
List数组是线程不安全的实例*/
public class ThreadListy {public static void main(String[] args) {List<String> lists = new ArrayList<>();//启动1000个线程,往数组里边放对象for (int i = 0; i < 1000; i++) {// 采用lanmada表达式Thread thread=new Thread(()->{lists.add(Thread.currentThread().getName());});thread.start();}//查看到底放进去了多少个对象System.out.println("数组大小"+ lists.size());}
}
执行结果预期是1000,实际上是977,这说明有一部分位置被覆盖掉了
同步方法与同步块 synchronized
牢牢把握,synchronized解决的是变化的量,是需要增删改查的对象。
在解决单对象问题的时候,比如买票问题,买票问题本身操作的就是票的数量
这里针对于上边的代码的银行不安全实例,狂神的实例中,写了account对象作为银行账户,但是对于我上边的代码,可以明显的,基本上与买票问题很相似.所以说我还是在本身买票的方法上加了, 关键字实现了效果.
最后通过List不安全的例子来看优化方法,首先确定我们操作的就是List数组对象,具体来说是lists对象.那就用synchronized 代码块来实现本实例
出现了问题,在for循环启动完线程之后,实际上还有一些线程没有执行完成,在一直往里边存数,尽管加了锁,但是与主线程里边的输出数组大小这个程序冲突,所以对于主线程看数组大小的命令,延时一段时间之后会变好.
JUC 并发 里边的安全类型 CopyOnWriteArrayList
读取操作没有任何同步控制和锁操作,理由就是内部数组 array 不会发生修改,只会被另外一个 array 替换,因此可以保证数据安全,而且也是线程安全的。
CopyOnWriteArrayList 写入操作
add()
方法在添加集合的时候加了锁,保证同步,避免多线程写的时候会 copy 出多个副本。
package com.xiucai;
/*** 线程实例-JUC并发编程*/import java.util.concurrent.CopyOnWriteArrayList;public class TestJUC {public static void main(String[] args) throws InterruptedException {CopyOnWriteArrayList copyOnWriteArrayList=new CopyOnWriteArrayList();for (int i = 0; i < 1000; i++) {new Thread(()->{copyOnWriteArrayList.add(Thread.currentThread().getName());}).start();}Thread.sleep(100);System.out.println(copyOnWriteArrayList.size());}
}
执行结果
死锁
相互需要对方的资源,但这两个线程都在执行,谁都不愿意给谁,形成僵持
产生死锁的四个必要条件
- 互斥条件:一个资源只能一个进程使用,我用口红,你也想用口红
- 请求与保持的条件:我想过去拿你的口红,镜子我还不想给你
- 不剥夺条件:进程获得的资源,未使用完成之前,不能强行剥夺
- 循环等待条件:若干进程行成一种循环等待资源关系
- 出现死锁,条件,互斥条件
解释一下,两个资源 镜子跟口红,小美用着口红,小红用着镜子.在锁内写了一个锁,造成了手里抱着一个锁,还想再去搞个锁.
写了两个类代替口红跟镜子,写了化妆的方法,来具体实现锁,然后启动了两个线程
package syn;public class DeadLock implements Runnable{static Rouge rouge = new Rouge();static Mirror mirror = new Mirror();/*** 执行线程*/@Overridepublic void run() {makeup();}//化妆方法执行private void makeup(){if (Thread.currentThread().getName().equals("小美")){synchronized (rouge){System.out.println(Thread.currentThread().getName()+"在用口红想要镜子");//锁内写锁synchronized (mirror){System.out.println(Thread.currentThread().getName()+"我还想要镜子");}}//小红想怎么搞}else{synchronized (mirror){System.out.println(Thread.currentThread().getName()+"在用镜子想要口红");//锁内写锁synchronized (rouge){System.out.println(Thread.currentThread().getName()+"我还想要口红");}}}}public static void main(String[] args) {DeadLock deadLock=new DeadLock();new Thread(deadLock,"小美").start();new Thread(deadLock,"小红").start();}
}
/** 资源一:口红*/
class Rouge{}
/**资源二:镜子*/
class Mirror{}
- 解决死锁,锁外加锁
package syn;public class DeadLock implements Runnable {static Rouge rouge = new Rouge();static Mirror mirror = new Mirror();/*** 执行线程*/@Overridepublic void run() {makeup();}//化妆方法执行private void makeup() {if (Thread.currentThread().getName().equals("小美")) {synchronized (rouge) {System.out.println(Thread.currentThread().getName() + "在用口红想要镜子");//锁内写锁
// synchronized (mirror){// System.out.println(Thread.currentThread().getName()+"我还想要镜子");}//变成锁外写锁synchronized (mirror) {System.out.println(Thread.currentThread().getName() + "我还想要镜子");}//小红想怎么搞} else {synchronized (mirror) {System.out.println(Thread.currentThread().getName() + "在用镜子想要口红");//锁内写锁
// synchronized (rouge){// System.out.println(Thread.currentThread().getName()+"我还想要口红");
// }}//变成锁外写锁synchronized (rouge) {System.out.println(Thread.currentThread().getName() + "我还想要口红");}}}public static void main(String[] args) {DeadLock deadLock = new DeadLock();new Thread(deadLock, "小美").start();new Thread(deadLock, "小红").start();}
}/*** 资源一:口红*/class Rouge {}/*** 资源二:镜子*/class Mirror {}
以上的例子正好跟上边的银行买票不安全实例对应一下代码块跟代码关键字的区别.
面试题:多线程中 synchronized 锁升级的原理是什么
synchronized 锁升级原理:在锁对象的对象头⾥⾯有⼀个 threadid 字段,在第⼀次访问的时候 threadid
为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进⼊的时候会先判断 threadid 是否与其
线程 id ⼀致,如果⼀致则可以直接使⽤此对象,如果不⼀致,则升级偏向锁为轻量级锁,通过⾃旋循环⼀
定次数来获取锁,执⾏⼀定次数之后,如果还没有正常获取到要使⽤的对象,此时就会把锁从轻量级升级为
重量级锁,此过程就构成了 synchronized 锁的升级。
面试题:怎么防⽌死锁?
尽量使⽤ tryLock(long timeout, TimeUnit unit)的⽅法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防⽌死锁。
尽量使⽤ java.util.concurrent 并发类代替⾃⼰⼿写锁,在上边JUC并发类,就能看到。
尽量降低锁的使⽤粒度,尽量不要⼏个功能⽤同⼀把锁。
尽量减少同步的代码块。
面试题: synchronized 和 volatile 的区别是什么?
volatile 是变量修饰符;synchronized 是修饰类、⽅法、代码段。
volatile 仅能实现变量的修改可⻅性,不能保证原⼦性;⽽ synchronized 则可以保证变量的修改可⻅
性和原⼦性。
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
面试题 static的作用
static修饰的变量,在类加载时会被分配到数据区的方法区。
static修饰的方法中不能使用this或super,static修饰的方法属于类的方法,而this或super只是对象的方法。
面试: synchronized和volatile的区别?
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性
LOCK锁
出现了一个问题,我在复制测试lock锁的时候,把忘记更改,直接用了这个类实现了Runnable接口,实际上应该写成
TestLock buyTicket = new TestLock();
也进一步说明了,线程对象会去调用,线程对象本身的方法。
package com.xiucai;import java.util.concurrent.locks.ReentrantLock;/*** 测试unlock 与lock*/
public class TestLock implements Runnable {//票数,注意这个可以是参数通过注入更改可以传一下private int tickets=10;/**1.实现ReentrantLock对象 */ReentrantLock lock= new ReentrantLock();//线程执行内容,即循环买票@Overridepublic void run() {boolean buy;while(true) {try {//2.这里加锁,锁增删改查的对象lock.lock();buy = buyTicket2();if (buy==false){break;}Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}finally {//3.这里解锁lock.unlock();}}}//买票的方法public boolean buyTicket2(){//如果没票,返回失败while(tickets<=0){return false;}//lock.lock(); 这里System.out.println(Thread.currentThread().getName()+"买票成功"+tickets--);//lock.unlock();return true;}public static void main(String[] args) {TestLock buyTicket = new TestLock();new Thread(buyTicket,"A").start();new Thread(buyTicket,"B").start();new Thread(buyTicket,"C").start();}
}
五、线程协作
生产者消费者模式(管程法:利用缓冲区解决问题)
Object类下边的几个需要用的方法
- wait() :表示线程等待,直到其他线程通知,它不会释放锁,sleep抱着锁
- wait():指定等待的毫秒数
- notify():唤醒一个处于等待的线程
- notifyAll:欢迎一个对象上所有调用wait方法的线程
package Cooperate;import java.util.function.Consumer;/*** 这是一个测试多线程的类*/
public class TestCooperate {public static void main(String[] args) {Container container=new Container();new Producutor(container).start();new Customer(container).start();}}
/**生产者,只需要管生产即可*/
class Producutor extends Thread{Container container=new Container();Producutor(Container container){this.container=container;}@Overridepublic void run() {for (int i = 0; i < 100; i++) {try {container.push(new Chicken());System.out.println("生产的第"+i);} catch (InterruptedException e) {e.printStackTrace();}}}
}
/*
消费者*/
class Customer extends Thread{Container container=new Container();Customer(Container container){this.container=container;}@Overridepublic void run() {for (int i = 0; i < 100; i++) {try {container.pop(new Chicken());System.out.println("消费的第"+i);} catch (InterruptedException e) {e.printStackTrace();}}}}
//模拟产品,鸡
class Chicken{ }//中间的容器,看成一个对象,欢迎一个对象上所有调用wait方法的线程
class Container {// 需要一个容器大小Chicken[] chickens=new Chicken[10];
//容器计数器int count=0;//生产者放入产品public synchronized void push(Chicken chicken) throws InterruptedException {//如果容器不满的情况下可以放入产品if (count== chickens.length){//如果不能放,当前线程给休息起来this.wait();}chickens[count++]=chicken;//可以通知消费者了this.notifyAll();}// 消费者拿出商品public synchronized Chicken pop(Chicken chicken) throws InterruptedException {//如果容器不空的情况下可以消费产品if (count==0){//此时让消费者等待,生产者起this.wait();return chicken;}chicken= chickens[--count];//吃完了通知消费者this.notifyAll();return chicken;}}
生产者消费者模式(设置标志位的解决方式)
消费者和生产者是两个线程
package Cooperate2;/*** 将利用缓冲区解决问题,变成设立标志位解决问题*/
public class TestCooperate {public static void main(String[] args) {Container container=new Container();new Producutor(container).start();new Customer(container).start();}}/**生产者,只需要管生产即可*/
class Producutor extends Thread{Container container=new Container();Producutor(Container container){this.container=container;}@Overridepublic void run() {//在线程里一共生产是10只往里边放for (int i = 0; i < 10; i++) {try {container.push();System.out.println("这是在往里边放鸡,放的第"+i+"只鸡");} catch (InterruptedException e) {e.printStackTrace();}}}}/*
消费者*/
class Customer extends Thread{Container container=new Container();Customer(Container container){this.container=container;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {try {container.pop();System.out.println("这是在往里边吃鸡,吃的第"+i+"只鸡");} catch (InterruptedException e) {e.printStackTrace();}}}}//模拟产品,鸡
class Chicken{ }//中间的容器,看成一个对象,欢迎一个对象上所有调用wait方法的线程
class Container {//设置标志位,当T为生产,F为消费boolean flag=true;//生产者放入产品public synchronized void push() throws InterruptedException {//标志位为F的时候为消费,此时不能生产,所以用wait让其等等if (flag==false){//如果不能放,当前线程给休息起来this.wait();}//如果为ture//可以通知消费者了this.notifyAll();flag=!flag;}// 消费者拿出商品public synchronized void pop() throws InterruptedException {//如果容器不空的情况下可以消费产品if (flag==true){//此时让消费者等待,生产者起this.wait();}//吃完了通知生产者this.notifyAll();flag=!flag;}}
面试题:sleep() 和 wait() 有什么区别?
类的不同:sleep() 来⾃ Thread,wait() 来⾃ Object
释放锁:sleep() 不释放锁;wait() 释放锁
⽤法不同:sleep() 时间到会⾃动恢复;wait() 可以使⽤ notify()/notifyAll()直接唤醒。
线程池
可以避免创建销毁,实现重复利用,便于线程销毁跟创建
线程池
package Cooperate;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPool {public static void main(String[] args) {//1.创建服务,创建线程池 ,参数为线程池的大小ExecutorService service= Executors.newFixedThreadPool(10);//2.启动线程service.execute(new MyThread() );service.execute(new MyThread() );service.execute(new MyThread() );service.execute(new MyThread() );//3.关闭连接service.shutdown();}
}
class MyThread implements Runnable{@Overridepublic void run() {System.out.println("线程:"+Thread.currentThread().getName());}}
与callable之间的对比
面试题 说⼀下 runnable 和 callable 有什么区别?
runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。
面试题:创建Thread得几种方式
- newSingleThreadExecutor():它的特点在于⼯作线程数⽬被限制为 1,操作⼀个⽆界的⼯作队列,所
以它保证了所有任务的都是被顺序执⾏,最多会有⼀个任务处于活动状态,并且不允许使⽤者改动线
程池实例,因此可以避免其改变线程数⽬; - newCachedThreadPool():它是⼀种⽤来处理⼤量短时间⼯作任务的线程池,具有⼏个鲜明特点:它
会试图缓存线程并重⽤,当⽆缓存线程可⽤时,就会创建新的⼯作线程;如果线程闲置的时间超过 60 秒,则被终⽌并移出缓存;⻓时间闲置时,这种线程池,不会消耗什么资源。其内部使⽤
SynchronousQueue 作为⼯作队列; - newFixedThreadPool(int nThreads):重⽤指定数⽬(nThreads)的线程,其背后使⽤的是⽆界的⼯
作队列,任何时候最多有 nThreads 个⼯作线程是活动的。这意味着,如果任务数量超过了活动队列
数⽬,将在⼯作队列中等待空闲线程出现;如果有⼯作线程退出,将会有新的⼯作线程被创建,以补
⾜指定的数⽬ nThreads; - newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进
⾏定时或周期性的⼯作调度; - newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建
的是个 ScheduledExecutorService,可以进⾏定时或周期性的⼯作调度,区别在于单⼀⼯作线程还是
多个⼯作线程; - newWorkStealingPool(int parallelism):这是⼀个经常被⼈忽略的线程池,Java 8 才加⼊这个创建⽅
法,其内部会构建ForkJoinPool,利⽤Work-Stealing算法,并⾏地处理任务,不保证处理顺序;
ThreadPoolExecutor():是最原始的线程池创建,上⾯1-3创建⽅式都是对ThreadPoolExecutor的封
装。
面试题 线程池中 submit()和 execute()⽅法有什么区别?
execute():只能执⾏ Runnable 类型的任务。
submit():可以执⾏ Runnable 和 Callable 类型的任务。
潜入浅出,java多线程到底是个什么东东?面试中应该注意哪方面多线程的知识?相关推荐
- spring boot 潜入浅出
前言 今天的博客想写点spring boot,最近身边的朋友都再问这个相关的问题.所以我就简单的做了一个spring boot 框架的小demo. 我还在纠结要写到什么深度,考虑再三感觉写点最最基础浅 ...
- 潜入浅出--通信中的频带利用率,以MASK.MPSK作为例子
前言:通信原理中的频带率利用率大家肯定都有所了解,因为我读研也是学的通信,最近被这东西搞混淆了一阵子,这几天亲手推导了几下,因为本科的时候老师都是大致的说一下2进制的,也没说什么物理意义和多进制的时候 ...
- 【Java知识点详解 10,如何在面试中通过工厂模式来给自己加分
(1)设置参数 path环境变量的作用就是告诉系统,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到哪些目录下去寻找.而我们通常情况下配置的path变 ...
- 学java用孙悟空_悟空老师会出Java实战课麽?期待...
Hi小伙伴你好,Java并发实战课将于8月12日上线,章节包括: 1. [实现多线程的方式到底是1种还是2种还是4种?]:本质只有一种方式,实现执行内容有 2种方式,而包装后的外在表现形式有多种形式. ...
- 浅入浅出Oracle Spatial GeoRaster 10g影像数据管理(2)
浅入浅出Oracle Spatial GeoRaster 10g 影像数据管理(2)--物理存储 1.物理存储方式概要 在上个部分<浅入浅出Oracle Spatial GeoRas ...
- java 多线程同步_浅谈Java多线程(状态、同步等)
Java多线程是Java程序员必须掌握的基本的知识点,这块知识点比较复杂,知识点也比较多,今天我们一一来聊下Java多线程,系统的整理下这部分内容. 一.Java中线程创建的三种方式: 1.通过继承T ...
- 浅谈Java内存模型、并发、多线程
浅谈Java内存模型.并发.多线程 Java内存模型(Java Memory Model)是围绕着在并发编程中如何处理原子性,可见性,有序性三个特性而建立的模型. 下面我简单描述一下这三个特性: 原子 ...
- 浅谈Java多线程机制
浅谈Java多线程机制 (-----文中重点信息将用红色字体凸显-----) 一.话题导入 在开始简述Java多线程机制之前,我不得不吐槽一下我国糟糕的IT界技术分享氛围和不给力的互联网技术解答深度. ...
- 浅谈Java 多线程同步
最近在研究多线程同步的一些问题,整理了网上很多文档,在这里给大家分享下 Java并发机制的底层实现原理 Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终 ...
最新文章
- JBPM深入解析之变量设计
- 根据map中某一字段排序
- Heartbeat+ipvsadm+ldirectord组建linux高可用集群
- .NET Core开发实战(第25课:路由与终结点:如何规划好你的Web API)--学习笔记(下)...
- [Go] Template 使用简介
- 那些在家啃书自学算法的人,最后都找到工作了吗?
- VOC2007xml转YOLO的txt格式代码
- python程序运行键_python实现按任意键继续执行程序
- 教你轻松将仅能在线阅读的PDF文件下载到本地(小白也能学会)
- KELL中程序封装的实现
- 微信小程序云开发答题小程序源代码(实现在线pk)
- 菜鸟历程1腾讯云服务器 10元学生套餐购买
- (二) CGAL库应用:轮廓中轴骨架生成create_interior_straight_skeleton_2()及轮廓的偏置create_offset_polygons_2()
- vue首次赋值不触发watch及watch和computed的区别
- 企业信息化:体系比软件更重要
- 使用U盘安装 mac os
- m35c android 4.4,索尼m35c刷机的方法
- 碧桂园香港上市 25岁杨惠妍512亿身家变身首富
- SparkSQL内置函数
- java 网络编程详细解析
热门文章
- 34%的人会出轨。。。
- GAMES101笔记_Lec01_计算机图形学概述 Overview of Computer Graphics
- PaddleNLP_基于seq2seq的对联生成
- Linux错误:bash:finger 未找到命令...
- 0.91英寸和0.96英寸OLED驱动区别
- 读书笔记-数据库系统概念-chapter3SQL
- java 调试sql server_sql server 如何调试存储过程
- java语言使用栈和队列实现简易停车场管理系统
- 使用具有高性能事件流的数据库:关键注意事项
- 让你终生受用的时间管理技巧