为了近期的面试,准备多线程的学习,这一部分十分重要,在我近期的面试中,问的十分多.尤其是创建线程三种方式,线程池的创建.
线程简介
主线程走主线程,子线程走子线程,main线程,gc线程(也可以称之为守护线程). 线程不能人为干预,可能会出现资源抢夺的问题,所以需要加上并发的控制.线程会带来额外的开销,比如cpu的调度时间
面试题:线程和进程的区别?
?个程序下?少有?个进程,?个进程下?少有?个线程,?个进程下也可以有多个线程来增加程序的执? 速度。举个例子,进程:包含视频,声音,弹幕,线程就是分别去执行视频,弹幕,线程。在这里可能会有并发跟并行的概念,并行,是多个cup同时工作,并发是一个cpu按照时间片的轮转,执行,尽管我们看着是同时进行的,但最终不是.
线程的创建
面试题:创建线程有哪?种?式?
创建线程有三种?式: ★继承 Thread 重写 run ?法; ★实现 Runnable 接?; 实现 Callable 接?。
一.继承 Thread 重写 run ?法;
因为java的单继承,所以不推荐使用.
入门实例
package com.xiucai;
public class TestThread extends Thread{
@Override
public void run() {
super.run();
for (int i = 0; i < 20; i++) {
System.out.println("run方法执行的此时"+i);
}
}
public static void main(String[] args) {
TestThread testThread=new TestThread();
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){
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;
}
@Override
public 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;
public class TestThread3 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run方法执行的此时"+i);
}
}
public static void main(String[] args) {
TestThread3 testThread3=new TestThread3();
Thread thread=new Thread(testThread3);
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程此时执行"+i);
}
}
}
实现上边run的方法那边,这里值得深究
package com.xiucai;
public class TestThread3 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run方法执行的此时"+i);
}
}
public static void main(String[] args) {
TestThread3 testThread3=new TestThread3();
Thread thread=new Thread(testThread3);
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程此时执行"+i);
}
}
}
实例:龟兔赛跑
package com.xiucai;
public class TestThread6 implements Runnable{
private static String winner;
@Override
public void run() {
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+"步");
}
}
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;
}
public static void main(String[] args) {
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;
@Override
public void run() {
while (true) {
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();
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.*;
public class TestThread7 implements Callable {
private String url;
private String name;
public TestThread7(String url,String name){
this.url=url;
this.name=name;
}
@Override
public 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;
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 {
@Override
public void HappyMarry() {
System.out.println("我要结婚了");
}
}
class Company implements Marry{
private Marry target;
public Company(Marry target){
this.target=target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before() {
System.out.println("结婚之前订车");
}
private void after() {
System.out.println("结婚打扫卫生");
}
}
二、Lamda 表达式,
为了简化代码,实现
函数式接口.任何接口只有一个抽象方法.
interface Ilike{
void lamda();
}
2.常用的方式,
class Like implements Ilike{
@Override
public void lamda() {
System.out.println("i like lamda");
}
}
结果(正常输出):
3,进一步简化,写在测试类里边
static class Like2 implements Ilike{
@Override
public void lamda() {
System.out.println("i like lamda你静态内部类");
}
}
4,局部内部类,写在main函数里边
class Like3 implements Ilike{
@Override
public void lamda() {
System.out.println("i like lamda局部内部类");
}
}
like= new Like3();
like.lamda();
5,我们没有显性的去定义一个类没有类的名称必须借助接口或者父类,在main中
like = new Ilike() {
@Override
public 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() {
@Override
public 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= 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 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());
thread.start();
System.out.println(thread.getState());
while(thread.getState()!=Thread.State.TERMINATED){
Thread.sleep(100);
System.out.println(thread.getState());
}
System.out.println(thread.getState());
}
}
线程停止
package com.xiucai;
public class ThreadStop implements Runnable{
private boolean flag=true;
@Override
public 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;
@Override
public void run() {
while (true) {
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();
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{
@Override
public 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++);
}
}
@Override
public 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 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);
thread.start();
thread_son.start();
}
}
class Deamon implements Runnable{
@Override
public void run() {
while (true){
System.out.println("守护线程在这执行");
}
}
}
class Son implements Runnable{
@Override
public 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");
@Override
public void run() {
getMonry();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public boolean getMonry(){
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);
wife.start();
}
}
List不安全实例
经过测试list跟ArrayList都是线程不安全的.Hashtabale以及Vector是线程安全的,这也是面试中常考的
package syn;
import java.util.ArrayList;
import java.util.List;
public class ThreadListy {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
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;
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();
@Override
public 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();
@Override
public 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 {
}
以上的例子正好跟上边的银行买票不安全实例对应一下代码块跟代码关键字的区别.
面试题:多线程中 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;
public class TestLock implements Runnable {
private int tickets=10;
ReentrantLock lock= new ReentrantLock();
@Override
public void run() {
boolean buy;
while(true) {
try {
lock.lock();
buy = buyTicket2();
if (buy==false){
break;
}
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public boolean buyTicket2(){
while(tickets<=0){
return false;
}
System.out.println(Thread.currentThread().getName()+"买票成功"+tickets--);
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;
}
@Override
public 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;
}
@Override
public 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{ }
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;
}
@Override
public void run() {
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;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
container.pop();
System.out.println("这是在往里边吃鸡,吃的第"+i+"只鸡");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Chicken{ }
class Container {
boolean flag=true;
public synchronized void push() throws InterruptedException {
if (flag==false){
this.wait();
}
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) {
ExecutorService service= Executors.newFixedThreadPool(10);
service.execute(new MyThread() );
service.execute(new MyThread() );
service.execute(new MyThread() );
service.execute(new MyThread() );
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public 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 类型的任务。
|