-
2020-07-09 16:11:55
方法一:逻辑控制器---仅一次控制器
方法二:逻辑控制器--循环控制器,登陆设置为1,发帖设置为永远
更多相关内容 -
java多线程如何让一个线程只执行一次?
2017-03-06 10:07:20java多线程如何让一个线程只执行一次?我目前只能是检测到getname()然后用wait()方法。想知道还有没其他方法? ``` public class TicketSale { public static void main(String[] args) { ... -
如何实现ABC三个线程按顺序执行十次
2021-09-26 01:29:27题目要求:创建三个线程,每个线程分别打印ABC,并按照ABC的顺序执行十次 题目可以使用多种不同的方式解决,下面我们分别使用 Condition 等待唤醒机制、Semaphore 信号量、CountDownLatch 闭锁、Thread.join() 方法...题目要求:创建三个线程,每个线程分别打印ABC,并按照ABC的顺序执行十次
题目可以使用多种不同的方式解决,下面我们分别使用 Condition 等待唤醒机制、Semaphore 信号量、CountDownLatch 闭锁、Thread.join() 方法四种方式实现题目要求。
一、使用一个 ReentrantLock 和 三个 Condition 来实现:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 题目要求:ABC三个线程顺序执行10次 * 实现思路:使用一个ReentrantLock 和 三个 Condition 来实现 */ public class PrintABCUsingCondition { private static ReentrantLock lock = new ReentrantLock(); private static Condition conditionA = lock.newCondition(); private static Condition conditionB = lock.newCondition(); private static Condition conditionC = lock.newCondition(); public void execute(String flag) { lock.lock(); for (int i = 1 ; i <= 10 ; i++){ if ("A".equals(flag)) print(flag, conditionA, conditionB); if ("B".equals(flag)) print(flag, conditionB, conditionC); if ("C".equals(flag)) print(flag, conditionC, conditionA); } lock.unlock(); } private void print(String name, Condition currentThread, Condition nextThread) { try{ System.out.println(Thread.currentThread().getName() + "-" + name); nextThread.signal(); currentThread.await(); }catch (InterruptedException e){ e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { PrintABCUsingCondition myTask = new PrintABCUsingCondition(); new Thread(() -> myTask.execute("A")).start(); //必须确保线程A比另外两个线程先拿到ReentrantLock,所以让主线程sleep一段时间 Thread.sleep(500); new Thread(() -> myTask.execute("B")).start(); new Thread(() -> myTask.execute("C")).start(); } }
二、基于 Semaphore 信号量来实现:
import java.util.concurrent.Semaphore; /** * 题目要求:ABC三个线程顺序执行10次 * 实现思路:使用一个Semaphore信号量来实现 */ class PrintABCUsingSemaphore { private Semaphore semaphoreA = new Semaphore(1); private Semaphore semaphoreB = new Semaphore(0); private Semaphore semaphoreC = new Semaphore(0); private void printA(){ print("A", semaphoreA, semaphoreB); } private void printB(){ print("B", semaphoreB, semaphoreC); } private void printC(){ print("C", semaphoreC, semaphoreA); } private void print(String name, Semaphore currentSemaphore, Semaphore nextSemaphore) { for (int i = 0; i < 10; i++){ try { currentSemaphore.acquire(); System.out.println(Thread.currentThread().getName() +" print "+ name); nextSemaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { PrintABCUsingSemaphore printABC = new PrintABCUsingSemaphore(); new Thread(() -> printABC.printA()).start(); new Thread(() -> printABC.printB()).start(); new Thread(() -> printABC.printC()).start(); } }
三、基于 CountDownLatch 闭锁来实现:
import java.util.HashMap; import java.util.Map; import java.util.concurrent.*; /** * 题目要求:ABC三个线程顺序执行10次 * 实现思路:使用 CountDownLatch 来实现: * (1)定义dependLatch(所依赖的latch名)、selfLatch(自己的latch名) * (2)首先调用所依赖的latch的await()方法,如果所依赖的latch的count为0,则重置所依赖的latch并打印需要输出的,最后将自身的count减去 * (3)sum为需要执行的次数 */ public class PrintABCUsingCountDownLatch implements Runnable { private static Map<String, CountDownLatch> countDownLatchMap = new HashMap<>(); private String dependLatch; private String selfLatch; private PrintABCUsingCountDownLatch(String dependLatch, String selfLatch) { this.dependLatch = dependLatch; this.selfLatch = selfLatch; } @Override public void run() { for (int i = 0; i < 10; i++) { try { countDownLatchMap.get(dependLatch).await(); countDownLatchMap.put(dependLatch, new CountDownLatch(1)); System.out.println(Thread.currentThread().getName() + ":" + selfLatch); countDownLatchMap.get(selfLatch).countDown(); }catch (InterruptedException e){ e.printStackTrace(); } } } public static void main(String[] args) { String latchA = "A"; String latchB = "B"; String latchC = "C"; countDownLatchMap.put(latchA, new CountDownLatch(1)); countDownLatchMap.put(latchB, new CountDownLatch(1)); countDownLatchMap.put(latchC, new CountDownLatch(1)); //创建三个线程,但是此时由于三个CountDownLatch都不为0,所以三个线程都处于阻塞状态 Thread threadA = new Thread(new PrintABCUsingCountDownLatch(latchC, latchA)); Thread threadB = new Thread(new PrintABCUsingCountDownLatch(latchA, latchB)); Thread threadC = new Thread(new PrintABCUsingCountDownLatch(latchB, latchC)); threadA.start(); threadB.start(); threadC.start(); //latchC 阻塞了 latchA;调用latchC的countDown()方法,先让latchC为0,使latchA先运行 countDownLatchMap.get(latchC).countDown(); }
四、 使用 Thread.join() 方法来实现:
/** * 题目要求:ABC三个线程顺序执行10次 * 实现思路:使用 Thread.join() 方法来实现 */ public class PrintABCUsingJoin { public static void main(String[] args) { Thread t0 = new Thread(new Work((null))); Thread t1 = new Thread(new Work((t0))); Thread t2 = new Thread(new Work((t1))); t0.start(); t1.start(); t2.start(); } } class Work implements Runnable { private Thread beforeThread; public Work(Thread beforeThread) { this.beforeThread = beforeThread; } @Override public void run() { //调用前面线程的join方法 if(beforeThread != null) { try{ beforeThread.join(); }catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("当前线程:" + Thread.currentThread().getName()); } }
-
Java同一个线程对象能否多次调用start方法
2020-03-31 22:24:12同一个线程对象能否多次调用start方法,搞清楚这个问题,首先需要了解线程的生命周期 一、线程生命周期 更多线程状态细节描述可查看Thread内部枚举类:State 从上图线程状态转换图可以看出: 新建(NEW)状态是...同一个线程对象能否多次调用start方法,搞清楚这个问题,首先需要了解线程的生命周期
一、线程生命周期
更多线程状态细节描述可查看Thread内部枚举类:State
从上图线程状态转换图可以看出:- 新建(NEW)状态是无法通过其他状态转换而来的;
- 终止(TERMINATED)状态无法转为其他状态。
为何新建状态和终止状态不可逆转,接下来将通过Thread源码来分析
二、先通过一个正常程序来了解线程的执行过程:
public static void main(String[] args) { //创建一个线程t1 Thread t1 = new Thread(() -> { try { //睡眠10秒,防止run方法执行过快,线程组被销毁 TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } }); //第一次启动 t1.start(); }
- 当执行new Thread时,Thread构造方法会调用内部init方法做一些初始化工作,如设置线程组、目标方法、线程名称、堆栈大小、线程优先级等,线程状态是由
volatile
关键字修饰的threadStatus
控制的,初始值为0,即0表示新建状态(NEW); - 调用t1.start方法后,该方法会将调用本地方法start0,start0会创建一个新线程并修改Thread.threadStatus的值;
下面看下start方法源码:
/**线程成员变量,默认为0,volatile修饰可以保证线程间可见性*/ private volatile int threadStatus = 0; /* 当前线程所属的线程组 */ private ThreadGroup group; /** * 同步方法,同一时间,只能有一个线程可以调用此方法 */ public synchronized void start() { //threadStatus if (threadStatus != 0) throw new IllegalThreadStateException(); //线程组 group.add(this); boolean started = false; try { //本地方法,该方法会实际调用run方法 start0(); started = true; } finally { try { if (!started) { //创建失败,则从线程组中删除该线程 group.threadStartFailed(this); } } catch (Throwable ignore) { /* start0抛出的异常不用处理,将会在堆栈中传递 */ } } }
- 通过断点跟踪,可以看到当线程对象第一次调用start方法时会进入同步方法,会判断
threadStatus
是否为0,如果为0,则进行往下走,否则抛出非法状态异常; - 将当前线程对象加入线程组;
- 调用本地方法
start0
执行真正的创建线程工作,并调用run方法,可以看到在start0
执行完后,threadStatus
的值发生了改变,不再为0; - finally块用于捕捉
start0
方法调用发生的异常。
扩展:线程是如何根据
threadStatus
来判断线程的状态的呢?
通过查看Thread提供的public方法getState可以看到,调用的是sun.misc.VM.toThreadState(threadStatus),根据位运算得出线程的不同状态:public static State toThreadState(int var0) { if ((var0 & 4) != 0) { return State.RUNNABLE; } else if ((var0 & 1024) != 0) { return State.BLOCKED; } else if ((var0 & 16) != 0) { return State.WAITING; } else if ((var0 & 32) != 0) { return State.TIMED_WAITING; } else if ((var0 & 2) != 0) { return State.TERMINATED; } else { return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE; } }
继续回到原话题,当start调用后,并且run方法内容执行完后,线程是如何终止的呢?实际上是由虚拟机调用Thread中的exit方法来进行资源清理并终止线程的,看下exit方法源码:
/** * 系统调用该方法用于在线程实际退出之前释放资源 */ private void exit() { //释放线程组资源 if (group != null) { group.threadTerminated(this); group = null; } //清理run方法实例对象 target = null; /*加速资源释放。快速垃圾回收 */ threadLocals = null; inheritableThreadLocals = null; inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; }
- 到这里,t1 线程经历了从新建(NEW),就绪(RUNNABLE),运行(RUNNING),定时等待(TIMED_WAITING),终止(TERMINATED)这样一个过程;
- 由于在第一次 start 方法后,threadStatus 值被改变,因此第二次调用start时会抛出非法状态异常;
- 在调用start0方法后,如果run方法体内容被快速执行完,那么系统会自动调用exit方法释放资源,销毁对象,所以第二次调用start方法时,有可能内部资源已经被释放。
初步结论:同一个线程对象不可以多次调用 start 方法。
三、通过反射修改threadStatus来多次执行start方法
:
public static void main(String[] args) throws Exception { //创建一个线程t1 Thread t1 = new Thread(() -> { try { //睡眠10秒,防止run方法执行过快, //触发exit方法导致线程组被销毁 TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } }); //第一次启动 t1.start(); //修改threadStatus,重新设置为0,即 NEW 状态 Field threadStatus = t1.getClass().getDeclaredField("threadStatus"); threadStatus.setAccessible(true); //重新将线程状态设置为0,新建(NEW)状态 threadStatus.set(t1, 0); //第二次启动 t1.start(); }
截取start后半截源码:
boolean started = false; try { //第二次执行start0会抛异常,这时started仍然为false start0(); started = true; } finally { try { if (!started) { //创建失败,则从线程组中删除该线程 group.threadStartFailed(this); } } catch (Throwable ignore) { /* start0抛出的异常不用处理,将会在堆栈中传递 */ } }
- 在上面代码中,在第一次调用start方法后,我通过反射修改
threadStatus
值,这样在第二次调用时可以跳过状态值判断语句,达到多次调用start方法; - 当我第二次调用t1.start时,需要设置run方法运行时间长一点,防止系统调用exit方法清理线程资源;
- 经过以上两步,我成功绕开
threadStatus
判断和线程组增加方法,开始执行start0方法,但是在执行start0的时候抛出异常,并走到了finally块中,由于start为false,所以会执行group.threadStartFailed(this)操作,将该线程从线程组中移除; - 所以start0中还是会对当前线程状态进行了一个判断,不允许重复创建线程。
最后结论:无论是直接二次调用还是通过反射二次调用,同一个线程对象都无法多次调用start方法,仅可调用一次。
-
多线程(并发执行)
2020-08-02 15:06:54但并发不是真正意义上的“同时进行”,只是将CPU划分成好几个时间片段,每个片段内执行一个任务,然后在这几个片段之间来回切换,由于CPU处理速度快,让用户感觉像是多个任务在同时执行。 区别: 并行是某一时刻,...一、概念区分
1、并行与并发
并行
当系统有一个以上CPU时,同一时刻,当一个CPU在执行一个任务时,另一个CPU在执行另一个任务,两个任务互不抢占CPU资源,可以同时进行(多核CPU,一个CPU执行一个进程)
并发
一个CPU,同一时间,有多个任务在执行。但并发不是真正意义上的“同时进行”,只是将CPU划分成好几个时间片段,每个片段内执行一个任务,然后在这几个片段之间来回切换,由于CPU处理速度快,让用户感觉像是多个任务在同时执行。
区别:
-
并行是某一时刻,真正有多个程序在运行;并发是在一段时间内,宏观上多个程序同时运行。
-
并发,指多个事情,在同一时间段内同时发生了;多个任务之间是相互抢占资源的
并行,指多个事情,在同一时间点上同时发生了;多个任务之间是不相互抢占资源的
-
只有在多个CPU或CPU多核时,才会发生并行,否则看似同时发生的事情,都是并发的
2、进程与线程
进程
指系统中正在运行的一个应用程序;是资源分配的最小单位
线程
是进程内独立执行的一个单一顺序的控制流;是系统分配处理器时间资源的基本单位;是程序执行的最小单位
区别
- 进程之间数据不共享
- 线程之间可以共享资源
二、线程的生命周期
生命周期:在程序开发中,一个对象从被实例化完成,到这个对象使用结束并销毁的整个过程,类似于人的一生
线程的生命周期:一个线程被实例化,到这个线程销毁的整个过程
线程的状态
- 新建:New
一个线程被实例化完成,但是还没有做任何动作
- 就绪:Ready
一个线程已经被启动 (调用start()方法),开始争抢CPU的时间片
- 运行:Run
一个线程抢到了CPU的时间片,开始执行这个线程中的逻辑
- 阻塞:Interrupt
一个线程在运行的过程中,受到某些操作的影响,放弃已经获取的CPU时间片,并且不再参与CPU时间片的争抢,此时线程处于挂起状态
- 死亡:Dead
一个线程对象需要被销毁
三、开启线程的方式
1、继承Thread类,实现其run()方法
//要自定义一个线程类,并且该类要继承Thread类 class MyThread extends Thread{ //重写run方法 @Override public void run() { for(int i=0;i<5;i++) { System.out.println("子线程逻辑:"+i); } } } public class ThreadClass { public static void main(String[] args) { MyThread mt=new MyThread(); //新建 mt.start(); //就绪 System.out.println("主线程逻辑执行结束"); } } /*输出结果: 主线程逻辑执行结束 子线程逻辑:0 子线程逻辑:1 子线程逻辑:2 子线程逻辑:3 子线程逻辑:4 */
如果是串行运行,则“主线程逻辑执行结束”这句话应该最后执行。但由于并发执行的多线程存在,使得主程序逻辑先执行完毕,在执行子线程
注意:只有调用start方法才会启动线程,并且使该线程执行run方法;如果直接调用run方法,则并没有开启线程,即线程不会进入就绪状态。
2、实现Runnable接口,实现其run()方法
/* * Runnable接口是一个函数式接口,可以采用Lambda表达式实现其run方法 */ public class ThreadClass { public static void main(String[] args) { Runnable r1=()->{ for(int i=0;i<5;i++) { System.out.println("子线程中的逻辑:"+i); } }; Thread t=new Thread(r1); //新建 t.start(); //就绪 System.out.println("主线程逻辑执行结束"); } } //输出结果同上
3、实现Callable接口
与Runnable接口类似,只是该方式有返回值,但Runnable没有返回值
需要使用一个中介FutureTask
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) { //返回值是int类型 Callable callable=()->{ int result=0; for(int i=0;i<100;i++) { result+=i; } return result; }; //Thread thread=new Thread(callable); 不能直接像创建Runnable接口一样 //知道返回值是int性。使用泛型约束 FutureTask<Integer> task=new FutureTask<> (callable); Thread thread=new Thread(task); thread.start(); //获取计算结果 Integer integer = null; try { integer = task.get(); //该方法会抛出两个异常,需要手动处理 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(integer); } }
4、异同点
- 继承Thread类,可读性更高,但是如果某个类类继承了Thread类,那么该类将不能再继承其他类,这有可能会破坏原有的继承结构
- 使用Runnable接口,程序可对象降低,但不会破坏继承结构,一般多使用这种方式
四、线程的常用方法
1、线程的命名setName
- 实例化一个线程,使用setName()方法
- 实例化一个线程的同时,通过构造方法对线程进行命名
- 3、使用用户自定义的线程类,在实例化的同时,进行名字的赋值
需要给自定义线程类添加对应的构造方法
class MyThread extends Thread{ public MyThread() {} public MyThread(String name) { this.setName(name); //使用setName()方法 //super(name); //直接调用父类的构造方法 } } public class ThreadClass { public static void main(String[] args) { //1、实例化一个线程,使用setName()方法 Thread t=new Thread(); t.setName("用户线程1"); System.out.println(t.getName()); //2、实例化一个线程的同时,通过构造方法对线程进行命名 // 构造方法:Thread(Runnable r,String name); Thread t2=new Thread(()->{},"用户线程2"); System.out.println(t2.getName()); //3、使用用户自定义的线程类,在实例化的同时,进行名字的赋值 // 需要给自定义线程类添加对应的构造方法 MyThread t3=new MyThread("用户线程3"); System.out.println(t3.getName()); } }
2、线程休眠sleep(Run->Interrupt)
- 调用**sleep()**方法,参数:以毫秒为单位的时间差
- 会抛出InterruptedException异常,需要处理
- 使得线程由运行状态变为阻塞状态,当休眠时间到达时,才会重新变为就绪状态。即使此时系统中没有其他可执行的线程,处于sleep的线程也依然不会执行
class MyThread extends Thread{ //重写run方法 @Override public void run() { for(int i=0;i<5;i++) { System.out.println(+i); //线程休眠 //参数:以毫秒为单位 //需要捕获异常 try { Thread.sleep(1000); //休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadClass { public static void main(String[] args) { //调用threadSleep方法 threadSleep(); } /****线程休眠****/ public static void threadSleep() { //实例化一个线程 MyThread mt=new MyThread(); mt.start(); } } //输出形式:每隔1秒输出一个i值
3、线程的优先级setPriority
- 调用**setPriority()**方法,参数:[0,10]范围内的一个整数,默认是5
- 设置优先级,只是设置这个线程可以抢到CPU时间片的概率,并不是优先级高的线程一定能抢到CPU时间片(不是优先级高的线程一定先执行,也不是优先级高的线程执行完再执行其他线程)
- 设置优先级必须要放在线程开始(start)之前
public class ThreadClass { public static void main(String[] args) { threadPriority(); } /****设置线程的优先级***/ public static void threadPriority() { Runnable r=()->{ for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } }; //1、线程实例化 Thread t1=new Thread(r,"Thread-1"); Thread t2=new Thread(r,"Thread-2"); //2、设置优先级, 必须要将该操作放在线程开始(start)之前 t1.setPriority(10); t2.setPriority(1); //3、线程启动 t1.start(); t2.start(); } } //输出结果:交替执行
4、线程的礼让yield(Run->Ready)
- 调用**yield()**方法,类方法
- 线程礼让是指让当前运行的线程释放自己的CPU资源,由运行状态,回到就绪状态。**但并不意味着一定去执行另一个线程,**此时依然是两个线程进行CPU时间片的抢夺
public class ThreadClass { public static void main(String[] args) { threadYield(); } /***线程的礼让***/ public static void threadYield() { Runnable r=()->{ for(int i=0;i<10;i++) { System.out.println(Thread.currentThread().getName()+":"+i); //线程礼让 if(i==3) { Thread.yield(); } } }; Thread t1=new Thread(r,"Thread-1"); Thread t2=new Thread(r,"Thread-2"); t1.start(); t2.start(); } } /*输出结果: Thread-2:0 Thread-2:1 Thread-2:2 Thread-2:3 //Thread-2礼让,CPU被Thread-1抢到 Thread-1:0 Thread-1:1 Thread-1:2 Thread-1:3 //Thread-1礼让,但是CPU还是被Thread-1抢到,Thread-1继续执行 Thread-1:4 Thread-1:5 Thread-1:6 Thread-1:7 Thread-1:8 Thread-1:9 //Thread-1执行完毕,Thread-2接着执行 Thread-2:4 Thread-2:5 Thread-2:6 Thread-2:7 Thread-2:8 Thread-2:9 */
5、线程合并join
- 执行join的线程,在该过程中,其他线程阻塞,待此线程执行完毕,再执行其他线程。(插队)
- 抛出InterruptException异常
public class JoinTest { public static void main(String[] args) { Runnable runnable=()->{ for(int i=0;i<100;i++) { System.out.println("vip线程"+i); } }; Thread thread=new Thread(runnable); thread.start(); //主线程输出100次 for(int i=0;i<100;i++) { /* * 当主线程运行到第50次时,调用join方法,那么此时会等join方法加入的线程执行完毕,在执行主线程 * */ if(i==50) { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("main"+i); } } } /*输出:在50之前,主线程和子线程交替执行,但是等到主线程为50时,此时子线程会执行直到100结束,然后主线程才执行 */
6、守护线程setDaemon
- 如果所有的用户线程结束,那么守护线程会自动死亡;虚拟机不需要等待守护线程执行结束
- setDaemon默认是false,如果要设置一个线程为守护线程,则改为true即可
public class DaemonTest { public static void main(String[] args) { Runnable r1=()->{ while(true) { System.out.println("守护线程"); } }; for(int i=0;i<10;i++) { System.out.println("主线程"+i); } Thread thread=new Thread(r1); thread.setDaemon(true); //默认是false,表示用户线程 thread.start(); } } //守护线程是一个死循环,但是等待主线程执行结束后,该线程会自动停止
五、线程安全问题
临界资源:多个线程共享的资源。当多个线程同时去访问这个共享资源时,会出现线程安全问题
1、产生的原因
当一个线程在访问并操作某个资源的过程中,还没来得及完全修改该资源,CPU时间片就被其他线程抢走
//用四个线程模拟四个售票员卖票,仓库中的余票即为临界资源 class TicketCenter{ //描述剩余票的数量 public static int restCount=100; } public class SourseProblem { public static void main(String[] args) { Runnable r=()->{ //当余票大于0时,可以继续售票 while(TicketCenter.restCount>0) { System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+ --TicketCenter.restCount+"张"); } }; //四个线程模拟四个售票员,线程名模拟售票员名 Thread t1=new Thread(r,"Thread-1"); Thread t2=new Thread(r,"Thread-2"); Thread t3=new Thread(r,"Thread-3"); Thread t4=new Thread(r,"Thread-4"); t1.start(); t2.start(); t3.start(); t4.start(); } }
输出结果:
出现临界资源问题,这是因为一个线程在计算余票的过程中,还没来的及将计算、或计算后的结果还没来得及赋给restCount,CPU就被其他线程抢走,此时其他线程中的余票是当前抢到时刻的余票值。
2、解决方法
- JVM实现的synchronized
- JDK实现的ReentrantLock
方式一:使用同步代码块
用synchronized修饰多线程需要访问的代码
class TicketCenter{ //描述剩余票的数量 public static int restCount=100; } public class SourseProblem { public static void main(String[] args) { Runnable r=()->{ //当余票大于0时,可以继续售票 while(TicketCenter.restCount>0) { //同步监视器 synchronized("") { if(TicketCenter.restCount<=0) { return; } System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+ --TicketCenter.restCount+"张"); } } }; //四个线程模拟四个售票员,线程名模拟售票员名 Thread t1=new Thread(r,"Thread-1"); Thread t2=new Thread(r,"Thread-2"); Thread t3=new Thread(r,"Thread-3"); Thread t4=new Thread(r,"Thread-4"); t1.start(); t2.start(); t3.start(); t4.start(); } }
方法二:同步方法:使用关键字synchronized修饰的方法
将上面的同步代码段用一个方法实现
- 静态方法:同步监视器就是:当前类.class
- 非静态方法:同步监视器是 this
class TicketCenter{ //描述剩余票的数量 public static int restCount=100; } public class SourseProblem { public static void main(String[] args) { Runnable r=()->{ while(TicketCenter.restCount>0) { soldTicket(); } }; Thread t1=new Thread(r,"Thread-1"); Thread t2=new Thread(r,"Thread-2"); Thread t3=new Thread(r,"Thread-3"); Thread t4=new Thread(r,"Thread-4"); t1.start(); t2.start(); t3.start(); t4.start(); } //同步方法 public synchronized static void soldTicket(){ if(TicketCenter.restCount<=0) { return; } System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+ --TicketCenter.restCount+"张"); } }
方式三:同步锁
显式定义同步锁对象来实现同步
class TicketCenter{ //描述剩余票的数量 public static int restCount=100; } public class SourseProblem { public static void main(String[] args) { //实例化一个锁对象 ReentrantLock rt=new ReentrantLock(); Runnable r=()->{ while(TicketCenter.restCount>0) { //对临界资源上锁 rt.lock(); if(TicketCenter.restCount<=0) { return; } System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+ --TicketCenter.restCount+"张"); //对临界资源解锁 rt.unlock(); } }; Thread t1=new Thread(r,"Thread-1"); Thread t2=new Thread(r,"Thread-2"); Thread t3=new Thread(r,"Thread-3"); Thread t4=new Thread(r,"Thread-4"); t1.start(); t2.start(); t3.start(); t4.start(); } }
3、死锁
多个线程彼此持有对方所需要的锁,而不释放自己的锁
//线程A、B互相等待对方释放拥有的锁 public class DeadLock { public static void main(String[] args) { Runnable runnable1=()->{ synchronized("A"){ System.out.println("A线程持有了A锁,等待B锁"); //此时A线程已经持有A锁了,让它继续持有B锁 /*为了确保产生死锁 try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }*/ synchronized("B"){ System.out.println("A线程持有了A锁和B锁"); } } }; Runnable runnable2=()->{ synchronized("B"){ System.out.println("B线程持有了B锁,等待A锁"); //此时B线程已经持有B锁了,让它继续去持有A锁 synchronized("A"){ System.out.println("B线程持有了A锁和B锁"); } } }; Thread t1=new Thread(runnable1); Thread t2=new Thread(runnable2); t1.start(); t2.start(); } } /*输出结果: B线程持有了B锁,等待A锁 A线程持有了A锁,等待B锁 (程序未结束) */
上述代码其实不能完全产生死锁,如果在A线程获取B锁之前,B线程都没有获得执行机会,那么B线程就不会获取到B锁,此时程序依然会执行,不会产生死锁。为了一定产生死锁情况,可以在A线程执行过程中调用一个sleep方法。
4、线程通信:解决死锁的办法
方式1:synchronized下的通信
- wait():等待,当前的线程释放对同步监视器的锁定,并且让出CPU资源,使得当前的线程进入等待队列中
- notify():通知,唤醒在此同步监视器上等待的一个线程(具体哪一个由CPU决定),使这个线程进入锁池
- notifyAll():通知,唤醒在此同步监视器上等待的所有线程,使这些线程进入锁池
public class DeadLock { public static void main(String[] args) { Runnable runnable1=()->{ synchronized("A"){ System.out.println("A线程持有了A锁,等待B锁"); //A线程释放A锁(捕获异常) try { "A".wait(); } catch (InterruptedException e) { e.printStackTrace(); } synchronized("B"){ System.out.println("A线程持有了A锁和B锁"); } } }; Runnable runnable2=()->{ synchronized("B"){ System.out.println("B线程持有了B锁,等待A锁"); synchronized("A"){ System.out.println("B线程持有了A锁和B锁"); //此时B线程已经执行完成了,但是A线程任然还在等待,因此需要唤醒A线程 "A".notify(); } } }; Thread t1=new Thread(runnable1); Thread t2=new Thread(runnable2); t1.start(); t2.start(); } } /*输出结果: A线程持有了A锁,等待B锁 B线程持有了B锁,等待A锁 B线程持有了A锁和B锁 A线程持有了A锁和B锁 */
方式2:Lock锁下的通信,采用Condition控制通信。JUC中的类(java.util.comcurrent类)
- await():等价于wait()
- signal():等价于notify()
- signalAll():等价于notifyAll()
4、多线程下的单例类
懒汉式单例类会出现问题
//定义一个单例类 class Boss{ //构造器私有化 private Boss() { System.out.println("一个Boss对象被实例化了"); } private static Boss instance=null; //外部类只能通过该方法获取Boss类的实例 public static Boss getBoss() { if(instance==null) { instance=new Boss(); } return instance; } } public class SingletonTest { public static void main(String[] args) { Runnable runnable=()->{ Boss.getBoss(); }; //开辟了100条线程去获取这Boss实例 for(int i=0;i<100;i++) { new Thread(runnable).start(); } } }
当多线程去执行这个单例类时,还是希望只产生一个实例对象,但程序输出结果明显不是,这是由于多线程导致的。
修改方式1:对临界资源上锁,使用同步代码
//定义一个单例类 class Boss{ //构造器私有化 private Boss() { System.out.println("一个Boss对象被实例化了"); } private static Boss instance=null; public static Boss getBoss() { //同步代码段 synchronized("") { if(instance==null) { instance=new Boss(); } } return instance; } } public class SingletonTest { public static void main(String[] args) { Runnable runnable=()->{ Boss.getBoss(); }; //开辟了100条线程去获取这Boss实例 for(int i=0;i<100;i++) { new Thread(runnable).start(); } } }
修改方式2:对临界资源上锁,使用同步方法
class Boss{ //构造器私有化 private Boss() { System.out.println("一个Boss对象被实例化了"); } private static Boss instance=null; //同步方法 public static synchronized Boss getBoss() { if(instance==null) { instance=new Boss(); } return instance; } } public class SingletonTest { public static void main(String[] args) { Runnable runnable=()->{ Boss.getBoss(); }; //开辟了100条线程去获取这Boss实例 for(int i=0;i<100;i++) { new Thread(runnable).start(); } } }
六、线程池
线程池在系统启动时就创建大量空闲的线程。提前创建多个线程,放入线程池,使用时直接从线程池中获取,使用完放回池中
1、作用
可以避免频繁创建销毁线程的过程,实现充分利用
- corePoolSize:核心池的大小(可以放多少个线程)
- maximumPoolSize:最大线程数(一次可以同时运行的线程数量)
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
2、创建方式
- ExecutorService接口:线程池真正的接口
- Executor:创建线程的工具类,调用该类的newFixedThreadPool(corePoolSizesize)方法来创建线程池
- execute:执行Runnable接口的,无返回值
- Future submit:执行Callable接口的,有返回值
- shutdown:关闭连接
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolTest { public static void main(String[] args) { Runnable r=()->{ System.out.println(Thread.currentThread().getName()); }; //创建线程池,设置大小为10 ExecutorService service=Executors.newFixedThreadPool(10); //执行 service.execute(r); service.execute(r); service.execute(r); service.execute(r); //关闭连接 service.shutdown(); } } /*输出结果: pool-1-thread-3 pool-1-thread-4 pool-1-thread-2 pool-1-thread-1 */
七、JUC组件
1、未来任务FutureTask
利用Callable创建线程时,有返回值,该值由Future进行封装,FutureTask实现了RunnableFuture接口,而该接口继承自Runnable和Future接口,因此FutureTask既可以当做一个任务执行,也可以有返回值。
当计算一个任务需要很长时间时,可使用FutureTask来封装这个任务,使得主线程在完成自己的任务后在去获取这个计算结果
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class FutureTaskTest { public static void main(String[] args) { //创建一个Clallable接口,有返回值,给子线程执行 Callable<Integer> cla=()->{ int result=0; for(int i=0;i<100;i++) { Thread.sleep(10); //每一次计算时都让让主线程执行一段时间 result+=i; } return result; }; //新建一个FutureTask实例 FutureTask<Integer> futureTask=new FutureTask<>(cla); //执行计算任务的线程 Thread t1=new Thread(futureTask); t1.start(); //创建Runnable接口,给主线程执行 Runnable runnable=()->{ System.out.println("主线程任务正在执行"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } }; Thread t2=new Thread(runnable); t2.start(); //得到有返回值的输出 try { System.out.println(futureTask.get()); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /*输出结果: 另一个线程任务正在执行 4950 */ //如果将Callable执行体中的Thread.sleep(10);去掉,则执行结果为:4950 另一个线程任务正在执行。
2、阻塞队列BlockingQueue
利用BlockingQueue作为线程同步的工具,主要用来实现消费者生产者设计模式。详见《生产者消费者设计模式》
3、叉链接ForkJoin
主要用于并行计算中,将大的任务分成小的任务进行计算,再把小任务的结果合并成总的计算结果
-
-
14 如何多个线程按顺序执行
2020-08-12 21:48:06调用一个线程的join()方法就可以让这个线程强制运行,并且它会阻塞主线程的运行。 原理:调用join方法,会调用join(0)方法,当参数为0时,会调用wait方法,使主线程阻塞,等待子线程执行完毕后,主线程结束等待,... -
开启Thread线程只执行一次
2018-04-26 17:09:09代码如下 private static Thread mTaskThread = new Thread(new Runnable() { @Override public void run() { count++;... System.out.println("this is time do task:" +...运行显示,多次执行了线程的任务。 -
【Jmeter】一个线程组下面有多个请求,怎么只让一个请求并发执行?
2017-07-07 11:01:191. 我一个线程组下面有多个请求,比如访问(拉取token)、登入(获取sesseion)、创建订单 2. 其中访问、登入我只要执行一次就行了,我只要拿去到他们的返回值就行了,就是创建订单我要并发多次,请问这个应该怎么弄? -
解决java Android 中同一个线程多次运行问题
2016-10-11 09:32:42但是我们的软件需求常常需要同一个线程多执行同一个方法。为此可以使用notify(),wait()方法来达到我们想要的效果。先看代码public class ThreadTest extends Thread { private static boolean flag = false; ... -
Java面试题:多个线程交替执行
2020-06-30 11:17:57Java面试题:多个线程交替执行 文章目录Java面试题:多个线程交替执行前言1.Condition 版本2.公平锁3.如何解决线程打印顺序的问题 前言 最近在一些技术群里看到有很多小伙伴面试的时候碰到这个多线程的笔试题,实现... -
Thread启动线程的start方法能执行多次吗?
2020-06-27 17:17:51线程的创建 我们知道在Java里线程是通过java.lang.Thread类来实现的。一般我们创建无返回值的线程会用下面两...如果多次调用start方法会发生什么? 其实答案就是源码里,在这之前我们要了解线程的状态有哪些。 线程的 -
如何确保三个线程顺序执行?
2018-07-03 09:42:00确保三个线程t1执行完后t2执行,t2执行完成后t3执行。1.使用join1.使用join thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到... -
java线程问题:怎样多次执行
2016-04-20 02:15:22有三个线程,第一个执行1到5递增,第二个执行6-10递增,第三个11-15。并且每个线程执行5次。我们老师给了一个类,但是我不会写方法,还请大神帮帮忙。![图片说明]... -
java 多个线程同时写同一个文件
2019-10-11 17:00:17话不多说,先直接上代码: 主方法: import java.util.concurrent.CountDownLatch; /** * @ProjectName: emp_customer * @Package: PACKAGE_NAME * @ClassName: Test * @Author: Administrator * @... -
Java多线程面试题之如何让主线程等子线程执行完之后再执行
2019-11-05 23:37:49现在有一个主线程X,和两个子线程A和B,A和B之间没有依赖关系且两者的执行时间不确定,现在要求如下: 1:不限制A和B执行顺序的 2:主线程X需要在子线程A和B执行完成之后再执行 方案1 1)思路 使用join()方法实现 2... -
三个线程依次顺序执行
2017-07-11 11:08:59保证三个线程依次按顺序执行在有一些需求的情况下,我们需要三个线程依次按顺序执行,那么有人就会问了,为什么不把三个线程的run方法依次放到三个方法体中,然后依次执行,按顺序调用三个方法体,这样不是同样达到... -
如何保证多个线程同时执行?如何保证多个线程在并发下依次执行?如何保证多个线程有序交错执行?
2019-01-27 15:08:08Thread提供了一个让一个线程等待另一个线程执行完的方法——join();当线程A调用B线程join()方法后,线程A将会阻塞,只有等B线程执行完后在会执行线程A public class ThreadMain { public static void main... -
android 线程只启动一次
2022-03-25 17:52:10目录 android 线程只启动一次 在java中同一个线程只能被start()一次,当第二次start()就会报异常 android 线程只启动一次 将static线程放到Application ...一般放到最后面,不然...需求:需要同一个线程多次. -
实现两个线程交替执行
2019-06-04 09:26:24题目:使用多线程实现输出的效果为: 1 -1 2 -2 3 -3 4 -4 … package com.thread.synchronizedDemo.lock; import java.util.concurrent.locks.ReentrantLock; /** * @author 007 * @ClassName 类名称 * @... -
python多进程多线程,多个程序同时运行
2021-04-08 13:47:15python 多线程 多进程同时运行 多任务要求 python 基础语法 python 文件目录操作 python 模块应用 开发工具 pycharm ...在一段时间内交替执行多个任务, 例如单核cpu 处理多任务, 操作系统让各个任务交 -
【python 开启线程】在一个程序同时开启另一个线程执行另一项任务
2019-08-14 16:40:38在python程序中,我们往往会遇到需要在一个脚本里面,同时执行多项任务的需求,那么此时,我们可以开启一个线程去执行。 如下面,我需要开启一个读取redis白名单线程任务。 每一分钟跟新一次用户白名单。 import ... -
java如何在多线程执行完成后再执行某个方法
2018-06-29 10:35:09System.out.println("运行线程号:" + j); synchronized (waitObject) { int cnt = count.decrementAndGet(); if (cnt == 0) { waitObject.notifyAll(); } } } }); } synchronized (waitObject) { ... -
python3 多线程执行后再执行主线程的问题
2018-05-16 01:40:50先进行for 循环,每次循环创建1个线程,然后都执行func1这个函数,每次循环传递给func1的参数都不同, 我想在所有子线程运行结束后,再执行下边的代码,请高手帮忙,如何在我代码基础上进行修改? ![图片说明]... -
java多线程分批执行任务demo
2020-04-28 15:01:07一、需求描述 利用线程池实现线程分批执行...实际应用:当我们批量的需求比如启动1000个节点,启动一个节点的时间大概是3s,我们肯定不会去并行执行1000次启动,肯定是利用线程池的技术利用多线去异步启动1000个... -
一个线程两次调用start()方法会出现什么情况?
2019-05-24 10:29:24这里有一道经典的面试题:“一个线程两次调用start()方法会出现什么情况?谈谈线程的生命周期和状态转移。” 我们就以这道题目为切入点深入聊聊线程吧。 典型回答 Java的线程是不允许启动两次的,第二次调用必然会... -
单核cpu多核cpu如何执行多线程
2020-03-31 11:19:00进程: 是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,比如电脑上的各种运行中的软件 进程和线程 进程可进一步细化为线程,是一个程序内部的一条执行路径,比如杀毒软件中的体检,杀毒,清理等... -
Java利用多线程执行SQL减少执行时间提高效率
2017-11-22 14:23:47Java利用多线程执行SQL减少执行时间提高效率 首先这是个人在实际的项目中使用的代码,部分敏感代码没有上,不过不影响对代码的理解: 1.实现自己的线程 /** * * @author * @date */ package ... -
c#多线程并发执行一个操作函数
2013-05-13 16:41:08有时候我们进行很多的数据对比运算时,单线程的程序显得很慢,这个时候可以用多线程并发运行: int maxThread = 10; //10个并发线程 int currTNum = 0; WaitHandle[] whs = new WaitHandle... -
c#中等待某个线程执行完后再执行某个线程
2018-08-27 17:06:17在方法的外部申请一个这样的变量 CountdownEvent latch = new CountdownEvent(3); 比如现在执行的是方法a public void a() { int si=0,ei=0; Thread thread = new Thread(() => refreshData(si, ei))... -
linux shell 多线程执行程序
2019-06-19 09:40:59Shell中并没有真正意义的多线程,要实现多线程可以启动多个后端进程,最大程度利用cpu性能。 直接看代码示例吧。 (1) 顺序执行的代码 #!/bin/bash date for i in `seq 1 5` do { echo "sleep 5" sleep 5 } done ...