Thread多线程
约 13549 字大约 45 分钟
(一) 线程概念
1. 进程与线程
程序:开发写的代码称之为程序。程序就是一堆代码,一组数据和指令集,是一个静态的概念。
进程(Process):将程序运行起来,我们称之为进程。进程是执行程序的一次执行过程,它是动态的概念。进程存在生命周期,也就是说程序随着程序的终止而销毁。进程之间是通过TCP/IP端口实现交互的。
线程(Thread):线程是进程中的实际运作的单位,是进程的一条流水线,是程序的实际执行者,是最小的执行单位。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程。线程是CPU调度和执行的最小单位。
注意:很多多线程都是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器,如果是模拟出来的多线程,即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
2. 并发、并行与串行
并发:同一个对象被多个线程同时操作。(这是一种假并行。即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉)。
并行:多个任务同时进行。并行必须有多核才能实现,否则只能是并发。
串行:一个程序处理完当前进程,按照顺序接着处理下一个进程,一个接着一个进行。
3. 线程的状态
新建状态(NEW):线程已创建,尚未调用start()方法启动之前。
运行状态(RUNNABLE):线程对象被创建后,调用该对象的start()方法,并获取CPU权限进行执行。
阻塞状态(BLOCKED):线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
等待状态(WAITING ):等待状态。正在等待另一个线程执行特定动作来唤醒该线程的状态。
超时等待状态(TIME_WAITING):有明确结束时间的等待状态。
终止状态(TERMINATED ):当线程结束完成之后就会变成此状态。
4. 锁池与等待池
1. 锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
2. 等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中。
5. 进程
进程就是正在运行中的程序(进程是驻留在内存中的)是系统执行资源分配和调度的独立单位每一进程都有属于自己的存储空间和系统资源。
注意:进程A和进程B的内存独立不共享。
6. 线程
线程就是进程中的单个顺序控制流,也可以理解成是一条执行路径。
单线程:一个进程中包含一个顺序控制流(一条执行路径)。
多线程:一个进程中包含多个顺序控制流(多条执行路径)。
在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。对于单核的CPU来说,不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是多个事情同时在做。
7. 线程模型
局部变量:在栈中。
局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。
实例变量:在堆中。
实例变量在堆中,堆只有1个。
静态变量:在方法区。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
8. 异步与同步编程模型
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。
9. 程序、进程与线程
程序是指令和数据的集合,其本身没有任何运行的含义,是一个静态的概念。
在一个操作系统中,每个独立的程序都可以称为一个进程,也就是“正在运行的程序”,(进程就是程序执行的过程)。它是一个动态的概念,是系统分配资源的单位。
每个运行的程序都是一个进程,在一个进程中还可以有多个执行单元同时运行,这些运执行单元可以看作程序执行的一 条路径,被称为线程。线程是CPU调度和执行的单位。操作系统中的每一个进程中都至少存在一个线程。当一个Java程序启动时就会产生一个进程, 该进程会默认创建一个线程, 在这个线程上会运行main ()方法中的代码。
10. 多线程
在一个进程中,同时运行了多个线程,用来完成不同的工作,则称之为多线程。多个线程交替占用CPU资源,而非真正的并行执行。
好处:
- 充分利用CPU资源。
- 简化编程模型。
- 带来良好的用户体验。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
11. 普通方法调用与多线程
线程就是独立的执行路径。 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。main()称之为主线程,为系统的入口,用于执行整个程序。 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。线程会带来额外的开销,如cpu调度时间,并发控制开销。每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
12. 线程状态
新建状态(new):Thread t = new Thread(); 创建一个线程对象后,该线程对象就处新建状态。此时它不能运行,和其他Java对象一样,仅仅由Java虚报机为其分配了内存,没有表现出任何线程的动态特征。
就绪状态(Runnable):当线程对象调用了start ()后,该线程就进人就绪状态,也称可运行状态。处于就绪状态的线程放人到可运行池中,此时它只是其就进运行的条件,能否获得CPU的使用权,正需要等待系统的调度。
运行状态(Running):如果处于就绪状态的线程获得了 CPU的使用权,开始执行run()方法,则该线程处于运行状态。当一个线程启动后,它不可能一直处于运行状态,当使用完系统分配CPU使用权时同后,系统就会剥夺该线程占有的CPU资源,让其他线程获得执行的机会。
阻塞状态(Blocked):一个正在执行的线程在某些特殊情况下,如执行耗时的输入/输出操作时,会放弃CPU的使用权,进人阻塞状态。线程进人阻塞状态后,就不能进人排队队列。只有当引起阻塞的原因被消除后,线程才可以转人就绪状态。
13. wait/sleep/join/yield区别
1. wait()
wait()必须在synchronized 修饰的同步代码块中调用
wait()会释放cpu资源和释放同步锁(类锁和对象锁)
调用wait()后必须调用notify()或notifyAll()后线程才会从等待池进入到锁池,当我们的线程竞争得到同步锁后就会重新进入绪状态等待cpu资源分配
wait()是Object类的方法
sleep()
sleep()会释放cpu资源,但是不会释放同步锁(类锁和对象锁)
sleep()是Thread类的方法
sleep()调用后线程会进入阻塞队列,时间到之后线程会进入就绪队列,重新去竞争cpu资源,而wait()方法不会。
yield()
yield()执行后线程直接进入就绪状态
join()
执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到join结束或中断线程B才开始进入阻塞队列
(二) 线程实现
1. 继承Thread
/**
* 自定义线程对象,继承Thread,重写run()方法
* 步骤:
* - 自定义线程类继承Thread类
* - 重写run()方法,编写线程执行体
* - 创建线程对象,调用start()方法启动线程(启动后不一定立即执行,抢到CPU资源才能执行)
*/
public class MyThread extends Thread {
public MyThread(String name){
super(name);
}
@Override
public void run() {
// 线程执行体
for (int i = 0; i < 10; i++) {
System.out.println("我是自定义" + Thread.currentThread().getName() + "--" + i);
}
}
public static void main(String[] args) {
// main线程,主线程
// 创建线程实现类对象
MyThread thread = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
// 调用start()方法启动线程
thread.start();
thread2.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是主线程--" + i);
}
}
}
2. 继承Thread
/**
多线程的实现方式(一)
继承Thread类
1、自定义一个类MyThread类,用来继承与Thread类
2、在MyThread类中重写run()方法
3、在测试类中创建MyThread类的对象
4、启动线程
此处最重要的为start()方法。单纯调用run()方法不会启动线程,不会分配新的分支栈。
start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。线程就启动成功了。
启动成功的线程会自动调用run方法(由JVM线程调度机制来运作的),并且run方法在分支栈的栈底部(压栈)。
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
单纯使用run()方法是不能多线程并发的。
*/
public class MyThread2 extends Thread{
public MyThread2() {
}
public MyThread2(String name) {
super(name);
}
//run方法是每个线程运行过程中都必须执行的方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(this.getName() + ":" + i);
}
}
public static void main(String[] args) {
//创建线程
MyThread2 t01 = new MyThread2();
MyThread2 t02 = new MyThread2();
MyThread2 t03 = new MyThread2("线程03");
//开启线程
t01.start();
t02.start();
t03.start();
//设置线程名(补救的设置线程名的方式)
t01.setName("线程01");
t02.setName("线程02");
//设置主线程名称
Thread.currentThread().setName("主线程");
for (int i = 0; i < 50; i++) {
//Thread.currentThread() 获取当前正在执行线程的对象
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
3. 继承Thread
/**
* 继承Thread类
* 将一个类声明为Thread的子类。这个子类应该重写run类的方法Thread。然后可以分配并启动子类的实例。
*
* 可以分为三步:
*
* 自定义线程类继承Thread类。
* 重写run()方法,编写线程执行体。
* 创建线程对象,调用start()方法启动线程。
*/
class MyThread3 extends Thread{
public void run(){
while (true){
System.out.println("MyThread类的run方法运行");
}
}
public static void main(String[] args) {
MyThread3 myThread = new MyThread3();
myThread.start();
while (true){
System.out.println("Demo类的main运行方法");
}
}
}
4. 实现Runnable
/**
* 自定义线程对象,实现Runnable接口,重写run()方法
* 步骤:
* - 自定义线程类实现Runnable接口
* - 实现run()方法,编写线程体
* - 创建线程对象,调用start()方法启动线程(启动后不一定立即执行,抢到CPU资源才能执行)
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行体
for (int i = 0; i < 10; i++) {
System.out.println("我是自定义" + Thread.currentThread().getName() + "--" + i);
}
}
public static void main(String[] args) {
// main线程,主线程
// 创建实现类对象
MyRunnable myRunnable = new MyRunnable();
// 创建代理类对象
Thread thread = new Thread(myRunnable,"线程1");
Thread thread2 = new Thread(myRunnable,"线程2");
// 调用start()方法启动线程
thread.start();
thread2.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是主线程--" + i);
}
}
}
5. 实现Runnable
/**
多线程的实现方式(二)
实现Runnable接口
1、自定义一个MyRunnable类来实现Runnable接口
2、在MyRunnable类中重写run()方法
3、创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去
4、启动线程
*/
class MyRunnable2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
public static void main(String[] args) {
MyRunnable2 myRun = new MyRunnable2();//将一个任务提取出来,让多个线程共同去执行
//封装线程对象
Thread t01 = new Thread(myRun, "线程01");
Thread t02 = new Thread(myRun, "线程02");
Thread t03 = new Thread(myRun, "线程03");
//开启线程
t01.start();
t02.start();
t03.start();
//通过匿名内部类的方式创建线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
},"线程04").start();
}
}
6. 实现Runnable
/**
* 创建一个线程是声明实现类Runnable接口。那个类然后重写run类的方法。然后可以分配类的实例,在创建的时候作为参数传递,并启动。
*
* 可以分为三步:
*
* 定义MyRunnable类实现Runnable接口
* 实现run()方法,编写线程执行体
*
* 创建线程对象,调用start()方法启动线程
*
*/
public class MyRunnable3 {
public static void main(String[] args) {
//将该对象传给Thread类的构造函数
Thread thread = new Thread(()->{
while (true){
System.out.println("MyThread类的run方法");
}
});
//thread对象调用start方法,在这里不会调用自身的run方法,它会调用myRunnable对象的run方法
thread.start();
while (true){
System.out.println("Demo类的main方法");
}
}
}
7. 实现Callable
/**
* 自定义线程对象,实现Callable接口,重写call()方法
* 步骤:
* - 实现Callable接口,先要返回值类型
* - 重写call()方法,需要抛出异常
* - 创建目标对象
* - 创建执行服务:ExecutorService ser = Executor.newFixedThreadPool(1);
* - 提交执行:Future<Boolean> res = ser.submit(t1);
* - 获取结果:boolean r1 = res.get();
* - 关闭服务:ser.shutdownNow();
*/
public class MyCallable implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
// 线程执行体
for (int i = 0; i < 10; i++) {
System.out.println("我是自定义" + Thread.currentThread().getName() + "--" + i);
}
return true;
}
public static void main(String[] args) throws ExecutionException,
InterruptedException {
// main线程,主线程
// 创建线程实现类对象
MyCallable thread = new MyCallable();
MyCallable thread2 = new MyCallable();
// 创建执行服务,参数是线程池线程数量
ExecutorService ser = Executors.newFixedThreadPool(2);
// 提交执行
Future<Boolean> res = ser.submit(thread);
Future<Boolean> res2 = ser.submit(thread2);
// 获取结果
boolean r1 = res.get();
System.out.println(r1);
boolean r2 = res2.get();
System.out.println(r2);
// 关闭服务
ser.shutdownNow();
}
}
8. 实现Callable
/**
多线程的实现方式(三)
实现Callable接口( java.util.concurrent.FutureTask; /JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。)
1、自定义一个MyCallable类来实现Callable接口
2、在MyCallable类中重写call()方法
3、创建FutureTask,Thread对象,并把MyCallable对象作为FutureTask类构造方法的参数传递进去,把FutureTask对象传递给Thread对象。
4、启动线程
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
*/
public class MyCallable2 {
public static void main(String[] args) throws Exception {
// 第一步:创建一个“未来任务类”对象。
// 参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //自动装箱(300结果变成Integer)
}
});
// 创建线程对象
Thread t = new Thread(task);
// 启动线程
t.start();
// 这里是main方法,这是在主线程中。
// 在主线程中,怎么获取t线程的返回结果?
// get()方法的执行会导致“当前线程阻塞”
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
// main方法这里的程序要想执行必须等待get()方法的结束
// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
// 另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}
(三) 线程方法
1. setDaemon
/**
* 守护(Daemon)线程
* 线程分为用户线程和守护线程。
* 虚拟机必须确保用户线程执行完毕。
* 虚拟机不用等待守护线程执行完毕。(如:记录操作日志、监控内存、垃圾回收等线程)。
* Thread.setDaemon(boolean on)方法,true守护线程;false用户进程。默认是false。
*/
public class MyDeamon{
public static void main(String[] args) throws InterruptedException {
DeamonThread deamon = new DeamonThread();
UserThread user = new UserThread();
Thread deamonThread = new Thread(deamon);
deamonThread.setDaemon(true); // 设置为守护进程
deamonThread.start();
Thread userThread = new Thread(user);
userThread.setDaemon(false);// 设置为用户进程
userThread.start();
}
}
// 模拟守护线程
class DeamonThread implements Runnable{
@Override
public void run() {
// 验证虚拟机不用等待守护线程执行完毕,只要用户线程执行完毕,程序就结束。
// 如果成功,怎下面的打印不会一直输出;如果成功,则下面的打印会一直输出
while (true) {
System.out.println("我是守护线程");
}
}
}
// 用户线程
class UserThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是用户线程 :" + i);
}
}
}
2. setDaemon
/**
*
* 守护(daemon)线程
* 线程分为用户线程和守护线程
* 虚拟机必须确保用户线程执行完毕
* 虚拟机不用等待守护线程执行完毕
* 如:后台记录操作日志,监控内存,垃圾回收等待
*/
public class TestDaemon {
public static void main(String[] args) {
China china = new China();
You you = new You();
Thread thread = new Thread(china);//让国家编程守护线程
thread.setDaemon(true);//setDaemon返回值是Boolean 默认是false表示用户线程
thread.start();//守护线程启动
new Thread(you).start();//用户线程启动
}
}
//国家
class China implements Runnable{ //守护线程
@Override
public void run() {
while(true){//国家永远存在
System.out.println("国家保护着你!");
}
}
}
class You implements Runnable{ //用户线程
@Override
public void run() {
for (int i = 0; i < 30000; i++) {
System.out.println("你开心的活着");
}
System.out.println("*******goodbye!*******");
}
}
3. yield
/**
* yield()方法:
* - 提出申请释放CPU资源,至于能否成功释放取决于JVM决定。
* - 调用yield()方法后,线程仍然处于RUNNABLE状态,线程不会进入阻塞状态。
* - 调用yield()方法后,线程处于RUNNABLE状态,就保留了随时被调用的权利。
* 执行结果:从结果1看,a释放CPU成功后,b就抢到了CPU执行权,接着b也释放CPU成功,a抢到了CPU执行权;从结果2看,a并没有成功释放CPU。
*
*/
public class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程结束执行");
}
public static void main(String[] args) throws InterruptedException {
MyYield myThread = new MyYield();
Thread thread = new Thread(myThread,"a");
Thread thread2 = new Thread(myThread,"b");
thread.start();
thread2.start();
}
}
4. yield
/**
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
*/
public class DemoYield {
public static void main(String[] args) {
//创建线程
MyThread5 t01 = new MyThread5("线程01");
MyThread5 t02 = new MyThread5("线程02");
MyThread5 t03 = new MyThread5("线程03");
//开启线程
t01.start();
t02.start();
t03.start();
}
}
class MyThread5 extends Thread{
public MyThread5() {
}
public MyThread5(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(30 == i){
Thread.yield();//当循i环到30时,让线程让步
//1、回到抢占队列中,又争夺到了执行权
//2、回到抢占队列中,没有争夺到执行权
}
System.out.println(this.getName() + ":" + i);
}
}
}
5. yield
/**
* 礼让线程,让当前正在执行的线程暂停,但不阻塞。
* 将线程从运行状态转为就绪状态。
* 让cpu重新调度,礼让不一定成功!看CPU心情。
*/
public class TestYield {
public static void main(String[] args) {
System.out.println("*******线程的礼让*******");
TestThread my = new TestThread();
Thread t1 = new Thread(my,"线程A");
Thread t2 = new Thread(my,"线程B");
t1.start();
t2.start();
}
}
class TestThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"正在运行"+i);
if (i==3){
System.out.println("线程礼让:");
Thread.yield();
}
}
}
}
6. join
/**
* 线程插队
* 也叫线程的强制执行。
* 使当前线程暂停执行,等待其他线程结束后再继续执行本线程
* public final void join()
* public final void join(long mills)
* public final void join(long mills,int nanos)
* millis:以毫秒为单位的等待时长
* nanos:要等待的附加纳秒时长
* 需处理InterruptedException异常
*
*/
public class TestJoin {
public static void main(String[] args) {
System.out.println("*******线程强制执行********");
//创建子线程并启动
Thread temp = new Thread(new MyThread3());
temp.start();
for (int i = 0; i < 20; i++) {
if (i==5){
try {
temp.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行"+i);
}
}
}
class MyThread3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出当前线程信息
System.out.println(Thread.currentThread().getName()+"运行"+i);
}
}
}
7. join
/**
* join()方法:
* - 将当前的线程挂起,当前线程阻塞,待其他的线程执行完毕,当前线程才能执行。
* - 可以把join()方法理解为插队,谁插到前面,谁先执行。
*/
public class MyJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "join()线程执行:" + i);
}
}
public static void main(String[] args) throws InterruptedException {
MyJoin myThread = new MyJoin();
Thread thread = new Thread(myThread,"a");
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行:" + i);
if (i == 2) {
thread.join(); //主线程阻塞,等待thread一口气执行完,主线程才能继续执行 }
}
}
}
8. sleep
/**
* sleep(Long time)方法:
* - 让线程阻塞的指定的毫秒数。
* - 指定的时间到了后,线程进入就绪状态。
* - sleep可研模拟网络延时,倒计时等。
* - 每一个对象都有一个锁,sleep不会释放锁。
*/
public class MySleep implements Runnable {
@Override
public void run() {
// 模拟倒计时
for (int i = 10; i >= 0; i--) {
try {
System.out.println(i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MySleep myThread = new MySleep();
Thread thread = new Thread(myThread);
thread.start();
}
}
9. sleep
/**
* 让线程暂时睡眠指定时长,线程进入阻塞状态。
* 睡眠时间过后线程会再进入可运行状态。
* sleep (时间)指定当前线程阻塞的毫秒数。
* sleep存在异常InterruptedException。
* sleep时间达到后线程进入就绪状态。
* sleep可以模拟网络延时,倒计时等。
* 每一个对象都有一个锁,sleep不会释放锁。
*
*/
public class Sleep {
public static void main(String[] args) {
System.out.println("Wait");
Wait.bySet(5);//让主线程等待5秒后在执行
System.out.println("start");
}
}
class Wait{
public static void bySet(long s){
for (int i = 0; i < s; i++) {
System.out.println(i+1+"秒");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
10. sleep
/**
使当前正在执行的线程停留指定的毫秒数
sleep()方法 (谁执行谁就是当前线程)
注意:run()方法中的异常只能try catch,因为父类没有抛出异常,子类不能抛出比父类更多的异常。
*/
public class DemoSleep {
public static void main(String[] args) {
// 创建线程
MyThread1 t01 = new MyThread1("黄固");
MyThread1 t02 = new MyThread1("欧阳锋");
MyThread1 t03 = new MyThread1("段智兴");
MyThread1 t04 = new MyThread1("洪七公");
//开启线程
t01.start();
t02.start();
t03.start();
t04.start();
}
}
class MyThread1 extends Thread{
public MyThread1() {
}
public MyThread1(String name) {
super(name);
}
@Override
// 重点:run()当中的异常不能throws,只能try catch
// 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
public void run() {
for (int i = 1; i < 50; i++) {
System.out.println(this.getName() + "正在打出第 - " + i + "招");
try {
Thread.sleep(500);//让当前正在执行的线程睡眠指定毫秒数
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
11. setPriority
/**
* setPriority (int newPriority)、getPriority()
* - 改变、获取线程的优先级。
* - Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
* - 线程的优先级用数据表示,范围1~10。
* - 线程的优先级高只是表示他的权重大,获取CPU执行权的几率大。
* - 先设置线程的优先级,在执行start()方法。
*
* 执行结果:优先级高的线程不一定先执行
*
*/
public class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程优先级:"
+ Thread.currentThread().getPriority());
}
public static void main(String[] args) throws InterruptedException {
MyPriority myThread = new MyPriority();
Thread thread = new Thread(myThread,"a");
Thread thread2 = new Thread(myThread,"b");
Thread thread3= new Thread(myThread,"c");
Thread thread4= new Thread(myThread,"d");
thread3.setPriority(Thread.MAX_PRIORITY);
thread.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.NORM_PRIORITY);
thread4.setPriority(8);
thread.start();
thread2.start();
thread3.start();
thread4.start();
}
}
12. setPriority
/**
线程调度模型
均分式调度模型:所有的线程轮流使用CPU的使用权,平均分配给每一个线程占用CPU的时间。
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么就会随机选择一个线程来执行,优先级高的占用CPU时间相对来说会高一点点。
Java中JVM使用的就是抢占式调度模型
getPriority():获取线程优先级
setPriority:设置线程优先级
*/
public class DemoPriority {
public static void main(String[] args) {
//创建线程
MyThread t01 = new MyThread("线程01");
MyThread t02 = new MyThread("线程02");
MyThread t03 = new MyThread("线程03");
//获取线程优先级,默认是5
// System.out.println(t01.getPriority());
// System.out.println(t02.getPriority());
// System.out.println(t03.getPriority());
//设置线程优先级
t01.setPriority(Thread.MIN_PRIORITY); //低 - 理论上来讲,最后完成
t02.setPriority(Thread.NORM_PRIORITY); //中
t03.setPriority(Thread.MAX_PRIORITY); //高 - 理论上来讲,最先完成
//开启线程
t01.start();
t02.start();
t03.start();
}
}
class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
//run方法是每个线程运行过程中都必须执行的方法
@Override
public void run() {
for (int i = 0; i < 1; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
13. setPriority
/**
* Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
* 优先级越高的线程获得CPU执行的机会越大。
* 优先级越低的线程获得CPU执行的机会越小。
* 线程的优先级用数字表示,范围从1~10
* Thread.MIN_PRIORITY= 1;
* Thread.MAX_PRIORITY = 10;
* Thread.NORM_PRIORITY = 5;
* 使用以下方式改变或获取优先级
* getPriority() . setPriority(int xxx)
*
*/
public class TestSetPriority implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行:" + i);
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new TestSetPriority(), "线程A");//通过构造方法指定线程名
Thread t2 = new Thread(new TestSetPriority(), "线程B");
//设置线程的优先级
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
System.out.println("********线程的优先级*********
# (8) BIOServer
```java
public class BIOServer {
public static void main(String[] args) throws IOException {
//创建服务端套接字 & 绑定host:port & 监听client
ServerSocket serverSocket = new ServerSocket(9999);
//等待客户端连接到来
Socket socket = serverSocket.accept();
//拿到输入流 -- client write to server
InputStream in = socket.getInputStream();
//拿到输出流 -- server write to client
OutputStream out = socket.getOutputStream();
}
}
14. state
/**
*
* Thread.State
* 线程状态。线程可以处于以下状态之一
*
* NEw:尚未启动的线程处于此状态。
* RUNNABLE在Java虚拟机中执行的线程处于此状态。
* BLOCKED被阻塞等待监视器锁定的线程处于此状态。·
* WAITING正在等待另一个线程执行特定动作的线程处于此状态。
* TIMED_WAITING正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
* TERMINATED已退出的线程处于此状态。
* 一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
*
*
*/
//观察测试线程的状态
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{//lambda表达式
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("*******************");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);//NEW
//观察启动后
thread.start();//启动线程
state = thread.getState();
System.out.println(state);//Run
while (state !=Thread.State.TERMINATED){//只要线程不终止就一直输出状态
Thread.sleep(100);
state = thread.getState();//更新线程状态
System.out.println(state);//输出状态
}
}
}
15. stop
/*
测试停止线程
1.建议线程正常停止-->利用次数,不建议死循环
2.建议使用标志位
3.不要使用stop或者destroy等过时或者JDK不建议使用的方法(存在一些BUG或者弊端)
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态
*/
public class TestStop implements Runnable{
//1.设置一个标志位 私有的 保证安全
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("线程正在运行!"+i++);
}
}
//2.设置一个公开的方法停止线程,转换标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if (i==100){//100的时候停止
testStop.stop();//调用stop方法停止运行(自己写的stop方法)
System.out.println("线程停止运行!");
//两个i是分别存活在两个线程里的独立参数,两个线程没关系,不需要break
//注意:这里是主线程到100时,停止子线程
}
}
}
}
16. interrupt/stop
/**
interrupt()方法和stop()方法
void interrupt() 终止线程睡眠
*/
public class DemoInterrupt {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName("t");
t.start();
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
// t.interrupt();
t.stop(); //强行终止线程
//缺点:容易损坏数据 线程没有保存的数据容易丢失
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---> begin");
try {
// 睡眠1年
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
// e.printStackTrace();
}
//1年之后才会执行这里
System.out.println(Thread.currentThread().getName() + "---> end");
}
}
(四) 线程并发
1. 银行问题
/**
* 模拟线程不安全示例2:银行取钱
*/
public class MyBank{
public static void main(String[] args) throws InterruptedException {
Account account = new Account(1000, "旅游基金");
new Bank(account, 500, "你").start();
new Bank(account, 600, "女朋友").start();
}
}
// 账户
class Account {
// 账户总余额
int money;
// 账户名
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
// 银行
class Bank extends Thread{
// 客户账户
Account account;
// 取得钱数
int drawingMoney;
public Bank(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
if (account.money- drawingMoney <= 0) {
System.out.println(account.name+ "钱不够了,取不了了");
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额 = 余额 - 取得钱
account.money = account.money - drawingMoney;
System.out.println(Thread.currentThread().getName() + "取了" + drawingMoney);
System.out.println(account.name + "余额为:" + account.money);
}
}
2. 解决
/**
* 同步代码块:synchronized (Obj){执行体…}
*
* Obj称之为同步监视器,可以是任何对象,但是推荐使用共享资源作为同步监视器。
* 不同方法中无需指定同步监视器,因为同步方法中的同步监视器就是this,就是这个对象本身,或者是class。
*/
public class MyBank2{
public static void main(String[] args) throws InterruptedException {
Account2 account = new Account2(1000, "旅游基金");
new Bank2(account, 500, "你").start();
new Bank2(account, 600, "女朋友").start();
}
}
// 账户
class Account2 {
// 账户总余额
int money;
// 账户名
String name;
public Account2(int money, String name) {
this.money = money;
this.name = name;
}
}
// 银行
class Bank2 extends Thread{
// 客户账户
Account2 account;
// 取得钱数
int drawingMoney;
public Bank2(Account2 account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
synchronized (account) {
if (account.money- drawingMoney < 0) {
System.out.println(account.name+ "钱不够了,取不了了");
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额 = 余额 - 取得钱
account.money = account.money - drawingMoney;
System.out.println(Thread.currentThread().getName() + "取了" + drawingMoney);
System.out.println(account.name + "余额为:" + account.money);
}
}
}
3. 购票问题
/**
* 模拟线程不安全问示例1:买票
* 执行结果:可以看到第4、3张票卖了两次,还有人买到了第0张票
*/
public class MyTicker {
public static void main(String[] args) throws InterruptedException {
BuyTicker ticker = new BuyTicker();
Thread person1Thread = new Thread(ticker, "person1");
Thread person2Thread = new Thread(ticker, "person2");
Thread person3Thread = new Thread(ticker, "person3");
person1Thread.start();
person2Thread.start();
person3Thread.start();
}
}
class BuyTicker implements Runnable{
// 车票
private int tickerNum = 10;
// 停止线程标识
boolean flag = true;
@Override
public void run() {
while (flag) {
try {
buyTicker();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buyTicker() throws InterruptedException {
// 判断是否还有票
if (tickerNum <= 0) {
flag = false;
return;
}
// 模拟延时
Thread.sleep(100);
// 买票
System.out.println(Thread.currentThread().getName() + "买到第" + tickerNum -- + "张票");
}
}
4. 解决
/**
* 同步方法:public synchronized void method(int args){执行体…}
*
* synchronized 方法控制对(synchronized修饰的方法所在的对象,就是this)“对象”的访问,
* 每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的锁才能执行,否则线程会阻塞,
* synchronized所在方法一旦执行,就独占该锁,直到方法执行完才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
* 缺陷:若将一个大的方法声明为synchronized 将会影响效率。需要修改的内容才需要锁,锁的太多,浪费资源。
*/
public class MyTicker2 {
public static void main(String[] args) throws InterruptedException {
BuyTicker2 ticker = new BuyTicker2();
Thread person1Thread = new Thread(ticker, "person1");
Thread person2Thread = new Thread(ticker, "person2");
Thread person3Thread = new Thread(ticker, "person3");
person1Thread.start();
person2Thread.start();
person3Thread.start();
}
}
class BuyTicker2 implements Runnable{
// 车票
private int tickerNum = 10;
// 停止线程标识
boolean flag = true;
@Override
public void run() {
while (flag) {
try {
buyTicker();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized void buyTicker() throws InterruptedException {
// 判断是否还有票
if (tickerNum <= 0) {
flag = false;
return;
}
// 模拟延时
Thread.sleep(100);
// 买票
System.out.println(Thread.currentThread().getName() + "买到第" + tickerNum -- + "张票");
}
}
5. 死锁问题
/**
* 死锁例子:鱼和熊不可兼得
*
* 死锁形成的原因:多个线程各自占有一个资源,并且相互等待其他线程占有的资源才能运行,
* 从而导致另个或者多个线程都在等待对方释放资源,都停止了执行。
* 某一个同步代码块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
*
* 执行结果:两个线程一直阻塞,都在等在对方释放锁,结果导致死锁。
*/
public class MyDead {
public static void main(String[] args) throws InterruptedException {
Person personA = new Person(0, "猎人A");
Person personB = new Person(1, "猎人B");
personA.start();
personB.start();
}
}
// 熊掌
class Bear {
}
// 鱼
class Fish {
}
// 人
class Person extends Thread {
// 保证资源只有一份
public static Bear bear = new Bear();
public static Fish fish = new Fish();
int choose;
String personName;
public Person (int choose, String personName) {
this.choose = choose;
this.personName = personName;
}
@Override
public void run() {
// 捕猎
try {
this.hunting();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 捕猎方法
private void hunting() throws InterruptedException {
if (choose == 0) {
synchronized (bear) {
System.out.println(personName + "想捕捉熊");
Thread.sleep(1000);
synchronized (fish) {
System.out.println(personName + "想捕捉鱼");
}
}
} else {
synchronized (fish) {
System.out.println(personName + "想捕捉鱼");
Thread.sleep(1000);
synchronized (bear) {
System.out.println(personName + "想捕捉熊");
}
}
}
}
}
# (2) CopyOnWriteArrayList
```java
public class Thread07 {
public static void main(String[] args) {
// 并发修改异常ConcurrentModificationException
// List list = new ArrayList();
// 写入时复制、多个线程调用的时候、在写入的时候避免覆盖、造成数据丢失、读写分离
List list = new CopyOnWriteArrayList();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
6. 解决死锁
/**
* 死锁例子:鱼和熊不可兼得
* 解决死锁的方法:同步代码块中不要相互嵌套,即,不要相互嵌套锁。
*/
public class MyDead2 {
public static void main(String[] args) throws InterruptedException {
Person2 personA = new Person2(0, "猎人A");
Person2 personB = new Person2(1, "猎人B");
personA.start();
personB.start();
}
}
// 熊掌
class Bear2 {
}
// 鱼
class Fish2 {
}
// 人
class Person2 extends Thread {
// 保证资源只有一份
public static Bear2 bear = new Bear2();
public static Fish2 fish = new Fish2();
int choose;
String personName;
public Person2 (int choose, String personName) {
this.choose = choose;
this.personName = personName;
}
@Override
public void run() {
// 捕猎
try {
this.hunting();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 捕猎方法
private void hunting() throws InterruptedException {
if (choose == 0) {
synchronized (bear) {
System.out.println(personName + "想捕捉熊");
Thread.sleep(1000);
}
// 把嵌套的代码块拿到外面,两个代码块并列
synchronized (fish) {
System.out.println(personName + "想捕捉鱼");
}
} else {
synchronized (fish) {
System.out.println(personName + "想捕捉鱼");
Thread.sleep(1000);
}
// 把嵌套的代码块拿到外面,两个代码块并列
synchronized (bear) {
System.out.println(personName + "想捕捉熊");
}
}
}
}
7. 银行案例
//不安全取钱
//两个人去银行取钱,账户
// 银行取钱:给账户上锁
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account3 account = new Account3(1000,"生活费");
Drawing you = new Drawing(account,500,"你");
Drawing son = new Drawing(account,1000,"儿子");
you.start();
son.start();
}
}
//账户
class Account3{
int money; //余额
String name;
public Account3(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account3 account;//账户
int drawingMoney;
//现在手里还有多少钱
int nowMoney;
public Drawing(Account3 account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
//取钱
//synchronized默认锁this.
@Override
public void run(){
synchronized (account){
//判断有没有钱
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够取不了");
return;
}
try {//sleep放大问题的发生性
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡里的余额=余额-你取的钱
account.money=account.money-drawingMoney;
//你手里的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
}
8. 购票案例
/**
*
购票案例
多线程同步
多线程的并发执行虽然可以提高程序的效率,但是,当多个线程去访问同一个资源时,也会引发一些安全问题。
并发:同一个对象被多个线程同时操作。
处理多线程问题时,多个线程访问同一个对象﹐并且某些线程还想修改这个对象﹒这时候我们就需要线程同步.
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
由于同一进程的多个线程共享同一块存储空间﹐在带来方便的同时,也带来了访问冲突问题﹐为了保证数据在方法中被访问时的正确性,
在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源﹐其他线程必须等待,使用后释放锁即可.
存在以下问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起;
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题.
我们可以通过private关键字来保证数据对象只能被方法访问﹐所以我们只需要针对方法提出一套机制,
这套机制就是synchronized关键字,目前多线程同步有常用的两种方法,分别是同步代码块和同步方法。
同步代码块
当多个线程使用同一个资源时,可以将处理共享资源的代码放置在一个代码块中,使用synchronized关键字来修饰,被称作同步代码块
上述代码中,lock 是一个锁对象称之为同步监视器,它是同步代码块的关键。当线程执行同步代码块时,首先会检查锁对象的标志位,
默认情况下标志位为1,此时线程会执行同步代码块,同时将锁对象的标志位置为0。
当一个新的线程执行到这段同步代码块时,由于锁对象的标志位为0,新线程会发生阻塞,等待当前线程执行完同步代码块后,锁对象的标志位被置为1,
新线程才能进人同步代码块执行其中的代码。循环往复,直到共享资源被处理完为止。
同步方法
在方法前面同样可以使用synchronized关键字来修饰,被修饰的方法为同步方法,它能实现和同步代码块同样的功能。具体语法格式如下:
synchronized方法控制对“对象”的访问,被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,
直到当前线程访问完毕后,其他线程才有机会执行方法。
缺陷:若将一个大的方法申明为synchronized将会影响效率。
*/
public class MySite implements Runnable{
public static void main(String[] args) {
MySite site = new MySite();
Thread t1 = new Thread(site,"林枫");
Thread t2= new Thread(site,"叶凡");
Thread t3 = new Thread(site,"石昊");
t1.start();
t2.start();
t3.start();
}
//定义票的数量
int count = 10;
//定义购买了第几张票
int sum = 0;
@Override
public void run() {
while (true){
synchronized (this){
if (count==0)
break;
count--;
sum++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"购买了第"+sum+"张票,剩余"+count+"张票");
}
}
}
}
9. 死锁案例
/**
当两个线程或者多个线程互相锁定的情况就叫死锁
避免死锁的原则
解决死锁的基本方法:
当两个线程或线程者多个互相锁定时就形成了死锁。【使用Synchronized关键字 加锁的时候,要避免死锁的发生。】
避免死锁的原则:
• 顺序上锁,反向解锁。
注意:
切记尽量不要使用同步代码块的嵌套!
JAVA线程的API中很多过时方法都构成了死锁,因此不能调用。
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
// t1和t2两个线程共享o1,o2
Thread t1 = new MyThread1(o1, o2);
Thread t2 = new MyThread2(o1, o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread {
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
}
}
}
}
10. 死锁案例
/**
*
* 死锁
*
* 多个线程各自占有一些共享资源﹐并且互相等待其他线程占有的资源才能运行﹐而导致两个或者多个线程都在等待对方释放资源﹐都停止执行的情形.某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
* 产生死锁的四个必要条件:
* 1.互斥条件:一个资源每次只能被一个进程使用。
* 2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
* 4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
* 上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生。
*/
//死锁:多个线程抱着对方需要的资源,然后形成僵持。
public class TestLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "小白");
Makeup g2 = new Makeup(1, "小灰");
g1.start();
g2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
//需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择
String girlName;//使用化妆品的人
Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆,互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {//获得口红的锁}
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
synchronized (mirror) {//一秒钟后获得镜子的锁}
System.out.println(this.girlName + "获得镜子的锁");
}
} else {
synchronized (mirror) {//获得镜子的锁}
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(2000);
synchronized (lipstick) {//一秒钟后获得口红的锁}
System.out.println(this.girlName + "获得镜子的锁");
}
}
}
}
}
(五) 线程通信
1. wait/notify/notifyAll
/**
* 线程协作
* 线程之间需要进行通信,通信有数据共享(1、文件共享;2、网络共享;3、变量共享)和线程协作两种方式。
* 线程协作指不同线程驱动的任务相互依赖,依赖一般就是对共享资源的依赖。(有共享就有竞争,有竞争就会有线程安全问题(即并发),解决并发问题就用线程同步)。
*
* 应用场景:生产者和消费者问题
* 假如仓库中只能存放一件商品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
* 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
* 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
*
* 场景分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
* 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费。
* 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
* 在生产者消费者问题中,没生产出产品之前,消费者是不能消费的,反之,消费者没消费完之前,生产者是不能生产的。
* 这就需要锁来实现线程之间的同步。仅有同步还不行,还要实现线程之间的消息传递,即通信。
*
* Java提供了解决线程之间通信问题的方法:
* 方法名 作用
* wait () 表示线程一直等待,直到其他线程通知,与 sleep 不同会释放锁
* wait (long timeOut) 指定等待的毫秒数
* notify () 唤醒一个处于等待状态的线程
* notifyAll() 唤醒同一个对象所有的调用 wait()方法的线程,优先级高的优先调度
* 注意:均是Object的方法,均只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStageException。
*
* 解决线程之间通信的方式:管程法和信号灯法。本例为管程法。
*
*/
public class MyWait {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
// 产品
class Chicken {
int id;
public Chicken (int id) {
this.id = id;
}
}
// 生产者
class Productor extends Thread {
SynContainer synContainer;
public Productor(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
synContainer.pushTo(new Chicken(i));
}
}
}
// 消费者
class Consumer extends Thread {
SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
synContainer.popTo();
}
}
}
// 容器
class SynContainer {
// 定义一个大小为10的容器
Chicken[] chickens = new Chicken[10];
// 容器计数器
int count;
// 生产者生产产品方法
public synchronized void pushTo(Chicken chicken) {
// 如果容器满了,就停止生产
if (chickens.length == count) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果容器没满,就往容器中放入产品
chickens[count] = chicken;
System.out.println("生产了" + chicken.id + "个鸡腿");
count ++;
// 通知消费者消费
this.notifyAll();
}
// 消费者消费产品方法
public synchronized Chicken popTo() {
// 如果容器中没有产品了,就停止消费
if (count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果容器有产品,就可以消费
count --;
Chicken chicken = chickens[count];
System.out.println("消费了第" + chicken.id + "个鸡退");
//只要消费了,就通知生产者生产
this.notifyAll();
return chicken;
}
}
2. 生产消费
/**
*
产者和消费者
关于Object类中的wait和notify方法。
第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象
都有的方法,因为这两个方式是Object类中自带的。
wait方法和notify方法不是通过线程对象调用,
不是这样的:t.wait(),也不是这样的:t.notify()..不对。
第二:wait()方法作用:
Object o = new Object();
o.wait();
表示:
让正在o对象上活动的线程进入等待状态,无期限等待,
直到被唤醒为止。
o.wait();方法的调用,会让“当前线程(正在o对象上
活动的线程)”进入等待状态。
第三:notify()方法作用:
Object o = new Object();
o.notify();
表示:
唤醒正在o对象上等待的线程。
还有一个notifyAll()方法:
这个方法是唤醒o对象上处于等待的所有线程。
注意:wait方法和notify方法需要建立在synchronized线程同步的基础之上。
重点:o.wait()方法会让正在o对象上活动的当前线程进入等待状态,并且释放掉t线程之前占有的o对象的锁;
o.notify()方法只会通知,不会释放之前占有的o对象的锁。
生产者和消费者模式
生产者与消费者模式是并发、多线程编程中经典的设计模式,通过wait和notifyAll方法实现。
例如:生产满了,就不能继续生产了,必须让消费线程进行消费。
消费完了,就不能继续消费了,必须让生产线程进行生产。
而消费和生产者共享的仓库,就为多线程共享的了,所以需要考虑仓库的线程安全问题。
wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
*/
public class MyWait2 {
public static void main(String[] args) {
// 创建1个仓库对象,共享的。
List list = new ArrayList();
// 创建两个线程对象
// 生产者线程
Thread t1 = new Thread(new Producer2(list));
// 消费者线程
Thread t2 = new Thread(new Consumer2(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
// 生产线程
class Producer2 implements Runnable {
// 仓库
private List list;
public Producer2(List list) {
this.list = list;
}
@Override
public void run() {
// 一直生产(使用死循环来模拟一直生产)
while(true){
// 给仓库对象list加锁。
synchronized (list){
if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。
try {
// 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能够执行到这里说明仓库是空的,可以生产
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
// 唤醒消费者进行消费
list.notifyAll();
}
}
}
}
// 消费线程
class Consumer2 implements Runnable {
// 仓库
private List list;
public Consumer2(List list) {
this.list = list;
}
@Override
public void run() {
// 一直消费
while(true){
synchronized (list) {
if(list.size() == 0){
try {
// 仓库已经空了。
// 消费者线程等待,释放掉list集合的锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能够执行到此处说明仓库中有数据,进行消费。
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
// 唤醒生产者生产。
list.notifyAll();
}
}
}
}
(六) 定时器
1. 定时器
/**
定时器
定时器的作用:
间隔特定的时间,执行特定的程序。
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
*/
public class DemoTimer {
public static void main(String[] args) {
Timer timer = new Timer();//创建Timer定时器类的对象
//匿名内部类
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("我被执行了!~");
System.gc();//告诉JVM运行完毕,可以把我回收
}
},5000);
}
}
2. 定时器和线程
/**
线程与定时器执行轨迹不同
线程与定时器之间互不抢占CPU时间片
*/
public class DemoTimer2 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "<--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//定时器实现
new Timer().schedule(new TimerTask() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.gc();//将编程垃圾的定时器进行回收
}
},5000);
}
}