多线程
进程与线程
进程
进程是程序资源分配的基本单位、执行时的一个实例 在程序运行的时候就会产生一个进程,这个进程不会马上运行会进入就绪状态并进入队列中,在为它分配cpu时间的时候才会真正开始运行
线程
线程是一条执行路径,是操作系统可识别的最小执行和调度单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量,他们直接相互独立互不影响
进程与线程的关系:一个进程包含多个线程,进程是一个正在执行的程序,线程是一个能够被分配的资源单位,也可以称作一个任务
线程的生命周期
- 创建(new): 准备好了一个多线程的对象,也就是说执行了一个new Thread(); 创建完成后就需要为线程分配内存
- 就绪(runnable): 调用了start()方法, 等待CPU进行调度
- 运行(running): 执行run()方法
- 阻塞(blocked): 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
- 死亡(terminated): 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)
多线程并发编程的好处
我们的程序是从上往下以此执行,多线程的存在可以让我们在相同的时间里做多个操作,将程序的资源最大化的利用以此来提高我们程序的效率:
public static void main(String[] args) throws Exception{
new Thread(() -> {
try{
System.out.println(Thread.currentThread().getName()+"吃饭");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName()+"睡觉");
}catch (InterruptedException e){}
},"Thread-01").start();
new Thread(() -> {
try{
System.out.println(Thread.currentThread().getName()+"吃饭");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+"睡觉");
}catch (InterruptedException e){}
},"Thread-02").start();
}
创建线程的方式
- 继承Thread类
- 实现Runable接口
- 实现Callable接口
Thread
public class Main {
public static void main(String[] args) {
//通过继承Thread类, 重写run()方法
//调用start方法让线程进入就绪状态等待cpu调度
new DemoThread().start();
//匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
class DemoThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
Runable
public class Main {
public static void main(String[] args) {
//将Runnable实现类作为Thread的构造参数传递到Thread类中
//启动Thread类
DemoThread runnable = new DemoThread();
new Thread(runnable).start();
// Runnable是函数式接口,所以可以使用Lamda表达式形式
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName());
};
new Thread(runnable).start();
}
}
class DemoThread implements Runable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
Callable
实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread
public class Main {
public static void main(String[] args) throws Exception {
// 将Callable包装成FutureTask,FutureTask也是一种Runnable
DemoCallable callable = new DemoCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// get方法会阻塞调用的线程
Integer sum = futureTask.get();
System.out.println(Thread.currentThread().getName());
System.out.println(sum);
}
}
class DemoCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName());
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName());
return sum;
}
}
Thread-0
Thread-0
main
705082704
- Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
- Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
- Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
public class FutureTask<V> implements RunnableFuture<V> {
// 构造函数
public FutureTask(Callable<V> callable);
// 取消线程
public boolean cancel(boolean mayInterruptIfRunning);
// 判断线程
public boolean isDone();
// 获取线程执行结果
public V get() throws InterruptedException, ExecutionException;
}
Thread常用方法
// main方法就是一个主线程
public static void main(String[] args) {
// 获取当前正在运行的线程
Thread thread = Thread.currentThread();
// 线程名字
String name = thread.getName();
// 线程id
long id = thread.getId();
// 线程优先级
int priority = thread.getPriority();
// 是否存活
boolean alive = thread.isAlive();
// 是否守护线程
boolean daemon = thread.isDaemon();
}
start()
start是启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的,调用start()是一种并发编程
public static void main(String[] args) throws Exception {
new Thread(()-> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}, "Thread-A").start();
new Thread(()-> {
for (int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName() + " " + j);
}
}, "Thread-B").start();
//out
Thread-A 0
Thread-B 0
Thread-A 1
Thread-B 1
Thread-A 2
Thread-B 2
Thread-B 3
Thread-A 3
Thread-B 4
Thread-A 4
run()
run是调用线程的run方法,就是普通的方法调用,而调用run()方法就是一种顺序编程不是并发编程
public static void main(String[] args) throws Exception{
new Thread(()-> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}, "Thread-A").run();
new Thread(()-> {
for (int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName() + " " + j);
}
}, "Thread-B").run();
}
//out 都是main主线程
main 1
main 2
main 3
main 4
main 0
main 1
main 2
main 3
main 4
sleep()
睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到了会继续执行代码,如果时间未到就要醒 需要使用interrupt()来随时唤醒,睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行
public static void main(String[] args) throws InterruptedException{
Thread thread0 = new Thread(() -> {
try {
//调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常
System.out.println(Thread.currentThread().getName() + "/睡眠"+new Date());
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "/异常/唤醒"+new Date());
}
});
thread0.start();
// 这里睡眠只是为了保证先让上面的那个线程先执行
Thread.sleep(2000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "/唤醒"+new Date());
// 无需获取锁就可以调用interrupt
thread0.interrupt();
}).start();
}
//out
Thread-0/睡眠Tue Jul 06 14:27:07 CST 2021
Thread-1/唤醒Tue Jul 06 14:27:09 CST 2021
Thread-0/异常/唤醒Tue Jul 06 14:27:09 CST 2021
interrupt()方法其实作用是中断此线程(此线程不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行。
interrupted()判断当前是否被中断
new Thread(()->{
try {
Thread.sleep(1000);
Thread.currentThread().interrupt();
System.out.println("当前线程是否中断_02:" + Thread.interrupted());
System.out.println("当前线程是否中断_03:" + Thread.interrupted());
}catch (InterruptedException e){
}
}).start();
System.out.println("当前线程是否中断_01:"+Thread.interrupted());
Thread.sleep(2000);
//out
当前线程是否中断_01:false
当前线程是否中断_02:true
当前线程是否中断_03:false
wait、notify
wait、notify和notifyAll方法是Object类的final native方法,所以这些方法不能被子类重写,因此在程序中可以通过this去调用,像this.wait()…
wait(): 导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll()唤醒,这个方法只能在同步方法中调用
notify(): 该方法只能在同步方法或同步块内部调用, 随机选择一个(注意:只会通知一个)在该对象上调用wait方法的线程,解除其阻塞状态
notifyAll(): 唤醒所有的wait对象
class TempdemoApplicationTests {
public static void main(String[] args) throws Exception {
TempdemoApplicationTests waitNotifyTest = new TempdemoApplicationTests();
new Thread(() -> {
try {
waitNotifyTest.TempWait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
waitNotifyTest.TempWait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//睡眠1秒先执行wait()
new Thread(() -> {
try {
System.out.println(new Date() + "\t" + Thread.currentThread().getName()+"睡眠1秒");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//随机唤醒一个线程
waitNotifyTest.TempNotify();
}).start();
}
private synchronized void TempWait() throws InterruptedException {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "等待...");
this.wait();
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "结束...");
}
private synchronized void TempNotify() {
//如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常
this.notify();
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "完成...");
}
}
//out
Mon Jun 28 23:50:11 GMT+08:00 2021 Thread-0等待...
Mon Jun 28 23:50:11 GMT+08:00 2021 Thread-2睡眠1秒
Mon Jun 28 23:50:11 GMT+08:00 2021 Thread-1等待...
Mon Jun 28 23:50:12 GMT+08:00 2021 Thread-2完成...
Mon Jun 28 23:50:12 GMT+08:00 2021 Thread-0结束...
//这个地方有个注意的地方就是 wait会释放锁但是sleep不会
public static void main(String[] args) throws Exception {
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
//lock.wait(1000);
} catch (InterruptedException e) {
}
}
}
},"Thread_0").start();
//不会释放锁 sleep使用interrupt()来唤醒,wait需要notify或者notifyAll来通知
//让上面的先拿到锁
Thread.sleep(1000);
new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName()+i);
}
}
},"Thread_1").start();
}
//out
Mon Jun 28 23:53:38 GMT+08:00 2021 Thread_0 0
Mon Jun 28 23:53:40 GMT+08:00 2021 Thread_0 1
Mon Jun 28 23:53:41 GMT+08:00 2021 Thread_0 2
Mon Jun 28 23:53:42 GMT+08:00 2021 Thread_0 3
Mon Jun 28 23:53:43 GMT+08:00 2021 Thread_0 4
Mon Jun 28 23:53:44 GMT+08:00 2021 Thread_1 0
Mon Jun 28 23:53:44 GMT+08:00 2021 Thread_1 1
Mon Jun 28 23:53:44 GMT+08:00 2021 Thread_1 2
Mon Jun 28 23:53:44 GMT+08:00 2021 Thread_1 3
Mon Jun 28 23:53:44 GMT+08:00 2021 Thread_1 4
join
join本身是一个线程安全的方法,它的作用是将一个线程加入另一个线程当中,线程加入后会优先执行完当前加入的线程后再执行线程后的内容
public static void main(String[] args) {
new Thread(new ParentDemoRunable()).start();
}
class ParentDemoRunable implements Runnable {
@Override
public void run() {
// 线程处于new状态
Thread childThread = new Thread(new DemoRunable());
// 线程处于runnable就绪状态
childThread.start();
try {
// 当调用join时,当前线程会等加入的线程执行完毕后再继续运行
// 将某个线程加入到当前线程
childThread.join();
System.out.println("子线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 5; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父线程+++");
}
}
}
class DemoRunable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子线程+++");
}
}
}
//out
Tue Jun 29 19:45:09 CST 2021 Thread-1子线程+++
Tue Jun 29 19:45:10 CST 2021 Thread-1子线程+++
Tue Jun 29 19:45:11 CST 2021 Thread-1子线程+++
Tue Jun 29 19:45:12 CST 2021 Thread-1子线程+++
Tue Jun 29 19:45:13 CST 2021 Thread-1子线程+++
子线程执行完毕
Tue Jun 29 19:45:13 CST 2021 Thread-0父线程+++
Tue Jun 29 19:45:13 CST 2021 Thread-0父线程+++
Tue Jun 29 19:45:13 CST 2021 Thread-0父线程+++
Tue Jun 29 19:45:13 CST 2021 Thread-0父线程+++
Tue Jun 29 19:45:13 CST 2021 Thread-0父线程+++
join()方法使用后会调用join(0)方法 join本身执行并没有加锁,join(0)会加锁,join(0)会循环判断线程是否处于活动状态,如果加入的线程执行完毕则结束掉join()继续回到父线程体中执行后面的程序
yield
此方法很少被使用到,它的作用是交出CPU的执行时间,不会释放锁,让线程进入就绪状态,等待重新获取CPU执行时间
就是说使当前线程从执行状态(运行状态)变为可执行态(就绪状态),但是我们知道cpu会从众多的就绪状态中的线程中分配执行某个线程,也就是说刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了
public static void main(String[] args) {
new Thread(new Runnable() {
int sum = 0;
@Override
public void run() {
long beginTime=System.currentTimeMillis();
for (int i = 0; i < 99999; i++) {
sum += 1;
//Thread.yield();
}
long endTime=System.currentTimeMillis();
System.out.println("用时:"+ (endTime - beginTime) + " 毫秒!");
}
}).start();
new Thread(new Runnable() {
int sum = 0;
@Override
public void run() {
long beginTime=System.currentTimeMillis();
for (int i = 0; i < 99999; i++) {
sum += 1;
Thread.yield();
}
long endTime=System.currentTimeMillis();
System.out.println("用时:"+ (endTime - beginTime) + " 毫秒!");
}
}).start();
}
//out
用时:1 毫秒!
用时:53 毫秒!
setDaemon
主线程和用户线程的生命周期本身是各自独立的,setDaemon可以将用户线程设置为守护线程
public static void main(String[] args) throws Exception{
Thread thread = new Thread(()->{
try{
for (int i = 0; i <10 ; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
}catch (Exception e){
e.printStackTrace();
}
},"Thread_子线程");
thread.setDaemon(true);
thread.start();
for (int i = 0; i <5 ; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
System.out.println("结束");
}
//out
Thread_子线程 0
main 0
main 1
Thread_子线程 1
main 2
Thread_子线程 2
main 3
Thread_子线程 3
main 4
Thread_子线程 4
结束
线程池
在我们实际工作显式的去创建线程当并发很大时可能会使得内存耗尽,因为每个线程都需要被分配内存空间,线程是稀缺资源,如果无限制的创建不仅会消耗系统资源,还会降低系统的稳定性,实际开发中某些线程创建频繁的情况下一般使用线程池来控制线程的最大个数来合理的重复的利用线程,使用线程池可以进行统一分配,调优和监控
- 使用线程池可以降低资源的消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度,当任务到达时,任务可以不需要等到线程池创建就能立即执行
- 提高线程的可管理性。
ThreadPoolExecutor
ThreadPoolExecutor类是线程池中最核心的一个类,所有线程池创建都离不开这个类的使用,它的类中提供了四个构造方法
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize
,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize
,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize
,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize
,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue
,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
- corePoolSize:核心池的大小,这个参数跟线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
- maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程
- keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,就是说当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize
- unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue
LinkedBlockingQueue 常用
SynchronousQueue 常用 - threadFactory:线程工厂,主要用来创建线程
- handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
ThreadPoolExecutor继承了AbstractExecutorService ,并且实现了几个操作线程池重要的方法
-
execute():该方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行
-
submit():该方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法
-
shutdown()/shutdownNow() :关闭线程池
四种创建线程池的方式
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,缓存线程池创建的个数是去不确定的,根据每个线程的总执行时间确定的,当一个线程执行完后会有60秒的存活时间,如果有新的任务需要执行就使用刚才执行完空闲的线程执行,如果没有空闲的线程则新启动一个线程,最多启动Integer.MAX_VALUE个线程
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
int tempI = i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"\t"+"i="+tempI );
}
});
}
executorService.shutdown();
}
//out
pool-1-thread-2 i=1
pool-1-thread-4 i=3
pool-1-thread-3 i=2
pool-1-thread-1 i=0
pool-1-thread-6 i=5
pool-1-thread-5 i=4
pool-1-thread-7 i=6
pool-1-thread-8 i=7
pool-1-thread-10 i=9
pool-1-thread-9 i=8
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public class ThreadPoolTest {
public static void main(String[] args) {
// 固定线程池,线程池中的个数是固定的值
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
executorService.execute(thread);
}
executorService.shutdown();
}
}
//out
pool-1-thread-1
pool-1-thread-5
pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-1
pool-1-thread-5
newSingleThreadExecutor
创建一个定长线程池,支持定时及周期性任务执行,使用schedule向线程池提交任务
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
ScheduledFuture<String> schedule = scheduledThreadPool.schedule(new Callable<String>() {
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "ScheduledFuture";
}
}, 1, TimeUnit.SECONDS);
System.out.println(schedule.get() + "\t" +new Date());
}
scheduledThreadPool.shutdown();
}
//out
pool-1-thread-1
ScheduledFuture Mon Jul 05 14:35:22 CST 2021
pool-1-thread-1
ScheduledFuture Mon Jul 05 14:35:23 CST 2021
pool-1-thread-2
ScheduledFuture Mon Jul 05 14:35:24 CST 2021
pool-1-thread-1
ScheduledFuture Mon Jul 05 14:35:25 CST 2021
pool-1-thread-1
ScheduledFuture Mon Jul 05 14:35:26 CST 2021
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,按顺序来执行线程任务 但是不同于单线程,这个线程池只是只能存在一个线程,这个线程死后另外一个线程会补上。
public static void main(String[] args) throws Exception{
ExecutorService simpleExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
int tempI = i;
simpleExecutor.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"\t"+"i="+tempI);
}
});
}
simpleExecutor.shutdown();
}
//out
pool-1-thread-1 i=0
pool-1-thread-1 i=1
pool-1-thread-1 i=2
pool-1-thread-1 i=3
pool-1-thread-1 i=4
锁
synchronized 对象锁/类锁
public class ObjectClassLock {
public static void main(String[] args) {
TempText tempText = new TempText ();
new Thread(new Runnable() {
@Override
public void run() {
tempText.test();
}
}, "Thread_01").start();
new Thread(new Runnable() {
@Override
public void run() {
tempText.test2();
}
}, "Thread_02").start();
}
}
class TempText {
// 锁的是testService这个对象
public synchronized void test() {
for(int i = 0; i < 2; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println((new Date() + "\t" + Thread.currentThread().getName() + "\t 结束"));
}
// 静态是锁的TempText 这个类(TempText .class)
// 对象锁和类锁不是同一个锁,所以两个方法不会互斥
public synchronized static void test2() {
for(int i = 0; i < 2; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println((new Date() + "\t" + Thread.currentThread().getName() + "\t 结束"));
}
}
//out
Mon Jul 05 15:05:16 CST 2021 Thread_02
Mon Jul 05 15:05:16 CST 2021 Thread_01
Mon Jul 05 15:05:18 CST 2021 Thread_01
Mon Jul 05 15:05:18 CST 2021 Thread_02
Mon Jul 05 15:05:20 CST 2021 Thread_01 结束
Mon Jul 05 15:05:20 CST 2021 Thread_02 结束
--都替换为静态方法后
Mon Jul 05 15:05:51 CST 2021 Thread_01
Mon Jul 05 15:05:53 CST 2021 Thread_01
Mon Jul 05 15:05:55 CST 2021 Thread_01 结束
Mon Jul 05 15:05:55 CST 2021 Thread_02
Mon Jul 05 15:05:57 CST 2021 Thread_02
Mon Jul 05 15:05:59 CST 2021 Thread_02 结束
Lock锁
Lock需要手动加锁,释放锁也需要手动释放,释放锁最好(必须)放到finally代码块中释放
同一个锁可以加锁多次,也就是调用多次lock(),相应的加N次锁也必须解N次锁,使用unlock(),一般情况下都是加一次锁和解一次锁
加锁lock()与释放锁unlock()
public static void main(String[] args) throws Exception{
Lock lock = new ReentrantLock();
Runnable runnable = () -> {
System.out.println(new Date() + "\t" + Thread.currentThread().getName());
lock.lock();
try {
for(int i = 0; i < 4; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
Runnable runnable2 = () -> {
System.out.println(new Date() + "\t" + Thread.currentThread().getName());
lock.lock();
try {
for(int i = 0; i < 4; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\ti=" + i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
new Thread(runnable, "Thread-A").start();
new Thread(runnable2, "Thread-B").start();
}
//out
Mon Jul 05 17:29:56 CST 2021 Thread-B
Mon Jul 05 17:29:56 CST 2021 Thread-A
Mon Jul 05 17:29:56 CST 2021 Thread-B i=0
Mon Jul 05 17:29:56 CST 2021 Thread-B i=1
Mon Jul 05 17:29:56 CST 2021 Thread-B i=2
Mon Jul 05 17:29:56 CST 2021 Thread-B i=3
Mon Jul 05 17:29:56 CST 2021 Thread-A i=0
Mon Jul 05 17:29:56 CST 2021 Thread-A i=1
Mon Jul 05 17:29:56 CST 2021 Thread-A i=2
Mon Jul 05 17:29:56 CST 2021 Thread-A i=3
锁被线程B拿到后 先执行完释放锁后才被其他线程拿到锁