Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个Java应用程序,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
进程和线程的区别
进程
应用程序的执行实例,有独立的内存空间和系统资源。一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
线程
CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
进程和线程的关系:
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
- 处理机分给线程,即真正在处理机上运行的是线程。
- 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
并行与并发:
- 并行: 多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发: :一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
多线程程序的优点:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
Java 提供了三种创建线程的方法:
Runnable
接口;Thread
类本身;Callable
和 Future
创建线程。写一个类继承自 Thread 类,重写 run 方法。用 start 方法启动线程。 创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
package javase.util;/*** 继承Thread实现多线程*/
class MyThread extends Thread {public String title;public MyThread(String title) {this.title = title;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(this.title+":执行 i = " + i);}}
}public class TestMain {public static void main(String[] args) {new MyThread("线程A").start();new MyThread("线程B").start();new MyThread("线程C").start();}
}
程序运行结果如下:
线程B:执行 i = 0
线程A:执行 i = 0
线程C:执行 i = 0
线程A:执行 i = 1
线程B:执行 i = 1
线程A:执行 i = 2
线程C:执行 i = 1
线程A:执行 i = 3
线程B:执行 i = 2
线程A:执行 i = 4
线程C:执行 i = 2
线程A:执行 i = 5
线程B:执行 i = 3
线程A:执行 i = 6
线程C:执行 i = 3
线程A:执行 i = 7
线程B:执行 i = 4
线程A:执行 i = 8
线程C:执行 i = 4
线程A:执行 i = 9
线程B:执行 i = 5
线程C:执行 i = 5
线程B:执行 i = 6
线程C:执行 i = 6
线程B:执行 i = 7
线程C:执行 i = 7
线程B:执行 i = 8
线程C:执行 i = 8
线程B:执行 i = 9
线程C:执行 i = 9
MyThread thread = new MyThread("线程A");
thread.start();
// 出错,线程对象只允许启动一次:Exception in thread "main" java.lang.IllegalThreadStateException
// thread.start();
写一个类实现 Runnable 接口,实现 run 方法。用 new Thread(Runnable target).start() 方法来启动。
注意:Runnable接口是函数式接口@FunctionalInterface
,只有一个run()方法
@FunctionalInterface
public interface Runnable {public abstract void run();
}
Lambda实现:
package javase.util;public class TestMain {public static void main(String[] args) {for (int i = 1; i <= 3; i++) {String title = "线程对象"+i;// 函数式Runnable接口实现new Thread(()->{for (int j = 0; j < 10; j++) {System.out.println(title+ "执行 i = " + j);}}).start();}}
}
package javase.util;/*** 实现Runnable接口创建多线程*/
class MyThread implements Runnable {private String title;public MyThread(String title) {this.title = title;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(this.title+":执行 i = " + i);}}
}public class TestMain {public static void main(String[] args) {new Thread(new MyThread("线程A")).start();new Thread(new MyThread("线程B")).start();new Thread(new MyThread("线程C")).start();}
}
- 创建
Callable
接口的实现类,并实现call()
方法,该call()
方法将作为线程执行体,并且有返回值。- 创建
Callable
实现类的实例,使用FutureTask
类来包装Callable
对象,该FutureTask
对象封装了该Callable
对象的call()
方法的返回值。- 使用
FutureTask
对象作为Thread
对象的target
创建并启动新线程。- 调用
FutureTask
对象的get()
方法来获得子线程执行结束后的返回值。
注意:
Callable
接口:与Runnable
接口功能相似,用来指定线程的任务。其中的call()
方法,用来返回线程任务执行完毕后的结果,call
方法可抛出异常。
package javase.util;import urrent.Callable;
import urrent.ExecutionException;
import urrent.Future;
import urrent.FutureTask;/*** 通过 Callable 和 Future(FutureTask) 创建线程*/
class MyThread implements Callable<String> {private String title;public MyThread(String title) {this.title = title;}@Overridepublic String call() throws Exception {for (int i = 0; i < 10; i++) {System.out.println(this.title+":执行 i = " + i);}return "多线程执行完毕";}
}public class TestMain {public static void main(String[] args) throws InterruptedException, ExecutionException {FutureTask<String> futureTask = new FutureTask<String>(new MyThread("线程A"));new Thread(futureTask).start();String callStr = ();System.out.println("callStr = " + callStr);}
}
面试题: 请解释 Runnable
与Callable
的区别?。
背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理。
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池相关API:
ExecutorService
和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable
<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般又来执行Callable
void shutdown()
:关闭连接池Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
:创建一个可根据需要创建新线程的线程池 : 创建一个可重用固定线程数的线程池 :创建一个只有一个线程的线程池 :创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
import urrent.ExecutorService;
import urrent.Executors;
//创建并使用多线程的第四种方法:使用线程池
class MyThread implements Runnable {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}}public class ThreadPool {public static void main(String[] args) {// 1.调用Executors的newFixedThreadPool(),返回指定线程数量的ExecutorServiceExecutorService pool = wFixedThreadPool(10);// 2.将Runnable实现类的对象作为形参传递给ExecutorService的submit()方法中,开启线程// 并执行相关的run()ute(new MyThread());ute(new MyThread());ute(new MyThread());// 3.结束线程的使用pool.shutdown();}
}
package test;import urrent.ExecutorService;
import urrent.Executors;
import urrent.ScheduledExecutorService;
import urrent.TimeUnit;public class ThreadForpools implements Runnable{private Integer index;ThreadForpools(Integer index) {this.index = index;}@Overridepublic void run() {try {System.out.println("开始处理线程!!!");Thread.sleep(index *100);System.out.println("我的线程标识是:"+ System.identityHashCode(Thread.currentThread()));} catch (InterruptedException e) {e.printStackTrace();}}/*** @param args*/public static void main(String[] args) {System.out.println("创建一个可缓存线程池,应用中存在的线程数可以无限大。输出结果是:可以有无限大的线程数进来(线程地址不一样)");ExecutorService executorService = wCachedThreadPool();/*for (int i = 1; i <= 5; i++) {ute(new ThreadForpools(i));}System.out.println("创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。输出结果:每次只有两个线程在处理,当第一个线程执行完毕后,新的线程进来开始处理(线程地址不一样)");executorService = wFixedThreadPool(2);for (int i = 1; i <= 5; i++) {ute(new ThreadForpools(i));}System.out.println("创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。执行结果:只存在一个线程,顺序执行");executorService = wSingleThreadExecutor();for (int i = 1; i <= 5; i++) {ute(new ThreadForpools(i));}*/System.out.println("创建一个定长线程池,支持定时及周期性任务执行。执行结果:延迟三秒之后执行,除了延迟执行之外和newFixedThreadPool基本相同,可以用来执行定时任务");ScheduledExecutorService scheduledExecutorService = wSingleThreadScheduledExecutor();for (int i = 1; i <= 5; i++) {scheduledExecutorService.schedule(new ThreadForpools(i), 3, TimeUnit.SECONDS);}}}
Runnable
、Callable
接口的方式创建多线程时,线程类只是实现了 Runnable
接口或 Callable
接口,还可以继承其他类。Thread
类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()
方法,直接使用 this
即可获得当前线程。参考博文:线程状态 .html
线程的状态使用一个 枚举类型(Thread.State) 来描述的。这个枚举一共有6个值: NEW(新建)、RUNNABLE(运行)、BLOCKED(锁池)、TIMED_WAITING(定时等待)、WAITING(等待)、TERMINATED(终止、结束)。
线程共包括以下 5 种状态:
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
设置线程的名称:
- 构造函数:public Thread(String name)
- 构造函数:public Thread(Runnable target, String name)
- 实例方法:public final void setName(String name)
获取线程的名称:
- 实例方法:public final String getName()
- 静态方法:public static Thread currentThread().getName()
package javase.util;/*** 实现Runnable接口创建多线程*/
class MyThread implements Runnable {@Overridepublic void run() {// 获取线程名称System.out.println(Thread.currentThread().getName());}
}public class TestMain {public static void main(String[] args) {System.out.println("main主方法进程:"+ Thread.currentThread().getName());// mainnew Thread(new MyThread(), "线程A").start();// 线程Anew Thread(new MyThread()).start(); // Thread-0new Thread(new MyThread()).start();// Thread-1new Thread(new MyThread(), "线程B").start();// 线程BThread threadC = new Thread(new MyThread());threadC.setName("线程C");threadC.start();// // 线程C}
}
问题:
package javase.util;public class TestMain {public static void main(String[] args) {System.out.println("执行任务一");int temp = 0;for (int i = 0; i < Integer.MIN_VALUE; i++) {temp += i;}// 以下的输出需要以上先执行完才会执行System.out.println("执行任务二");System.out.println("执行任务三");}
}
解决方案:
package javase.util;public class TestMain {public static void main(String[] args) {System.out.println("执行任务一");new Thread(()->{int temp = 0;for (int i = 0; i < Integer.MIN_VALUE; i++) {temp += i;}});// 以下的输出不依赖于上面的执行,速度快多了System.out.println("执行任务二");System.out.println("执行任务三");}
}
线程休眠:
- 静态方法:public static void sleep(long millis) throws InterruptedException
- 静态方法:public static void sleep(long millis, int nanos) throws InterruptedException
单个线程的休眠示例:
package javase.util;public class TestMain {public static void main(String[] args) {new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+",i = " + i);try {Thread.sleep(100);// 休眠100毫秒} catch (InterruptedException e) {e.printStackTrace();}}}, "线程对象").start();}
}
多个线程的示例:
package javase.util;/*** 该程序执行上感觉上若干个线程一起休眠,一起唤醒,实际是有差别的*/
public class TestMain {public static void main(String[] args) {Runnable runnable = ()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+",i = " + i);try {Thread.sleep(1000);// 休眠1秒} catch (InterruptedException e) {e.printStackTrace();}}};// 创建5个线程对象for (int x = 1; x <= 5; x++) {new Thread(runnable, "线程对象"+x).start();}}
}
在我们的程序中经常会有一些不达到目的不会退出的线程,例如:我们有一个下载程序线程,该线程在没有下载成功之前是不会退出的,若此时用户觉得下载速度慢,不想下载了,这时就需要用到我们的线程中断机制了,告诉线程,你不要继续执行了,准备好退出吧。当然,线程在不同的状态下遇到中断会产生不同的响应,有点会抛出异常,有的则没有变化,有的则会结束线程。本篇将从以下两个方面来介绍Java中对线程中断机制的具体实现:
- Java中对线程中断所提供的API支持
- 线程在不同状态下对于中断所产生的反应
在以前的jdk版本中,我们使用stop方法中断线程,但是现在的jdk版本中已经不再推荐使用该方法了,反而由以下三个方法完成对线程中断的支持。
public boolean isInterrupted()
:实例方法,主要用于判断当前线程对象的中断标志位是否被标记了,如果被标记了则返回true表示当前已经被中断,否则返回false。
public void interrupt()
:实例方法,该方法用于设置当前线程对象的中断标识位。
public static boolean interrupted()
:静态的方法,用于返回当前线程是否被中断。
package javase.util;/*** 线程中断:* 我们有一个下载程序线程,该线程在没有下载成功之前是不会退出的,若此时用户觉得下载速度慢,不想下载了,* 这时就需要用到我们的线程中断机制了,告诉线程,你不要继续执行了,准备好退出吧。*/
public class TestMain {public static void main(String[] args) {Runnable runnable = ()->{// 以下模拟正常下载System.out.println("开始下载.......");for (int i = 0; i < 100; i++) {System.out.println("已下载进度" + i +"%");try {Thread.sleep(100);} catch (InterruptedException e) {// 中断了会报错//e.printStackTrace();System.out.println(Thread.currentThread().getName()+":中断下载");break;}/*// 是否中断if(Thread.interrupted()) {break;}*/}};Thread thread = new Thread(runnable, "下载任务进程");// 开始下载任务thread.start();// 以下模拟用户取消下载try {Thread.sleep(2000); // 用户等待2秒} catch (InterruptedException e) {e.printStackTrace();}// 下载任务完成了吗?if(thread.isInterrupted()) {System.out.println("用户:这么快就下载好了,佩服!佩服!");}else {// 中断下载thread.interrupt();System.out.println("用户:这么慢还没下载好,不下载了!");}}
}
参考博文:Java并发之线程中断[.html]
public final void join() throws InterruptedException
package javase.util;/*** 主线程:main* 子线程:new Thread()* 现象:主线程以及子线程抢占交替执行*/
public class TestMain {public static void main(String[] args) {// 子线程new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+": "+ i);}}, "子线程").start();// 主线程for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+": "+ i);}}
}
执行结果:
main: 0
子线程: 0
main: 1
子线程: 1
子线程: 2
子线程: 3
main: 2
子线程: 4
main: 3
main: 4
强制执行:
package javase.util;/*** 主线程:main* 子线程:new Thread()* 强制执行:一定需要先获取强制执行的线程对象,再使用join()方法*/
public class TestMain {public static void main(String[] args) {// 获取主线程Thread mainThread = Thread.currentThread();// 子线程new Thread(()->{for (int i = 0; i < 5; i++) {// 满足条件时强制执行if(i == 2) {try {// 主线程先强制执行,此时主线程将全部执行之后,再执行子线程mainThread.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+": "+ i);}}, "子线程").start();// 主线程for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+": "+ i);}}
}
执行结果:
main: 0
main: 1
子线程: 0
main: 2
main: 3
main: 4
子线程: 1
子线程: 2
子线程: 3
子线程: 4
public static void yield()
package javase.util;/*** 主线程:main* 子线程:new Thread()* 线程的礼让*/
public class TestMain {public static void main(String[] args) {// 子线程new Thread(()->{for (int i = 0; i < 10; i++) {// 满足条件时进行礼让if(i % 2 == 0) {// 进行礼让,让主线程先执行Thread.yield();}System.out.println(Thread.currentThread().getName()+": "+ i);}}, "子线程").start();// 主线程for (int i = 0; i < 20; i++) {System.out.println(Thread.currentThread().getName()+": "+ i);}}
}
执行结果:
子线程: 0
main: 0
main: 1
main: 2
main: 3
子线程: 1
main: 4
子线程: 2
main: 5
main: 6
main: 7
子线程: 3
main: 8
子线程: 4
main: 9
子线程: 5
main: 10
子线程: 6
main: 11
子线程: 7
main: 12
main: 13
main: 14
main: 15
main: 16
子线程: 8
main: 17
main: 18
main: 19
子线程: 9
参考博文:Thread之五:线程的优先级【.html】
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
- 设置优先级:
public final void setPriority(int newPriority)
- 获取优先级:
public final int getPriority()
三个常量:
- public static final int MAX_PRIORITY:10
- public static final int NORM_PRIORITY:5
- public static final int MIN_PRIORITY:1
package javase.util;/*** 线程的优先级*/
public class TestMain {public static void main(String[] args) {System.out.println("main主线程优先级:" + Thread.currentThread().getPriority()); // 中等:5System.out.println("默认线程优先级:" + new Thread().getPriority()); // 中等:5Runnable runnable = ()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+" 执行"+ i);}};Thread threadA = new Thread(runnable, "线程A");Thread threadB = new Thread(runnable, "线程B");Thread threadC = new Thread(runnable, "线程C");// 设置优先级threadA.setPriority(Thread.MIN_PRIORITY); // 设置优先级最低threadB.setPriority(Thread.NORM_PRIORITY); // 设置优先级中等threadC.setPriority(Thread.MAX_PRIORITY); // 设置优先级最高threadA.start();threadB.start();threadC.start();}
}
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),
将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
参考博文:5个步骤,教你瞬间明白线程和线程安全
参考博文:java笔记–关于线程同步(7种同步方式)【.html】
示例:
package javase.util;/*** 卖票线程*/
class TicketThread implements Runnable {int ticket = 10; // 总票数@Overridepublic void run() {while (true) {if(ticket > 0){try {// 模拟网络延迟Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" 执行"+ ticket--);}else {System.out.println("票卖完了");break;}}}
}public class TestMain {public static void main(String[] args) {Runnable runnable = new TicketThread();Thread threadA = new Thread(runnable, "票贩子A");Thread threadB = new Thread(runnable, "票贩子B");Thread threadC = new Thread(runnable, "票贩子C");threadA.start();threadB.start();threadC.start();}
}
执行结果:
票贩子C 执行8
票贩子A 执行10
票贩子B 执行9
票贩子C 执行7
票贩子A 执行5
票贩子B 执行6
票贩子B 执行4
票贩子A 执行4
票贩子C 执行4
票贩子B 执行2
票卖完了
票贩子A 执行1
票卖完了
票贩子C 执行3
票卖完了
分析:
为什么会出现票数为负数?
synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。
这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。
当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。
注意点: 虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就有点浪费资源。
synchronized的锁是什么?
- 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
- 同步方法的锁:静态方法(类名.class)、非静态方法(this)
- 同步代码块:自己指定,很多时候也是指定为this或类名.class
package javase.util;import java.util.Vector;
import urrent.ConcurrentMap;class TicketThread implements Runnable {int ticket = 1000;@Overridepublic void run() {while (true) {synchronized (this) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(Thread.currentThread().getName() + ",还剩余" + this.ticket);} else {break;}}}}
}public class TestMain {public static void main(String[] args) {TicketThread ticketThread = new TicketThread();new Thread(ticketThread).start();new Thread(ticketThread).start();new Thread(ticketThread).start();}
}
package javase.util;import java.util.Vector;
import urrent.ConcurrentMap;class TicketThread implements Runnable {int ticket = 10;@Overridepublic void run() {while (method()) {}}public synchronized boolean method(){if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(Thread.currentThread().getName() + ",还剩余" + this.ticket);return true;}return false;}
}public class TestMain {public static void main(String[] args) {TicketThread ticketThread = new TicketThread();new Thread(ticketThread).start();new Thread(ticketThread).start();new Thread(ticketThread).start();}
}
import urrent.locks.ReentrantLock;class Window implements Runnable{int ticket = 100;private final ReentrantLock lock = new ReentrantLock();public void run(){while(true){try{lock.lock();if(ticket > 0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ticket--);}else{break;}}finally{lock.unlock();}}}
}public class ThreadLock {public static void main(String[] args) {Window t = new Window();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();t2.start();}
}
import urrent.atomic.AtomicInteger;public class TestMain {public static void main(String[] args) {TicketThread ticketThread = new TicketThread();new Thread(ticketThread).start();new Thread(ticketThread).start();new Thread(ticketThread).start();}
}class TicketThread implements Runnable {// 注意:volatile可不必加private static volatile AtomicInteger atomicInteger = new AtomicInteger(10);@Overridepublic void run() {while (true) {() == 0) break;System.out.println(Thread.currentThread().getName()+":"AndDecrement());}}
}
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
wait()
:令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify()
:唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll()
:唤醒正在排队等待资源的所有线程结束等待
注意:
- 这三个方法只有在
synchronized
方法或synchronized
代码块中才能使用,否则会报java.lang.IllegalMonitorStateException
异常。- 因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify()或notifyAll) 为止。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
调用此方法后,当前线程将释放对象监控权 ,然后进入等待
在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
在当前线程中调用方法: 对象名.notify()
功能:唤醒等待该对象监控权的一个/所有线程。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
示例:
package javase.util;import urrent.locks.Lock;
import urrent.locks.ReentrantLock;class Producer implements Runnable {private Message msg ;public Producer(Message msg) {this.msg = msg ;}@Overridepublic void run() {for (int x = 0 ; x < 100 ; x ++) {if (x % 2 == 0) {this.msg.setTitle("孙悟空");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.msg.setContent("俺老孙来也");} else {this.msg.setTitle("猪八戒");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}this.msg.setContent("散伙回高老庄");}}}
}
class Consumer implements Runnable {private Message msg ;public Consumer(Message msg) {this.msg = msg ;}@Overridepublic void run() {for (int x = 0 ; x < 100 ; x ++) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Title() + " - " + Content());}}}
class Message {private String title ;private String content ;public void setContent(String content) {t = content;}public void setTitle(String title) {this.title = title;}public String getContent() {return content;}public String getTitle() {return title;}
}public class TestMain {public static void main(String[] args) {Message message = new Message();new Thread(new Producer(message)).start(); // 启动生产者线程new Thread(new Consumer(message)).start(); // 启动消费者线程}
}
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
package javase.util;import urrent.locks.Lock;
import urrent.locks.ReentrantLock;class Producer implements Runnable {private Message msg ;public Producer(Message msg) {this.msg = msg ;}@Overridepublic void run() {for (int x = 0 ; x < 100 ; x ++) {if (x % 2 == 0) {this.msg.set("孙悟空", "俺老孙来也");} else {this.msg.set("猪八戒", "散伙回高老庄");}}}
}
class Consumer implements Runnable {private Message msg ;public Consumer(Message msg) {this.msg = msg ;}@Overridepublic void run() {for (int x = 0 ; x < 100 ; x ++) {System.out.println(());}}}
class Message {private String title ;private String content ;public synchronized void set(String title,String content) {this.title = title ;try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}t = content ;}public synchronized String get() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}return this.title + " - " + t ;}
}public class TestMain {public static void main(String[] args) {Message message = new Message();new Thread(new Producer(message)).start(); // 启动生产者线程new Thread(new Consumer(message)).start(); // 启动消费者线程}
}
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
正常的:
package javase.util;import urrent.locks.Lock;
import urrent.locks.ReentrantLock;class Producer implements Runnable {private Message msg ;public Producer(Message msg) {this.msg = msg ;}@Overridepublic void run() {for (int x = 0 ; x < 100 ; x ++) {if (x % 2 == 0) {this.msg.set("孙悟空", "俺老孙来也");} else {this.msg.set("猪八戒", "散伙回高老庄");}}}
}
class Consumer implements Runnable {private Message msg ;public Consumer(Message msg) {this.msg = msg ;}@Overridepublic void run() {for (int x = 0 ; x < 100 ; x ++) {System.out.println(());}}}
class Message {private String title ;private String content ;private boolean flag = true ; // 表示生产或消费的形式// flag = true:允许生产,但是不允许消费// flag = false:允许消费,不允许生产public synchronized void set(String title,String content) {if (this.flag == false) { // 无法进行生产,应该等待被消费try {super.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.title = title ;try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}t = content ;this.flag = false ; // 已经生产过了ify(); // 唤醒等待的线程}public synchronized String get() {if (this.flag == true) { // 还未生产,需要等待try {super.wait();} catch (InterruptedException e) {e.printStackTrace();}}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}try {return this.title + " - " + t ;} finally { // 不管如何都要执行this.flag = true ; // 继续生产ify(); // 唤醒等待线程}}
}public class TestMain {public static void main(String[] args) {Message message = new Message();new Thread(new Producer(message)).start(); // 启动生产者线程new Thread(new Consumer(message)).start(); // 启动消费者线程}
}
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
package javase.util;public class JavaAPIDemo {public static void main(String[] args) {Clerk clerk = new Clerk();Productor productor = new Productor(clerk);new Thread(productor, "生产者").start();Consumer consumer = new Consumer(clerk);new Thread(consumer, "消费者").start();}
}
// 售货员
class Clerk {private int product = 0;/*** 生产商品*/public synchronized void addProduct(){// 商品数大于20的时候等待消费者取走if (this.product >= 20){try {wait();} catch (InterruptedException e) {e.printStackTrace();}}else {this.product++;System.out.println("生产者生产了第" + this.product + "个产品");// 通知消费者取走notifyAll();}}/*** 取出商品*/public synchronized void getProduct(){// 商品数小于0的时候等待生产者生产if (this.product <= 0) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}else{System.out.println("消费者取走了第" +this.product + "个产品");this.product--;// 通知生产者生产notifyAll();}}
}/*** 生产者*/
class Productor implements Runnable {Clerk clerk;public Productor(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {System.out.println("生产者开始生产产品");while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}clerk.addProduct();}}
}/*** 消费者*/
class Consumer implements Runnable {Clerk clerk;public Consumer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {System.out.println("消费者开始取走产品");while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Product();}}
}
package javase.util;/*** 优雅的停止线程*/
public class TestMain {// 判断线程停止的条件private static boolean flag = true;public static void main(String[] args) {new Thread(()->{long num = 0;while (flag) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" : num="+ num ++);}}, "线程A").start();// 模拟运行一段时间try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}// 优雅停止flag = false;}
}
Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。
只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。
设置守护线程:
public final void setDaemon(boolean on)
判断是否为守护线程:public final boolean isDaemon()
package javase.util;/*** 后台守护线程:当用户线程完成之后,守护线程关闭*/
public class TestMain {public static void main(String[] args) {Thread userThread = new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"正在运行,i = " + i );}}, "用户线程,完成核心业务");Thread daemonThread = new Thread(()->{for (int i = 0; i < Integer.MAX_VALUE; i++) {System.out.println(Thread.currentThread().getName()+"正在运行,i = " + i );}}, "守护线程");daemonThread.setDaemon(true); // 设置守护线程userThread.start();daemonThread.start();}
}
在多线程中,volatile关键字主要是在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理。
package javase.util;class MyThead implements Runnable {// 直接内存操作。能更快对变量处理private volatile int ticket = 5;@Overridepublic void run() {synchronized (this) {while (this.ticket > 0) {System.out.println(Thread.currentThread().getName() + "抢到了票,还剩" + this.ticket--);}}}
}public class TestMain {public static void main(String[] args) {MyThead ticketThread = new MyThead();new Thread(ticketThread, "线程A").start();new Thread(ticketThread, "线程B").start();new Thread(ticketThread, "线程C").start();}
}
面试题:volatile和synchronized区别?
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
设计4个线程,两个线程执行加操作,两个线程执行减操作。
package javase.util;/*** 定义操作的资源*/
class Resource {private int num = 0; // 进行加减的数字private boolean flag = true; // 加减的切换,flag = true时,表示可以进行加法操作,不可以减法操作;flag = false时,表示不可以进行加法操作,可以减法操作/*** 加法* @throws InterruptedException*/public synchronized void add() throws InterruptedException{if(this.flag == false) { // 不可以加法,只能减法操作。先等待super.wait();}Thread.sleep(100);this.num++;System.out.println("【执行加法操作】"+Thread.currentThread().getName()+", num = " + num);this.flag = false; // 加法操作之后,只能进行减法操作,需设置为ifyAll(); // 唤醒多个线程}public synchronized void sub() throws InterruptedException{if(this.flag == true) { // 不可以减法,只能加法操作。先等待super.wait();}Thread.sleep(200);this.num--;System.out.println("【执行减法操作】"+Thread.currentThread().getName()+", num = " + num);this.flag = true; // 减法操作之后,只能进行加法操作,需设置为ifyAll();}
}/*** 加法操作的线程*/
class AddThread implements Runnable {Resource resource;public AddThread(Resource resource) {source = resource;}@Overridepublic void run() {for (int i = 0; i < 50; i++) {try {resource.add();} catch (InterruptedException e) {e.printStackTrace();}}}
}/*** 减法的线程*/
class SubThread implements Runnable {Resource resource;public SubThread(Resource resource) {source = resource;}@Overridepublic void run() {for (int i = 0; i < 50; i++) {try {resource.sub();} catch (InterruptedException e) {e.printStackTrace();}}}
}public class TestMain {public static void main(String[] args) {Resource resource = new Resource();AddThread addThread = new AddThread(resource);SubThread subThread = new SubThread(resource);// 两个加法线程new Thread(addThread, "A").start();new Thread(addThread, "B").start();// 两个减法线程new Thread(subThread, "X").start();new Thread(subThread, "Y").start();}
}
设计一个生产电脑和搬走电脑类,要求生产一台电脑就搬走一台电脑,如果没有新电脑生产出来则搬运工要等待新电脑的生产;如果生产出的电脑没有搬走,则要等待电脑搬走之后再生产,并统计出生产电脑的数量。
package javase.util;class Producer implements Runnable {private Resource resource ;public Producer(Resource resource) {source = resource ;}public void run() {for (int x = 0 ; x < 50 ; x ++) {try {source.make();} catch (Exception e) {e.printStackTrace();}}};
}
class Consumer implements Runnable {private Resource resource ;public Consumer(Resource resource) {source = resource ;}public void run() {for (int x = 0 ; x < 50 ; x ++) {try {();} catch (Exception e) {e.printStackTrace();}}};
}
class Resource {private Computer computer ;public synchronized void make() throws Exception {if (thisputer != null) { // 已经生产过了super.wait();}Thread.sleep(100);thisputer = new Computer("MLDN牌电脑",1.1) ;System.out.println("【生产电脑】" + thisputer);ifyAll();}public synchronized void get() throws Exception {if (thisputer == null) { // 没有生产过super.wait();}Thread.sleep(10);System.out.println("【取走电脑】" + thisputer);thisputer = null ; // 已经取走了ifyAll();}
}class Computer {private static int count = 0 ; // 表示生产的个数private String name ;private double price ;public Computer(String name,double price) {this.name = name ;this.price = price ;count ++ ;}public String toString() {return "【第" + count + "台电脑】" + "电脑名字:" + this.name + "、价值:" + this.price;}
}public class TestMain {public static void main(String[] args) {Resource res = new Resource() ;new Thread(new Producer(res)).start();new Thread(new Consumer(res)).start();}
}
实现一个竞拍抢答程序:要求设置三个抢答者(三个线程),而后同时发出抢答指令,抢答成功者给出成功提示,未抢答成功者给出失败提示。
package javase.util;import urrent.Callable;
import urrent.ExecutionException;
import urrent.FutureTask;class MyThread implements Callable<String> {private boolean flag = true; // 抢答处理@Overridepublic String call() throws Exception {synchronized (this) { // 数据同步if (this.flag == false) { // 抢答成功this.flag = true;return Thread.currentThread().getName() + "抢答成功!";}return Thread.currentThread().getName() + "抢答失败!";}}
}public class TestMain {public static void main(String[] args) throws ExecutionException, InterruptedException {MyThread mt = new MyThread() ;FutureTask<String> taskA = new FutureTask<String>(mt) ;FutureTask<String> taskB = new FutureTask<String>(mt) ;FutureTask<String> taskC = new FutureTask<String>(mt) ;new Thread(taskA,"竞赛者A").start();new Thread(taskB,"竞赛者B").start();new Thread(taskC,"竞赛者C").start();System.out.());System.out.());System.out.());}
}
参考:
【漫画:什么是 CAS 机制?】
【漫画:什么是 CAS 机制?】
【漫画:什么是 CAS 机制?】
本文发布于:2024-01-31 14:38:58,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170668313829241.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |