本篇文章为你整理了Java多线程详解(通俗易懂)(java多线程有几种实现方法实战)的详细内容,包含有java多线程基础知识 java多线程有几种实现方法实战 java多线程使用方法 java多线程简单实例 Java多线程详解(通俗易懂),希望能帮助你了解 Java多线程详解(通俗易懂)。
1. 什么是进程?
电脑中会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。例如图中的微信、酷狗音乐、电脑管家等等。
2. 什么是线程?
进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。
补充:Java中有默认有几个线程?两个:main、gc
那什么又是多线程呢?
提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。
了解完这两个概念之后,我们再来说什么是多线程,举个例子,比如我们打开联想电脑管家,电脑管家本身是一个程序,也可以说就是一个进程,它里面包括很多功能,电脑加速、安全防护、空间清理等等功能,如果对于单线程来说,无论我们想要电脑加速,还是空间清理,那么必须得一件事一件事的做,做完其中一件事再做下一件事,有一个执行顺序。但如果是多线程的话,我们可以在清理垃圾的同时进行电脑加速,还可以病毒查杀等等其他操作,这个就是在严格意义上的同一时刻发生的,没有先后顺序。
二、线程的创建
Java 提供了三种创建线程的方法:
通过继承 Thread 类本身。(重点)
通过实现 Runnable 接口。(重点)
通过 Callable 和 Future 创建线程。(了解)
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名:"+name);
public static void main(String[] args) {
ImageDownload d1 = new ImageDownload("https://cn.bing.com/images/search?view=detailV2 ccid=64mezA1F id=0567ED050842B109CEFE6D7C2E235E6513915D00 thid=OIP.64mezA1F6eYavcDWrgjHQgHaEK mediaurl=https%3a%2f%2fimages.hdqwalls.com%2fwallpapers%2fcute-kitten-4k-im.jpg exph=2160 expw=3840 q=Cat+Wallpaper+4K simid=608031326407324483 FORM=IRPRST ck=5E947A96CD5B48E39B116D48F58466AB selectedIndex=12 ajaxhist=0 ajaxserp=0", "cat1.jpg");
ImageDownload d2 = new ImageDownload("https://cn.bing.com/images/search?view=detailV2 ccid=qXtg4Nx0 id=A80C30163A6B55D16D61F27E632239424517705F thid=OIP.qXtg4Nx0BUoeUP53fz_HKgHaFI mediaurl=https%3a%2f%2fimages8.alphacoders.com%2f856%2f856433.jpg exph=2658 expw=3840 q=Cat+Wallpaper+4K simid=608046255722156270 FORM=IRPRST ck=986D5F99CF8474477F4A1F2DB2850C9D selectedIndex=25 ajaxhist=0 ajaxserp=0", "cat2.jpg");
ImageDownload d3 = new ImageDownload("https://cn.bing.com/images/search?view=detailV2 ccid=kvYsfUHA id=6311D8D1DC87AA4B69783A97020038B03827134D thid=OIP.kvYsfUHAAQlEVW3Z3_EEWwHaEK mediaurl=https%3a%2f%2fwallpapershome.com%2fimages%2fpages%2fpic_h%2f19418.jpg exph=1080 expw=1920 q=Cat+Wallpaper+4K simid=608016886736366855 FORM=IRPRST ck=37C2818B80D19766E7A91B5BB7A060D6 selectedIndex=159 ajaxhist=0 ajaxserp=0", "cat3.jpg");
d1.start();
d2.start();
d3.start();
//每次执行结果有可能不一样,再次证明线程之间是由cpu调度执行
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题!");
2. 实现Runnable接口
* @ClassName RunnableDemo
* @Description TODO 线程创建的第二种方式:实现Runnable接口 (推荐使用)
* @Author ZhangHao
* @Date 2022/12/10 15:07
* @Version: 1.0
//模拟抢火车票
public class RunnableDemo implements Runnable{
//票数
private int ticketNums = 10;
@Override
public void run() {
while (ticketNums 0){
try {
//让线程睡眠一会
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"-- 抢到了第"+ticketNums--+"张票");
public static void main(String[] args) {
//创建runnable接口的实现类对象
RunnableDemo demo = new RunnableDemo();
//创建线程对象,通过线程对象开启线程(使用的代理模式)
//Thread thread = new Thread(demo,"老王");
//thread.start();
//简写:new Thread(demo).start();
new Thread(demo,"老王").start();
new Thread(demo,"小张").start();
new Thread(demo,"黄牛党").start();
//发现问题:多个线程操作同一个资源时,线程不安全,数据紊乱。(线程并发)
再来一个小小的案例:模拟《龟兔赛跑》首先得有一个赛道,兔子天生跑得快,但是兔子跑一段路就偷懒睡觉,乌龟在不停的跑,最终乌龟取得胜利!
/**
* @ClassName Race
* @Description TODO 模拟龟兔赛跑
* @Author ZhangHao
* @Date 2022/12/11 9:25
* @Version: 1.0
public class Race implements Runnable {
private static String winner;//胜利者
@Override
public void run() {
//设置赛道
for (int i = 1; i = 100; i++) {
//让兔子跑得快一点
if (Thread.currentThread().getName().equals("兔子")) {
i += 4;
System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
if (i % 4 == 0) {
try {
//模拟兔子跑一段路就睡觉
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} else {
System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
//判断游戏是否结束
boolean flag = gameOver(i);
if (flag) {
break;
//判断游戏是否结束
private boolean gameOver(int steps) {
if (winner != null) {
return true;
if (steps == 100) {
winner = Thread.currentThread().getName();
System.out.println("Winner is " + winner);
return true;
return false;
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "乌龟").start();
new Thread(race, "兔子").start();
3. 实现Callable接口
创建执行服务:ExecutorService es = Executors.newFixedThreadPool(1);
提交执行:Future r1 = es.submit(d1);
获取结果:Boolean res1 = r1.get();
关闭服务:es.shutdownNow();
/**
* @ClassName CallableDemo
* @Description TODO 线程创建的第三种方式:实现Callable接口(了解即可)
* @Author ZhangHao
* @Date 2022/12/11 10:24
* @Version: 1.0
public class CallableDemo implements Callable Boolean {
private String url;//网图下载地址
private String name;//网图名称
public CallableDemo(String url, String name) {
this.url = url;
this.name = name;
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载了文件名:" + name);
return true;
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo d1 = new CallableDemo("https://cn.bing.com/images/search?view=detailV2 ccid=64mezA1F id=0567ED050842B109CEFE6D7C2E235E6513915D00 thid=OIP.64mezA1F6eYavcDWrgjHQgHaEK mediaurl=https%3a%2f%2fimages.hdqwalls.com%2fwallpapers%2fcute-kitten-4k-im.jpg exph=2160 expw=3840 q=Cat+Wallpaper+4K simid=608031326407324483 FORM=IRPRST ck=5E947A96CD5B48E39B116D48F58466AB selectedIndex=12 ajaxhist=0 ajaxserp=0", "cat1.jpg");
CallableDemo d2 = new CallableDemo("https://cn.bing.com/images/search?view=detailV2 ccid=qXtg4Nx0 id=A80C30163A6B55D16D61F27E632239424517705F thid=OIP.qXtg4Nx0BUoeUP53fz_HKgHaFI mediaurl=https%3a%2f%2fimages8.alphacoders.com%2f856%2f856433.jpg exph=2658 expw=3840 q=Cat+Wallpaper+4K simid=608046255722156270 FORM=IRPRST ck=986D5F99CF8474477F4A1F2DB2850C9D selectedIndex=25 ajaxhist=0 ajaxserp=0", "cat2.jpg");
CallableDemo d3 = new CallableDemo("https://cn.bing.com/images/search?view=detailV2 ccid=kvYsfUHA id=6311D8D1DC87AA4B69783A97020038B03827134D thid=OIP.kvYsfUHAAQlEVW3Z3_EEWwHaEK mediaurl=https%3a%2f%2fwallpapershome.com%2fimages%2fpages%2fpic_h%2f19418.jpg exph=1080 expw=1920 q=Cat+Wallpaper+4K simid=608016886736366855 FORM=IRPRST ck=37C2818B80D19766E7A91B5BB7A060D6 selectedIndex=159 ajaxhist=0 ajaxserp=0", "cat3.jpg");
//创建执行任务
ExecutorService es = Executors.newFixedThreadPool(3);
//提交执行
Future Boolean r1 = es.submit(d1);
Future Boolean r2 = es.submit(d2);
Future Boolean r3 = es.submit(d3);
//获取结果
Boolean res1 = r1.get();
Boolean res2 = r2.get();
Boolean res3 = r3.get();
System.out.println(res1);//打印结果
System.out.println(res2);
System.out.println(res3);
//关闭服务
es.shutdownNow();
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题!");
小结
启动线程:传入目标对象+Thread对象.start()
推荐使用:避免单继承局限性,灵活方面,方便同一个对象被多个线程使用
代理模式在我们生活中很常见,比如我们购物,可以从生产工厂直接进行购物,但是在生活中往往不是这样,一般都是厂家委托给超市进行销售,而我们不直接跟厂家进行关联,这其中就引用了静态代理的思想,厂家相当于真实角色,超市相当于代理角色,我们则是目标角色。代理角色的作用其实就是,帮助真实角色完成一些事情,在真实角色业务的前提下,还可以增加其他的业务。AOP切面编程就是运用到了这一思想。
写一个小小的案例,通过婚庆公司,来实现静态代理。
/**
* @ClassName StaticProxy
* @Description TODO 静态代理(模拟婚庆公司实现)
* @Author ZhangHao
* @Date 2022/12/11 11:38
* @Version: 1.0
public class StaticProxy {
public static void main(String[] args) {
Marry marry = new WeddingCompany(new You());
marry.happyMarry();
//注意:真实对象和代理对象要实现同一个接口
interface Marry{
//定义一个结婚的接口
void happyMarry();
//你(真实角色)
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("张三结婚了!");
//婚庆公司(代理角色)
class WeddingCompany implements Marry{
//引入真实角色
private Marry target;
public WeddingCompany(Marry target){
this.target = target;
@Override
public void happyMarry() {
//在结婚前后增加业务
before();
target.happyMarry();
after();
private void before(){
System.out.println("结婚之前:布置婚礼现场");
private void after(){
System.out.println("结婚之后:收尾工作");
Thread底层就使用的静态代理模式,源码分析
//Thread类实现了Runnable接口
public class Thread implements Runnable{
//引入了真实对象
private Runnable target;
//代理对象中的构造器
public Thread(Runnable target, String name) {
init(null, target, name, 0);
当我们开启一个线程,其实就是定义了一个真实角色实现了Runnable接口,重写了run方法。
public void TestRunnable{
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread,"张三").start();
//Thread就是代理角色,myThread就是真实角色,start()就是实现方法
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("我是子线程,同时是真实角色");
动态代理
前面使用到了静态代理,代理类是自己手工实现的,自己创建了java类表示代理类,同时要代理的目标类也是确定的,如果当目标类增多时,代理类也需要成倍的增加,代理类的数量过多,当接口中的方法改变或者修改时,会影响实现类,厂家类,代理都需要修改,于是乎就有了jdk动态代理。
动态代理的好处:
代理类数量减少
修改接口中的方法不影响代理类
实现解耦合,让业务功能和日志、事务和非事务功能分离
实现步骤:
创建接口,定义目标类要完成功能。
创建目标类实现接口。
创建InvocationHandler接口实现类,在invoke()方法中完成代理类的功能。
使用Proxy类的静态方法,创建代理对象,并且将返回值转换为接口类型。
/**
* @ClassName DynamicProxy
* @Description TODO 动态代理
* @Author ZhangHao
* @Date 2022/12/11 15:11
* @Version: 1.0
public class DynamicProxy {
public static void main(String[] args) {
//创建目标对象
Marry target = new You();
//创建InvocationHandler对象
MyInvocationHandler handler = new MyInvocationHandler(target);
//创建代理对象
Marry proxy = (Marry)handler.getProxy();
//通过代理执行方法,会调用handle中的invoke()方法
proxy.happyMarry();
//创建结婚接口
interface Marry{
void happyMarry();
//目标类实现结婚接口
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("张三结婚了!");
//创建工具类,即方法增强的功能
class ServiceTools{
public static void before(){
System.out.println("结婚之前:布置婚礼现场");
public static void after(){
System.out.println("结婚之后:清理结婚现场");
//创建InvocationHandler的实现类
class MyInvocationHandler implements InvocationHandler{
//目标对象
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
//通过代理对象执行方法时,会调用invoke()方法
* @Param [proxy:jdk创建的代理类的实例]
* @Param [method:目标类中被代理方法]
* @Param [args:目标类中方法的参数]
* @return java.lang.Object
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//增强功能
ServiceTools.before();
//执行目标类中的方法
Object obj = null;
obj = method.invoke(target,args);
ServiceTools.after();
return obj;
//通过Proxy类创建代理对象(自己手写的嗷)
* @Param [ClassLoader loader:类加载器,负责向内存中加载对象的,使用反射获取对象的ClassLoader]
* @Param [Class ? [] interfaces: 接口, 目标对象实现的接口,也是反射获取的。]
* @Param [InvocationHandler h: 我们自己写的,代理类要完成的功能。]
* @return java.lang.Object
public Object getProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
总结
代理分为静态代理和动态代理
静态代理需要手动书写代理类,动态代理通过Proxy.newInstance()方法生成
不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,本质面向接口编程
代理模式本质上的目的是为了在不改变原有代码的基础上增强现有代码的功能
三、线程的状态
都知道人有生老病死,线程也不例外。
Java中线程的状态分为 6种,可以在Thread类的State枚举类查看 。
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态统的称为“运行”。
2.1 就绪(ready):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态。
2.2 运行中(running):就绪状态的线程在获得CPU时间片后变为运行状态。
阻塞(BLOCKED):阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞情况又分为三种:
等待阻塞:当线程执行wait()方法时,JVM会把该线程放入等待队列(waitting queue)中。
同步阻塞:当线程在获取synchronized同步锁失败(锁被其它线程所占用)时,JVM会把该线程放入锁池(lock pool)中。
其他阻塞:当线程执行sleep()或join()方法,或者发出了 I/O 请求时,JVM会把该线程置为阻塞状态。 当sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
超时等待(TIMED_WAITING):处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
sleep()方法就会使线程进入到超时等待状态,并且不会释放机锁,当前线程里调用其它线程t的join方法,当前线程进入TIME_WAITING状态,当前线程不释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
终止(TERMINATED):当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。线程一旦终止了,就不能复生。
//主线程和子线程并行交替执行,当主线程i=700时,子线程停止执行,主线程继续执行直到执行完成。
2. 线程休眠
System.out.println(Thread.currentThread().getName()+"-- 抢到了第"+ticketNums--+"张票");
public static void main(String[] args) {
RunnableDemo demo = new RunnableDemo();
new Thread(demo,"老王").start();
new Thread(demo,"小张").start();
new Thread(demo,"黄牛党").start();
写一个小小的案例:使用sleep()完成倒计时和时间播报的功能
/**
* @ClassName TestSleep2
* @Description TODO 倒计时,时间播报
* @Author ZhangHao
* @Date 2022/12/12 21:39
* @Version: 1.0
public class TestSleep2 {
public static void main(String[] args) {
//tenDown();
printNowDate();
//倒计时
public static void tenDown(){
int i = 10;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(i--);
if (i = 0) {
break;
//时间播报
public static void printNowDate(){
//获取当前时间
Date date = new Date(System.currentTimeMillis());
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
String format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date);
//更新当前时间
date = new Date(System.currentTimeMillis());
System.out.println(format);
3. 线程礼让
让正在执行的线程停止,从运行状态转换为就绪状态,重写竞争时间片。
礼让不一定成功,由CPU重新调度,看CPU心情!
/**
* @ClassName TestYield
* @Description TODO 线程礼让
* @Author ZhangHao
* @Date 2022/12/13 12:46
* @Version: 1.0
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"A").start();
new Thread(myYield,"B").start();
//通俗的讲,线程礼让其实就是A、B线程处于就绪状态等待被cpu调度执行,
//当其中有一个线程被cpu调度执行了,则当前这个线程再退回就绪状态重新和另外一个线程竞争
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始!");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"线程停止!");
4. 线程插队
非常霸道的一个方法,相当于其他线程正在执行,相当于一个vip线程直接插队执行完,其他线程阻塞,再执行其他的线程。
/**
* @ClassName TestJoin
* @Description TODO 线程插队
* @Author ZhangHao
* @Date 2022/12/13 13:03
* @Version: 1.0
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i 1000; i++) {
System.out.println("vip线程" + i);
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
for (int i = 0; i 500; i++) {
if (i == 200) {
try {
thread.start();
thread.join();//插队执行
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("主线程" + i);
5. 线程状态
/**
* @ClassName TestState
* @Description TODO 线程状态
* @Author ZhangHao
* @Date 2022/12/13 13:22
* @Version: 1.0
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(() - {
for (int i = 0; i i++) {
try {
Thread.sleep(200);//线程睡眠
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程体执行完毕~");
Thread.State state = thread.getState();//观察线程状态
System.out.println(state);//NEW:没有调用start()方法之前都是new
thread.start();
state = thread.getState();
System.out.println(state);//RUNNABLE:进入运行状态
//只要线程还没有死亡,就打印线程状态
while (state != Thread.State.TERMINATED){
try {
Thread.sleep(100);//100毫秒打印一次状态
} catch (InterruptedException e) {
e.printStackTrace();
state = thread.getState();//更新状态
System.out.println(state);
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
线程体执行完毕~
TERMINATED
6. 线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
//主线程默认优先级:5
System.out.println(Thread.currentThread().getName()+"---- "+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
t1.start();//默认优先级是5
Thread t2 = new Thread(myPriority);
t2.setPriority(3);
t2.start();
Thread t3 = new Thread(myPriority);
t3.setPriority(8);
t3.start();
Thread t4 = new Thread(myPriority);
t4.setPriority(Thread.MAX_PRIORITY);//最大优先级10
t4.start();
Thread t5 = new Thread(myPriority);
//t5.setPriority(-1);//Exception in thread "main" java.lang.IllegalArgumentException
//t5.start();
//优先级越大代表被调度的可能性越高,优先级低不代表不会被调度,还是看CPU心情
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---- "+Thread.currentThread().getPriority());
7. 守护线程
Thread thread = new Thread(god);
thread.setDaemon(true);//默认是false用户线程,正常的线程都是用户线程
thread.start();
new Thread(you).start();
//记住:虚拟机不用等待守护线程执行完毕,只需确保用户线程执行完毕程序就结束了。
//当用户线程执行完成之后,守护线程还执行了一段时间,是因为虚拟机关闭需要一段时间。
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护着你!");
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i 36500; i++) {
System.out.println("开心的活着!"+i);
System.out.println("------goodbye!------");
五、线程同步
并发
同一个对象被多个线程同时操作,并发编程又叫多线程编程。生活中的例子很常见,比如过年了学生都需要在手机上抢火车票,几万个人同时去抢那10张票,最终只有10个幸运儿抢到,手速慢的学生是不是就没有抢到呀。
并发针对单核CPU处理器,它是多个线程被一个CPU轮流非常快速切换执行的,逻辑上是同步运行。
同一时刻多个任务(进程or线程)同时执行,真正意义上做到了同时执行,但是这种情况往往只体现在多核CPU,单核CPU是做不到同时执行多个任务的,多核CPU内部集成了多个计算机核心(Core),每个核心相当于一个简单的CPU,多核CPU中的每个核心都可以独立执行一个任务,并且多个核心之间互不干扰,在不同核心上执行的多个任务,称为并行。
并行针对多核CPU处理器,它是在不同核心执行的多个任务完成的,物理上是同步执行。
多个任务按顺序依次执行,就比如小学在学校上厕所,小学的学校一般都是公共的厕所,而且是固定的坑位,大家按照提前排好的次序依次进行上厕所,也就是多个任务之间一个一个按顺序的执行。
线程同步
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想快速吃到饭,然后几万个学生就一拥而上,全部挤在打饭的窗口,最后饭不仅没吃到,还挨了一顿打,这也就是并发问题引起的,所以我们需要一种解决方案,最天然的解决办法就是,排队一个个来。排队在编程中叫:队列。这种解决办法就叫线程同步。
处理多线程问题时,多个线程访问同一个对象,并且当中会有一些线程需要修改这个对象,这个时候就需要用到线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入对象等待池形成队列,等待前面的线程用完,下一个线程再使用。
由于同一进程的多个线程共享同一块存储空间,会出现冲突问题,所以为了保证安全性,还加入了锁机制。synchronized关键字,当一个线程获得对象之后需要上锁,独占着资源,其他线程必须等待,等当前线程使用完释放锁即可。解决了线程安全的问题,同样也带来了一些问题:
要想保证安全,就一定会失去性能,要想保证性能,就一定会失去安全。鱼和熊掌不可兼得的道理。
线程同步:队列 + 锁
用三个小小的案例演示并发引起的问题:
/**
* @ClassName Ticket
* @Description TODO 模拟买票
* @Author ZhangHao
* @Date 2022/12/14 10:40
* @Version: 1.0
public class Ticket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"张三").start();
new Thread(buyTicket,"李四").start();
new Thread(buyTicket,"王五").start();
//多线程同时抢票,加入延迟之后,会出现买到重复票和负数票。
class BuyTicket implements Runnable{
private int ticketNum = 10;//票数
private boolean flag = true;//设置标志位
@Override
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
//买票
private void buy() throws InterruptedException {
if(ticketNum =0){
flag = false;
return;
Thread.sleep(100);//模拟延时,放大问题的发生性
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum--+"张票");
李四抢到了第10张票
张三抢到了第10张票
王五抢到了第9张票
王五抢到了第8张票
张三抢到了第8张票
李四抢到了第8张票
张三抢到了第7张票
王五抢到了第7张票
李四抢到了第7张票
张三抢到了第6张票
王五抢到了第6张票
李四抢到了第6张票
张三抢到了第4张票
李四抢到了第5张票
王五抢到了第5张票
王五抢到了第3张票
李四抢到了第2张票
张三抢到了第1张票
李四抢到了第0张票
王五抢到了第-1张票
Process finished with exit code 0
/**
* @ClassName Bank
* @Description TODO 银行取钱
* @Author ZhangHao
* @Date 2022/12/14 11:05
* @Version: 1.0
public class Bank {
public static void main(String[] args) {
Card card = new Card(200);
new MyThread(card,100,"老婆").start();
new MyThread(card,150,"我").start();
//出现将卡的余额取成负数
//银行卡
class Card {
public int money;//余额
public Card(int money) {
this.money = money;
class MyThread extends Thread {
//卡号
private Card card;
//要取的钱
private int takeMoney;
//手里的钱
private int nowMoney;
public MyThread(Card card,int takeMoney,String name){
super(name);
this.card = card;
this.takeMoney = takeMoney;
@Override
public void run() {
if (card.money - takeMoney 0) {
System.out.println(Thread.currentThread().getName() + "--- 余额不足");
return;
try {
Thread.sleep(1000);//放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
//取钱
card.money = card.money - takeMoney;
//手里的钱
nowMoney += takeMoney;
//this.getName() = Thread.currentThread().getName()
//因为本类继承了Thread类可以直接使用其方法
System.out.println(Thread.currentThread().getName() + "取了" + takeMoney + "w,手里还有" + nowMoney + "w,银行卡余额还剩" + card.money);
我取了150w,手里还有150w,银行卡余额还剩-50
老婆取了100w,手里还有100w,银行卡余额还剩-50
/**
* @ClassName UnsafeList
* @Description TODO 多线程不安全的集合
* @Author ZhangHao
* @Date 2022/12/14 11:20
* @Version: 1.0
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List String strList = new ArrayList ();
for (int i = 0; i 10000; i++) {
new Thread(()- {
strList.add(Thread.currentThread().getName());
System.out.println(strList);
}).start();
Thread.sleep(1000);
System.out.println("集合大小:"+strList.size());
//ConcurrentModificationException异常(并发修改异常)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.hnguigu.demo06.UnsafeList.lambda$main$0(UnsafeList.java:19)
at java.lang.Thread.run(Thread.java:748)
集合大小:9997
Process finished with exit code 0
1. 同步方法
public synchronized void method(int args) {}
由于我们可以通过private关键字来保证数据对象只被封装的方法访问(get/set),所以我们只需要针对方法提供一套机制,这套机制就是synchronized关键字,包括两种用法synchronized方法和synchronized块。
synchronized方法:共享的资源是通过方法来实现的。
synchronized块:共享的资源是一个对象。
同步方法中的同步监视器就是this,这个对象的本身。
synchronized关键字是一个修饰符,直接加入在方法返回值前面就可以实现同步。
同步方法的弊端:
方法里面需要修改的内容才需要锁,锁得太多,浪费资源
//加入了synchronized关键字就是同步方法,锁的对象是this
private synchronized void buy() throws InterruptedException {
if(ticketNum =0){
flag = false;
return;
Thread.sleep(100);//模拟延时,放大问题的发生性
System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum--+"张票");
/**
* @ClassName Bank
* @Description TODO 银行取钱
* @Author ZhangHao
* @Date 2022/12/14 11:05
* @Version: 1.0
public class Bank {
public static void main(String[] args) {
Card card = new Card(200);
new MyThread(card,100,"老婆").start();
new MyThread(card,150,"我").start();
//银行卡
class Card {
public int money;//余额
public Card(int money) {
this.money = money;
class MyThread extends Thread {
//卡号
private Card card;
//要取的钱
private int takeMoney;
//手里的钱
private int nowMoney = 0;
public MyThread(Card card,int takeMoney,String name){
super(name);
this.card = card;
this.takeMoney = takeMoney;
@Override
public void run() {
//如果在这里加上synchronized关键字来修饰这个方法,锁的是this也就是MyThread,而真正操作的对象是Card,所以需要使用同步块实现
//锁的是需要变化的量,需要增删改的对象
synchronized (card){
if (card.money - takeMoney 0) {
System.out.println(Thread.currentThread().getName() + "--- 余额不足");
return;
try {
Thread.sleep(1000);//放大问题的发生性
} catch (InterruptedException e) {
e.printStackTrace();
//取钱
card.money = card.money - takeMoney;
//手里的钱
nowMoney += takeMoney;
//this.getName() = Thread.currentThread().getName()
//因为本类继承了Thread类可以直接使用其方法
System.out.println(Thread.currentThread().getName() + "取了" + takeMoney + "w,手里还有" + nowMoney + "w,银行卡余额还剩" + card.money);
/**
* @ClassName UnsafeList
* @Description TODO 多线程不安全的集合
* @Author ZhangHao
* @Date 2022/12/14 11:20
* @Version: 1.0
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List String strList = new ArrayList ();
for (int i = 0; i 10000; i++) {
new Thread(()- {
//锁住需要变化的对象,这里就是list
synchronized(strList){
strList.add(Thread.currentThread().getName());
System.out.println(strList);
}).start();
Thread.sleep(1000);
System.out.println("集合大小:"+strList.size());
补充:juc(java.util.concurrent)包下的线程安全的集合
/**
* @ClassName CopyOnWriteArrayList
* @Description TODO 测试JUC并发编程下线程安全的ArrayList集合
* @Author ZhangHao
* @Date 2022/12/14 13:19
* @Version: 1.0
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
CopyOnWriteArrayList String list = new CopyOnWriteArrayList ();
for (int i = 0; i 10000; i++) {
new Thread(()- {
list.add(Thread.currentThread().getName());
}).start();
//这里加入sleep()方法是防止在子线程还没完成之前,就打印了集合大小
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("集合大小:"+list.size());
3. 死锁
多个线程各自占有一些共享资。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。