【原】Java学习笔记032 - 多线程
package cn.temptation; public class Sample01 {
public static void main(String[] args) {
/*
* 【进程】:正在运行的程序,系统进行资源分配和调用的独立单位
* 每一个进程有自己的内存空间和系统资源
*
* 主流操作系统均为多任务操作系统,可以同时执行多个应用程序
* 以Window为例,通过任务管理器可以看到进程中多个正在运行的程序,即系统的多个进程
*
* 多任务操作系统表面上看起来支持多进程并发执行,例如:一边听歌一边打游戏
* 实际上不是同时执行,这些应用程序均由CPU进行执行,CPU的一个核在某一时刻只能运行一个程序,
* 即某一个时刻(时间片)只能执行一个进程,下一个时刻(时间片)切换到另一个进程的执行
* 因为CPU执行速度非常快,在很短的时间内在不同进程之间进行切换,给使用者造成一种同时执行多个程序的错觉
*
* 多进程的意义:操作系统支持多进程,没有提高执行速度,而是提高了CPU的使用率
*
* 【线程】:进程中的某个顺序控制流,是一条执行路径,是程序使用CPU的基本单位
*
* 线程必须依赖于进程存在
* 在一个进程内部可以执行多个任务,每一个任务可以看成一个线程
* 比如:Windows自带的扫雷游戏,游戏计时是一个任务,在界面上扫雷也是一个任务,这就是多任务(多线程)
*
* 进程中至少有一个线程
*
* 如果一个进程中只有一个线程(执行路径、执行任务),称为单线程程序
* 理解:类比,独唱(清唱)
* 如果一个进程中有多个线程(执行路径、执行任务),称为多线程程序
* 理解:类别,音乐会上的乐团演奏
*
* 多线程的意义:没有提高执行速度,而是提高了应用程序的使用率
*
* 注意:
* 1、多个线程共享一个进程的资源(堆内存和方法区)
* 2、对于栈内存是独立的,一个线程一个栈
* 3、进程中的多个线程抢夺CPU的资源执行,某一时间点上只能有一个线程执行,且哪个线程能抢到是随机的
*
* Java程序执行时,会产生一个进程,该进程默认创建一个线程,在这个线程上运行main主函数
*
* Java语言中对线程的操作 提供了 Thread类 以及 Runnable接口
*
* 类Thread:线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
*
* Java程序运行原理:Java命令启动Java虚拟机(JVM),启动JVM相当于启动了一个应用程序,也就是启动了一个进程
* 该进程会启动一个主线程,在主线程中调用某一个类的main主函数(程序的入口)
* 考虑JVM启动后,伴随着主线程的启动,也启动了GC垃圾回收线程,JVM启动也是多线程的
*/ Thread01 thread01 = new Thread01();
thread01.run(); for (int i = 0; i < 10; i++) {
System.out.println("主函数中的i:" + i);
} // 执行结果可以看出,Thread01类的实例对象的run方法被执行10次后,主函数的循环才开始执行,此时的程序还是单线程程序
}
} class Thread01 {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("成员方法中的i:" + i);
}
}
}
package cn.temptation; public class Sample02 {
public static void main(String[] args) {
/*
* 从JDK的API手册中得知:
* 创建新执行线程有两种方法。
* 一种方法是将类声明为Thread的子类。该子类应重写 Thread类的run方法。接下来可以分配并启动该子类的实例。
* 另一种方法是声明实现Runnable接口的类。该类然后实现 run方法。然后可以分配该类的实例,在创建 Thread时作为一个参数来传递并启动。
*
* 创建线程的方式1、继承Thread类
* 线程对象的启动,不是通过调用线程对象的run方法,而是使用start方法来启动线程对象
*
* Thread类的常用成员方法:
* 1、void run():如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
* 2、void start():使该线程开始执行;Java虚拟机调用该线程的run方法。
*
* 问题:如何理解线程对象的启动是通过start方法,而不是直接调用run方法?
* 答:Java程序执行就是一个进程启动了,至少启动了一个线程,即主函数所在的主线程
* 这个主线程是由JVM进行启动的,主线程的start方法相当于通知JVM的线程规划器,主线程已经准备好了,正在等待CPU调用该线程的run方法
* 而创建的其他线程也和主线程一样,它们的start方法相当于通知JVM的线程规划器,该线程已经准备好了,正在等待CPU调用该线程的run方法
* 只是其他线程的创建恰好写在主线程的主函数之中了
*/ // 创建Thread02类的实例对象
Thread02 thread02 = new Thread02();
// 下句语句对于多线程程序的启动,写法错误
// thread02.run();
// 正确写法:主线程被JVM调用,其他线程也被JVM调用,这样的执行才是多线程程序的执行
thread02.start(); // 多次调用线程对象的start()方法会产生执行异常
// 执行异常:java.lang.IllegalThreadStateException,指示线程没有处于请求操作所要求的适当状态时抛出的异常。
thread02.start(); for (int i = 0; i < 10; i++) {
System.out.println("主线程的主函数中的i:" + i);
}
}
} // 创建线程类
class Thread02 extends Thread {
// 重写Thread类的run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程类中的成员方法中的i:" + i);
}
} // 从执行结果可以清楚的看到,线程对象启动时,自定义的其他成员方法不会被调用
public void method() {
System.out.println("线程启动时,会调用我么?");
}
}
package cn.temptation; public class Sample03 {
public static void main(String[] args) {
/*
* Thread类的常用成员方法:
* 1、String getName():返回该线程的名称。
* 2、static Thread currentThread():返回对当前正在执行的线程对象的引用。
*/ Thread03 thread03 = new Thread03();
thread03.start(); System.out.println("-----------------"); Thread03Ex thread03Ex = new Thread03Ex();
thread03Ex.start(); System.out.println("-----------------"); // 语法错误:The method getName() is undefined for the type Sample03
// System.out.println("主线程的线程名称为:" + getName()); System.out.println("主线程的线程为:" + Thread.currentThread()); // 主线程的线程为:Thread[main,5,main]
System.out.println("主线程的线程名称为:" + Thread.currentThread().getName()); // 主线程的线程名称为:main
// 语法错误:Cannot use this in a static context
// System.out.println(this.getName());
}
} class Thread03 extends Thread {
@Override
public void run() {
System.out.println(getName()); // Thread-0
System.out.println("线程对象为:" + Thread.currentThread()); // 线程对象为:Thread[Thread-0,5,main]
System.out.println("线程对象的线程名称为:" + Thread.currentThread().getName()); // 线程对象的线程名称为:Thread-0
System.out.println("this指向的当前对象为:" + this); // this指向的当前对象为:Thread[Thread-0,5,main]
System.out.println("线程对象的线程名称为:" + this.getName()); // 线程对象的线程名称为:Thread-0
}
} class Thread03Ex extends Thread {
@Override
public void run() {
System.out.println(getName()); // Thread-1
System.out.println("线程对象为:" + Thread.currentThread()); // 线程对象为:Thread[Thread-1,5,main]
System.out.println("线程对象的线程名称为:" + Thread.currentThread().getName()); // 线程对象的线程名称为:Thread-1
System.out.println("this指向的当前对象为:" + this); // this指向的当前对象为:Thread[Thread-1,5,main]
System.out.println("线程对象的线程名称为:" + this.getName()); // 线程对象的线程名称为:Thread-1
}
} // 查看Thread类的getName()方法的源码
//public final String getName() {
// return name;
//} //public Thread() {
// init(null, null, "Thread-" + nextThreadNum(), 0);
//} //private void init(ThreadGroup g, Runnable target, String name,
// long stackSize) {
// init(g, target, name, stackSize, null, true);
//} //private void init(ThreadGroup g, Runnable target, String name,
// long stackSize, AccessControlContext acc,
// boolean inheritThreadLocals) {
// if (name == null) {
// throw new NullPointerException("name cannot be null");
// }
//
// this.name = name;
//
// ...
//} //private static int threadInitNumber; // 静态的成员变量 --- 类变量(对象们的变量)
//private static synchronized int nextThreadNum() {
// return threadInitNumber++;
//}
package cn.temptation; public class Sample04 {
public static void main(String[] args) {
/*
* Thread类的常用构造函数:
* Thread(String name):分配新的 Thread 对象。
*
* Thread类的常用成员方法:
* void setName(String name):改变线程名称,使之与参数 name 相同。
*/ // 使用Thread类的构造函数设置线程对象的名称
Thread04 thread04 = new Thread04("自定义线程");
thread04.start();
System.out.println(thread04.getName()); // 自定义线程 Thread04Ex thread04Ex = new Thread04Ex();
// thread04Ex.setName("又一个自定义线程");
// thread04Ex.start(); // 问题:如果将19行和20行语句颠倒,是否执行出错?
// 答:不会出错,和之前的效果一致
thread04Ex.start();
thread04Ex.setName("又一个自定义线程");
}
} class Thread04 extends Thread {
// 构造函数(无参)
public Thread04() {
super();
} // 构造函数(有参)
public Thread04(String name) {
super(name);
}
} class Thread04Ex extends Thread {
@Override
public void run() {
System.out.println("当前线程的名称为:" + Thread.currentThread().getName()); // 当前线程的名称为:又一个自定义线程
}
}
package cn.temptation; public class Sample05 {
public static void main(String[] args) {
/*
* Thread类的常用成员方法:
* static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
*/
// 注意:第10行的语句和第15行、16行语句无法测量出另一个线程执行耗时,因为是多线程抢占CPU资源
// long start = System.currentTimeMillis(); Thread05 thread05 = new Thread05();
thread05.start(); // long end = System.currentTimeMillis();
// System.out.println("程序执行耗时:" + (end - start) + "秒"); Thread05Ex thread05Ex = new Thread05Ex();
thread05Ex.start(); // 执行结果:
// 线程Thread05开始执行...
// 线程Thread05Ex开始执行...
// 线程Thread05休眠1秒后,再执行...
// 程序执行耗时:1000毫秒
// 线程Thread05Ex休眠5秒后,再执行... // 可以清楚的得知,线程在休眠时,不会独占CPU资源,且不会丢失对当前线程的监视,等到休眠结束时又开始执行
}
} class Thread05 extends Thread {
@Override
public void run() {
long start = System.currentTimeMillis(); System.out.println("线程Thread05开始执行..."); try {
// 设置线程休眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("线程Thread05休眠1秒后,再执行..."); long end = System.currentTimeMillis();
System.out.println("程序执行耗时:" + (end - start) + "毫秒");
}
} class Thread05Ex extends Thread {
@Override
public void run() {
System.out.println("线程Thread05Ex开始执行..."); try {
// 设置线程休眠5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("线程Thread05Ex休眠5秒后,再执行...");
}
}
package cn.temptation; public class Sample06 {
public static void main(String[] args) {
/*
* Thread类的常用成员方法:
* boolean isAlive():测试线程是否处于活动状态。
*/
Thread06 thread06 = new Thread06();
System.out.println("在线程对象的start方法调用之前,自定义线程的活动状态为:" + thread06.isAlive());
// 在线程对象的start方法调用之前,自定义线程的活动状态为:false
thread06.start(); // 在自定义线程启动后,让主线程休眠10秒,等待自定义线程的操作都完成再观察自定义线程的活动状态
try {
// 下句语句想在自定义线程启动后且开始休眠时观察自定义线程的活动状态,但是不太准确
// System.out.println(thread06.isAlive());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("在线程对象的start方法调用之后,自定义线程的活动状态为:" + thread06.isAlive());
// 在线程对象的start方法调用之后,自定义线程的活动状态为:false
}
} class Thread06 extends Thread {
@Override
public void run() {
System.out.println("自定义线程的活动状态为:" + isAlive()); // 自定义线程的活动状态为:true try {
Thread.sleep(5000);
// 下句语句其实看不到效果,因为此时安静的等待线程休眠结束
// System.out.println("执行线程休眠后,自定义线程的活动状态为:" + isAlive());
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("自定义线程结束休眠后,自定义线程的活动状态为:" + isAlive());
}
}
package cn.temptation; public class Sample07 {
public static void main(String[] args) {
/*
* Thread类的常用成员方法:
* void join():等待该线程终止。
*/
Thread07 thread07 = new Thread07();
thread07.start(); // 现象:下面的语句块如果不写,随机实心五角星和空心五角星交替呈现;写后,实心五角星全部先呈现,空心五角星再呈现
try {
// 下句语句表示等待Thread07线程对象运行终止后,后续的线程对象才开始执行
thread07.join();
} catch (InterruptedException e) {
e.printStackTrace();
} Thread07Ex thread07Ex = new Thread07Ex();
thread07Ex.start();
}
} class Thread07 extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("★");
}
}
} class Thread07Ex extends Thread {
@Override
public void run() {
for (int j = 0; j < 20; j++) {
System.out.println("☆");
}
}
}
package cn.temptation; public class Sample08 {
public static void main(String[] args) {
Thread08 thread08 = new Thread08();
thread08.start(); // 注意:
// 非主线程对象的静态代码块、构造代码块、构造函数均被main主线程调用,只有成员方法的run()方法被非主线程对象自己调用
// 理解:非主线程对象创建时靠主线程调用,非主线程对象启动时靠线程对象自身
}
} class Thread08 extends Thread {
// 静态代码块
static {
System.out.println("静态代码块的使用,对应的线程名称:" + Thread.currentThread().getName()); // 静态代码块的使用,对应的线程名称:main
System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[main,5,main]
// 语法错误:Cannot use this in a static context
// System.out.println(this);
} // 构造代码块
{
System.out.println("构造代码块的使用,对应的线程名称:" + Thread.currentThread().getName()); // 构造代码块的使用,对应的线程名称:main
System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[main,5,main]
System.out.println("当前对象:" + this); // 当前对象:Thread[Thread-0,5,main]
} // 构造函数
public Thread08() {
System.out.println("构造函数的使用,对应的线程名称:" + Thread.currentThread().getName()); // 构造函数的使用,对应的线程名称:main
System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[main,5,main]
System.out.println("当前对象:" + this); // 当前对象:Thread[Thread-0,5,main]
} // 成员方法
@Override
public void run() {
System.out.println("成员方法run()的使用,对应的线程名称:" + Thread.currentThread().getName()); // 成员方法run()的使用,对应的线程名称:Thread-0
System.out.println("当前线程对象:" + Thread.currentThread()); // 当前线程对象:Thread[Thread-0,5,main]
System.out.println("当前对象:" + this); // 当前对象:Thread[Thread-0,5,main]
}
}
package cn.temptation; public class Sample09 {
public static void main(String[] args) {
/*
* 创建线程的方式2、实现Runnable接口(推荐)
*
* 接口 Runnable:应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run的无参数方法。
* 设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。
* Runnable为非 Thread 子类的类提供了一种激活方式。
* 通过实例化某个 Thread 实例并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。
* 大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。
* 这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。
*
*
* Runnable接口的常用成员方法:
* void run():使用实现接口 Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。
*
* Thread类的常用构造函数:
* Thread(Runnable target):分配新的 Thread 对象。
*
* 注意:采用实现Runnable接口方式的线程对象创建方法在线程对象启动时,不需要直接调用线程对象的run方法,
* 借助于Thread类对象的构造函数以及start方法
*
* 优点:
* 1、将线程任务(实现Runnable接口的类)从线程类中分离出来,单独封装,这是面向对象思想的体现
* 2、因为Java的类是单继承的,为了避免这种继承的局限,通过接口提高了操作的灵活性
*/ Thread09 thread09 = new Thread09();
// 下句语句写法错误,这样写,还是单线程程序
// thread09.run();
// 正确写法:实现了Runnable接口的线程对象,需要借助于Thread类
Thread thread = new Thread(thread09);
thread.start(); for (int i = 0; i < 10; i++) {
System.out.println("主线程的主函数中的i:" + i);
}
}
} class Thread09 implements Runnable {
@Override
public void run() {
System.out.println("自定义线程对象启动了...");
}
}
package cn.temptation; public class Sample10 {
public static void main(String[] args) {
// 思考创建线程的方式1 和 创建线程的方式2 // 形式1
// Thread10 thread10 = new Thread10();
// thread10.start();
//
// (new Thread10()).start(); // (new Thread() {
// @Override
// public void run() {
// System.out.println("通过匿名对象创建自定义线程");
// }
// }).start(); // 通过匿名对象创建自定义线程 System.out.println("----------------------"); // 形式2
// Thread10Ex thread10Ex = new Thread10Ex();
// Thread thread = new Thread(thread10Ex);
// thread.start(); // Thread thread = new Thread(new Thread10Ex());
// thread.start(); // (new Thread(new Thread10Ex())).start(); // (new Thread(new Runnable() {
// @Override
// public void run() {
// System.out.println("通过匿名内部类创建自定义线程");
// }
// })).start(); // 通过匿名内部类创建自定义线程 System.out.println("----------------------"); // 混合形式1 和 形式2
(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("通过匿名内部类创建自定义线程");
}
}) {
@Override
public void run() {
System.out.println("通过匿名对象创建自定义线程");
}
}).start(); // 通过匿名对象创建自定义线程
}
} class Thread10 extends Thread {
@Override
public void run() {
System.out.println("自定义线程对象");
}
} class Thread10Ex implements Runnable {
@Override
public void run() {
System.out.println("自定义线程对象");
}
}
package cn.temptation; public class Sample11 {
public static void main(String[] args) {
// 需求:模拟三个窗口售卖20张票(使用创建方式1) (new Thread11("窗口1")).start();
(new Thread11("窗口2")).start();
(new Thread11("窗口3")).start(); // 从执行结果(使用局部变量的写法)可以看出,三个窗口(三个线程)各自售卖20张票,自己卖自己的
// 即这三个线程没有共享20张票 // 从执行结果(使用静态成员变量的写法)可以看出,三个窗口(三个线程)共同售卖20张票
// 即这三个线程共享20张票
}
} class Thread11 extends Thread {
// 成员变量:非静态的成员变量是属于对象的,无法做到不同对象间的共享
// private int tickets = 20;
// 成员变量:静态的成员变量是属于对象们(类)的,可以做到不同对象间的共享
private static int tickets = 20; // 构造函数
public Thread11() {
super();
} public Thread11(String name) {
super(name);
} // 成员方法
@Override
public void run() {
// 局部变量:局部变量作用范围在方法中,使用时随着方法的调用而产生,无法做到不同对象间的共享
// int tickets = 20; while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在售卖" + (tickets--) + "张票");
}
}
}
package cn.temptation; public class Sample12 {
public static void main(String[] args) {
// 需求:模拟三个窗口售卖20张票(使用创建方式2) /*
* Thread类的常用构造函数:
* Thread(Runnable target, String name):分配新的 Thread 对象。
*/ Thread12 thread12 = new Thread12(); (new Thread(thread12, "窗口1")).start();
(new Thread(thread12, "窗口2")).start();
(new Thread(thread12, "窗口3")).start(); // 对比上一个例子 和 这个例子
// 显然,上一个例子start()方法调用的是new创建的线程对象自身的run()方法
// 这个例子start()方法调用的是构造函数传入的目标线程对象的run()方法
}
} class Thread12 implements Runnable {
// 成员变量:因为三个线程对象调用的是同一个线程任务对象的run方法,所以这里实际就是用的这一个对象的成员变量,看起来像是被共享
private int tickets = 20; @Override
public void run() {
// 局部变量
// int tickets = 20; while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在售卖" + (tickets--) + "张票");
}
}
}
package cn.temptation; public class Sample13 {
public static void main(String[] args) {
// 需求:模拟三个窗口售卖20张票(使用Thread类的继承子类按给Thread类的构造函数传入Runnable类型的参数的方式使用) Thread13 thread13 = new Thread13(); (new Thread(thread13, "窗口1")).start();
(new Thread(thread13, "窗口2")).start();
(new Thread(thread13, "窗口3")).start(); // 注意:
// 这样的写法,功能也可以实现,但是很别扭
// 对比这三个例子,Thread11对象具有start()方法,可以自己启动
// Thread12对象不具有start方法,需要借助于Thread类的对象启动来调用它的run()方法
// Thread13对象具有start()方法,但是自己不启动,却借助于Thread类的对象启动来调用它的run()方法,明显多此一举
}
} class Thread13 extends Thread {
// 成员变量
private int tickets = 20; // 构造函数
public Thread13() {
super();
} public Thread13(String name) {
super(name);
} // 成员方法
@Override
public void run() {
// 局部变量
// int tickets = 20; while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在售卖" + (tickets--) + "张票");
}
}
}
package cn.temptation; public class Sample14 {
public static void main(String[] args) {
/*
* 守护线程(后台线程):程序运行时在后台提供一种通用服务的线程,且这种线程不属于程序中不可获取的部分
* 例如:Java中的GC线程
*
* 创建出的线程默认是应用线程(前台线程),Java程序中只要有应用线程(前台线程)在运行,进程就不会结束;
* 反之,如果只有后台线程运行,这个进程就会结束
*
* Thread类的常用成员方法:
* 1、boolean isDaemon():测试该线程是否为守护线程。
* 2、void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
*/ System.out.println("主线程是后台线程嘛?" + Thread.currentThread().isDaemon()); // 主线程是后台线程嘛?false Thread14 thread14 = new Thread14();
Thread thread = new Thread(thread14, "自定义线程");
System.out.println("自定义线程是后台线程嘛?" + thread.isDaemon()); // 自定义线程是后台线程嘛?false
// 下句语句如果注释掉,自定义线程的run方法中一直执行死循环
// 下句语句不注释,自定义线程的run方法会执行若干条语句,然后整个进程结束
thread.setDaemon(true);
System.out.println("自定义线程是后台线程嘛?" + thread.isDaemon()); // 自定义线程是后台线程嘛?true
thread.start(); // 注意:setDaemon方法在start方法之后进行设置,会产生执行异常
// 执行异常:java.lang.IllegalThreadStateException
// thread.setDaemon(true);
}
} class Thread14 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("自定义线程的run方法");
}
}
}
package cn.temptation; public class Sample15 {
public static void main(String[] args) {
/*
* 线程的生命周期:(五个状态,两种流程)
* 1、新建状态(new):创建一个线程后,这个线程就出于新建状态,此时该线程对象不能运行,由JVM为其分配内容
* 2、就绪状态(runnable):线程对象调用start()方法,该线程对象就进入就绪状态,此时具备了运行条件,是否运行等系统调用
* 3、运行状态(running):处于就绪状态的线程对象获取CPU使用权,开始执行run()方法中的语句,此时该线程对象就处于运行状态
* 4、阻塞状态(blocked):线程对象在某些特殊情况下,比如执行耗时的输入输出操作时,会放弃CPU使用权,进入阻塞状态
* 当引起阻塞原因被消除后,线程对象才可以转为就绪状态
* 5、死亡状态(terminated):线程对象执行完毕后,进入死亡状态,此时线程对象不能运行,也不能转换为其他状态
*
* 两种流程:
* 1、新建 -----> 就绪 -----> 运行 -----> 死亡
* 2、新建 -----> 就绪 -----> 运行 -----> 阻塞 -----> 就绪 -----> 运行 -----> 死亡
*/
}
}
package cn.temptation; public class Sample16 {
public static void main(String[] args) {
/*
* 线程的优先级:用1~10之间的整数表示,数字越大表示优先级越高
*
* Thread类的常用字段:
* static int MAX_PRIORITY:线程可以具有的最高优先级。 10
* static int MIN_PRIORITY:线程可以具有的最低优先级。 1
* static int NORM_PRIORITY:分配给线程的默认优先级。 5
*
* Thread类的常用成员方法:
* void setPriority(int newPriority):更改线程的优先级。
*
* 注意:
* 只能把线程优先级作为提高线程执行概率的手段,无法依赖线程优先级的设置
*/ Thread threadMax = new Thread(new Thread16(), "线程优先级最高 ★");
Thread threadMin = new Thread(new Thread16(), "线程优先级最低 ☆"); threadMax.setPriority(Thread.MAX_PRIORITY);
threadMin.setPriority(Thread.MIN_PRIORITY); threadMax.start();
threadMin.start();
}
} class Thread16 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
package cn.temptation; public class Sample17 {
public static void main(String[] args) {
/*
* 线程让步:让当前正在运行的线程暂停
*
* Thread类的常用成员方法:
* static void yield():暂停当前正在执行的线程对象,并执行其他线程。
* 这里的暂停会放弃CPU资源,且放弃CPU资源的时间不确定,可能刚放弃就又获得CPU资源,也可能放弃了一会儿才重新获取CPU资源
*
* 线程让步 和 线程休眠的区别:
* 相同点:线程让步 和 线程休眠都可以让当前正在运行的线程暂停
* 不同点:
* 线程让步:不会阻塞线程,只能让线程的状态转换为就绪状态,让CPU重新调度
* 线程休眠:会阻塞线程,将线程状态转换为阻塞状态
*/ Thread17 thread17 = new Thread17(); Thread thread = new Thread(thread17, "自定义线程");
thread.start(); for (int i = 0; i < 20; i++) {
System.out.println("主线程中的i:" + i);
}
}
} class Thread17 implements Runnable {
@Override
public void run() {
for (int j = 0; j < 20; j++) {
System.out.println(Thread.currentThread().getName() + "的j:" + j);
if (j == 10) {
Thread.yield();
}
}
}
}
package cn.temptation; public class Sample18 {
public static void main(String[] args) {
/*
* 线程安全:包括变量安全 和 线程同步 两个方面
*
* 1、变量安全:
* 进程中多个线程同时运行,这些线程可能会在某一时刻同时运行某一段代码,
* 如果每次运行结果和单线程运行的结果一致,且其他变量的值也和预期一样,则称为变量安全;否则就是变量不安全
*/ // 变量安全:
// 问题:静态的成员变量是线程安全的么?
// 答:不论单例还是多例,多线程中的静态的成员变量都是不安全
// 因为静态的成员变量就是对象们的变量(类变量),为所有对象共享,共享一份内存,一旦静态成员变量被修改,其他对象均对修改可见
Thread18 thread18 = new Thread18(); for (int i = 0; i < 1000; i++) {
// 调用run()方法所在的线程任务对象是单例的
// (new Thread(thread18, "自定义线程")).start(); // 调用run()方法所在的线程任务对象是多例的
(new Thread(new Thread18(), "自定义线程")).start();
} // 执行结果,如果线程安全,应该显示:
// 自定义线程获取 i 的值为:3
// 自定义线程获取 i * 10 的值为:50 // 实际得到的结果中,有
// 自定义线程获取 i 的值为:5
// 自定义线程获取 i * 10 的值为:30
}
} class Thread18 implements Runnable {
// 静态成员变量
private static int i = 2; @Override
public void run() {
i = 3;
System.out.println(Thread.currentThread().getName() + "获取 i 的值为:" + i);
i = 5;
System.out.println(Thread.currentThread().getName() + "获取 i * 10 的值为:" + i * 10);
}
}
package cn.temptation; public class Sample19 {
public static void main(String[] args) {
/*
* 线程安全:包括变量安全 和 线程同步 两个方面
*
* 1、变量安全:
* 进程中多个线程同时运行,这些线程可能会在某一时刻同时运行某一段代码,
* 如果每次运行结果和单线程运行的结果一致,且其他变量的值也和预期一样,则称为变量安全;否则就是变量不安全
*/ // 变量安全:
// 问题:非静态的成员变量是线程安全的么?
// 答:多线程中的非静态成员变量对于单例调用时,线程不安全;对于多例调用时,线程安全
// 因为单例时,每个线程都在修改同一个线程任务对象的成员变量;多例时,每个线程都在修改各自不同的线程任务对象的成员变量
Thread19 thread19 = new Thread19(); for (int i = 0; i < 1000; i++) {
// 调用run()方法所在的线程任务对象是单例的
// (new Thread(thread19, "自定义线程")).start(); // 调用run()方法所在的线程任务对象是多例的
(new Thread(new Thread19(), "自定义线程")).start();
} // 执行结果,如果线程安全,应该显示:
// 自定义线程获取 i 的值为:3
// 自定义线程获取 i * 10 的值为:50 // 实际得到的结果中,有
// 自定义线程获取 i 的值为:5
// 自定义线程获取 i * 10 的值为:30
}
} class Thread19 implements Runnable {
// 非静态成员变量
private int i = 2; @Override
public void run() {
i = 3;
System.out.println(Thread.currentThread().getName() + "获取 i 的值为:" + i);
i = 5;
System.out.println(Thread.currentThread().getName() + "获取 i * 10 的值为:" + i * 10);
}
}
package cn.temptation; public class Sample20 {
public static void main(String[] args) {
/*
* 线程安全:包括变量安全 和 线程同步 两个方面
*
* 1、变量安全:
* 进程中多个线程同时运行,这些线程可能会在某一时刻同时运行某一段代码,
* 如果每次运行结果和单线程运行的结果一致,且其他变量的值也和预期一样,则称为变量安全;否则就是变量不安全
*/ // 变量安全:
// 问题:局部变量是线程安全的么?
// 答:不论单例还是多例,多线程中的局部变量都是安全
// 因为每个线程执行时都会把局部变量放在各自栈的工作内存中,线程之间不共享局部变量的,所以不存在变量安全的问题
Thread20 thread20 = new Thread20(); for (int i = 0; i < 1000; i++) {
// 调用run()方法所在的线程任务对象是单例的
// (new Thread(thread20, "自定义线程")).start(); // 调用run()方法所在的线程任务对象是多例的
(new Thread(new Thread19(), "自定义线程")).start();
}
}
} class Thread20 implements Runnable {
@Override
public void run() {
int i = 3;
System.out.println(Thread.currentThread().getName() + "获取 i 的值为:" + i);
i = 5;
System.out.println(Thread.currentThread().getName() + "获取 i * 10 的值为:" + i * 10);
}
}
package cn.temptation; public class Sample21 {
public static void main(String[] args) {
/*
* 线程安全:包括变量安全 和 线程同步 两个方面
*
* 1、变量安全:
* 进程中多个线程同时运行,这些线程可能会在某一时刻同时运行某一段代码,
* 如果每次运行结果和单线程运行的结果一致,且其他变量的值也和预期一样,则称为变量安全;否则就是变量不安全
*
* 类 ThreadLocal<T>:提供了线程局部 (thread-local) 变量。
* 这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。
* ThreadLocal实例通常是类中的 private static字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
*
* ThreadLocal<T>类的常用成员方法:
* 1、T get():返回此线程局部变量的当前线程副本中的值。
* 2、protected T initialValue():返回此线程局部变量的当前线程的“初始值”。
* 3、void remove():移除此线程局部变量当前线程的值。
* 4、void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
*/ Thread21 thread21 = new Thread21(); for (int i = 0; i < 1000; i++) {
// 调用run()方法所在的线程任务对象是单例的
// (new Thread(thread21, "自定义线程")).start(); // 调用run()方法所在的线程任务对象是多例的
(new Thread(new Thread21(), "自定义线程")).start();
}
}
} class Thread21 implements Runnable {
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 2;
}
}; @Override
public void run() {
threadLocal.set(threadLocal.get() + 1); // 2 + 1
System.out.println(Thread.currentThread().getName() + "获取 线程局部变量 的值为:" + threadLocal.get()); // 3 // 移除此线程局部变量当前线程的值。
threadLocal.remove(); threadLocal.set(threadLocal.get() + 2); // 2 + 2
System.out.println(Thread.currentThread().getName() + "获取 线程局部变量 * 10 的值为:" + threadLocal.get() * 10); //
}
}
package cn.temptation; public class Sample22 {
public static void main(String[] args) {
/*
* 线程安全:包括变量安全 和 线程同步 两个方面
*
* 2、线程同步:
* 代码中的业务逻辑是一个原子性的动作,一旦分割执行就可能导致丧失其本来意义
* 在多线程环境中,运行线程被线程调度器(线程规划器)暂停的可能性随时存在,这就给原子性的操作造成了潜在的危险
* 在多线程的操作中,必须启动线程同步机制,即在一个线程执行完这组动作之前,其他线程必须不能进入这段代码
*
* 问题:为什么需要进行线程同步?
* 答:
* 1、多个线程再操作同一份共享数据
* 2、操作同一份共享数据的代码有多个
*
*/ Thread22 thread22 = new Thread22(); (new Thread(thread22, "窗口1")).start();
(new Thread(thread22, "窗口2")).start();
(new Thread(thread22, "窗口3")).start(); // 执行结果中,有窗口显示售卖第0张票,还有窗口显示售卖第-1张票
}
} class Thread22 implements Runnable {
// 成员变量
private int tickets = 20; @Override
public void run() {
while (tickets > 0) {
// 整个动作包括 动作1 和 动作2,但是多线程的操作会分割这两个动作,即破坏了动作的原子性
// 动作1、模拟实际场景,查看票数等操作会花费一些时间
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} // 动作2、卖票
System.out.println(Thread.currentThread().getName() + "正在售卖第" + (tickets--) + "张票");
}
}
}
package cn.temptation; public class Sample23 {
public static void main(String[] args) {
/*
* 线程安全:包括变量安全 和 线程同步 两个方面
*
* 问题:如何进行线程同步?
* 答: 能保证动作的完整性(原子性),Java中提供了 同步关键字 synchronized
*
* 线程同步方式1、使用同步代码块
* 格式:
* synchronized (lock) {
*
* }
*
* lock:锁对象,默认标志位为1
*
* 线程执行同步代码块的顺序:
* 1、检查锁对象的标志位,默认为1
* 2、执行同步代码块,将标志位设置为0
* 3、当其他线程对象执行到同步代码块时,因为锁对象标志位为0,其他线程对象发生阻塞
* 4、等待同步代码块执行完毕,再将标志位设置位为1,其他线程对象才可以进入同步代码块
*
* 理解:类比上厕所把门锁上了
*/
Thread23 thread23 = new Thread23(); (new Thread(thread23, "窗口1")).start();
(new Thread(thread23, "窗口2")).start();
(new Thread(thread23, "窗口3")).start();
}
} class Thread23 implements Runnable {
// 成员变量
private int tickets = 20;
// 随便创建一个对象
Object lock = new Object(); @Override
public void run() {
while (true) {
// 使用同步代码块
// 写法1、使用自定义对象
// synchronized (lock) {
// 写法2、使用this,即当前线程对象
synchronized (this) { // this:cn.temptation.Thread23@790d3283
if (tickets > 0) {
// 整个动作包括 动作1 和 动作2,但是多线程的操作会分割这两个动作,即破坏了动作的原子性
// 动作1、模拟实际场景,查看票数等操作会花费一些时间
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} // 动作2、卖票
System.out.println(Thread.currentThread().getName() + "正在售卖第" + (tickets--) + "张票");
} else {
break;
}
}
}
}
}
package cn.temptation; public class Sample24 {
public static void main(String[] args) {
/*
* 线程安全:包括变量安全 和 线程同步 两个方面
*
* 问题:如何进行线程同步?
* 答: 能保证动作的完整性(原子性),Java中提供了 同步关键字 synchronized
*
* 线程同步方式2、使用同步方法
* 格式:
* synchronized 返回值类型 方法名(参数列表) {
*
* }
*
* 使用synchronized修饰的方法在某一个时刻只允许一个线程对象的调用,其他调用该方法的线程对象都会发生阻塞,
* 直到该方法执行完毕,其他线程对象才能调用
*
* 问题:同步代码块 和 同步方法 的区别:
* 1、同步代码块的锁是任意对象(可以是this)
* 2、同步方法的锁是this(当前线程对象)
*
*
* 注意:线程同步的优缺点
* 优点:解决了动作的原子性问题(保障了线程安全)
* 缺点:多个线程对象需要判断锁,比较消耗资源
*/ Thread24 thread24 = new Thread24(); (new Thread(thread24, "窗口1")).start();
(new Thread(thread24, "窗口2")).start();
(new Thread(thread24, "窗口3")).start();
}
} class Thread24 implements Runnable {
// 成员变量
private int tickets = 20; @Override
public void run() {
while (true) {
// 调用同步方法
sale(); if (tickets <= 0) {
break;
}
}
} // 定义同步方法
public synchronized void sale() {
if (tickets > 0) {
// 整个动作包括 动作1 和 动作2,但是多线程的操作会分割这两个动作,即破坏了动作的原子性
// 动作1、模拟实际场景,查看票数等操作会花费一些时间
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} // 动作2、卖票
System.out.println(Thread.currentThread().getName() + "正在售卖第" + (tickets--) + "张票");
}
}
}
package cn.temptation; public class Sample25 {
public static void main(String[] args) {
// 多线程的典型问题:生产者、消费者问题
Message msg = new Message(); (new Thread(new Producer(msg), "生产者")).start();
(new Thread(new Consumer(msg), "消费者")).start(); // 执行效果可以看到:
// 1、设置的数据错位;
// 2、数据无法生产一条、消费一条
}
} // 作为生成和消费的对象 ----- 信息类
class Message {
// 成员变量
// 标题
private String title;
// 内容
private String content; // 成员方法
public String getTitle() {
return title;
} public void setTitle(String title) {
this.title = title;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
}
} // 生产者
class Producer implements Runnable {
// 成员变量
private Message msg = null; // 构造函数
public Producer() {
super();
} public Producer(Message msg) {
super();
this.msg = msg;
} // 成员方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 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 = null; // 构造函数
public Consumer() {
super();
} public Consumer(Message msg) {
super();
this.msg = msg;
} @Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(this.msg.getTitle() + "----->" + this.msg.getContent());
}
}
}
package cn.temptation; public class Sample26 {
public static void main(String[] args) {
// 多线程的典型问题:生产者、消费者问题
MessageEx msgEx = new MessageEx(); (new Thread(new ProducerEx(msgEx), "生产者")).start();
(new Thread(new ConsumerEx(msgEx), "消费者")).start(); // 执行效果可以看到:
// 1、数据无法生产一条、消费一条
}
} // 作为生成和消费的对象 ----- 信息类
class MessageEx {
// 成员变量
// 标题
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();
} this.content = content;
} // 同步方法进行取值
public synchronized void get() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(this.title + "----->" + this.content);
}
} // 生产者
class ProducerEx implements Runnable {
// 成员变量
private MessageEx msgEx = null; // 构造函数
public ProducerEx() {
super();
} public ProducerEx(MessageEx msgEx) {
super();
this.msgEx = msgEx;
} // 成员方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) { // 奇数时生产的是一种信息
this.msgEx.set("★", "★★★");
} else { // 偶数时生产的是另一种信息
this.msgEx.set("☆", "☆☆☆");
}
}
}
} // 消费者
class ConsumerEx implements Runnable {
// 成员变量
private MessageEx msgEx = null; // 构造函数
public ConsumerEx() {
super();
} public ConsumerEx(MessageEx msgEx) {
super();
this.msgEx = msgEx;
} @Override
public void run() {
for (int i = 0; i < 20; i++) {
this.msgEx.get();
}
}
}
package cn.temptation; public class Sample27 {
public static void main(String[] args) {
// 多线程的典型问题:生产者、消费者问题
/*
* Object类的常用成员方法:
* 1、void wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
* 2、void notify():唤醒在此对象监视器上等待的单个线程。
*/ MessageFinal msgFinal = new MessageFinal(); (new Thread(new ProducerFinal(msgFinal), "生产者")).start();
(new Thread(new ConsumerFinal(msgFinal), "消费者")).start(); // 执行效果可以看到之前列出的两个问题都解决
}
} // 作为生成和消费的对象 ----- 信息类
class MessageFinal {
// 成员变量
// 标题
private String title;
// 内容
private String content;
// 判断标识:设置为true,表示可以生产,不能消费;设置为false,表示可以消费,不能生产
private boolean flag = true; // 成员方法
// 同步方法进行赋值
public synchronized void set(String title, String content) {
if (this.flag == false) { // 可以消费,不能生产
try {
// 下面两条语句效果相同
// this.wait(); // 等待
super.wait(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
} this.title = title; try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} this.content = content; this.flag = false; // 已经完成了生产,可以进行消费了,修改标识位 // 下面两条语句效果相同
// this.notify(); // 唤醒等待线程
super.notify(); // 唤醒等待线程
} // 同步方法进行取值
public synchronized void get() {
if (this.flag == true) { // 可以生产,不能消费
try {
// 下面两条语句效果相同
// this.wait(); // 等待
super.wait(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
} try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(this.title + "----->" + this.content); this.flag = true; // 已经完成了消费,可以进行生产了,修改标识位 // 下面两条语句效果相同
// this.notify(); // 唤醒等待线程
super.notify(); // 唤醒等待线程
}
} // 生产者
class ProducerFinal implements Runnable {
// 成员变量
private MessageFinal msgFinal = null; // 构造函数
public ProducerFinal() {
super();
} public ProducerFinal(MessageFinal msgFinal) {
super();
this.msgFinal = msgFinal;
} // 成员方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) { // 奇数时生产的是一种信息
this.msgFinal.set("★", "★★★");
} else { // 偶数时生产的是另一种信息
this.msgFinal.set("☆", "☆☆☆");
}
}
}
} // 消费者
class ConsumerFinal implements Runnable {
// 成员变量
private MessageFinal msgFinal = null; // 构造函数
public ConsumerFinal() {
super();
} public ConsumerFinal(MessageFinal msgFinal) {
super();
this.msgFinal = msgFinal;
} @Override
public void run() {
for (int i = 0; i < 20; i++) {
this.msgFinal.get();
}
}
}
【原】Java学习笔记032 - 多线程的更多相关文章
- Java学习笔记之——多线程
多线程编程 程序: 进程:一个程序运行就会产生一个进程 线程:进程的执行流程,一个进程至少有一个线程,称为主线程 如:QQ聊着天,同时在听音乐 一个进程可以有多个线程,多个线程共享同一个进程的资源 线 ...
- Java学习笔记:多线程(一)
Java中线程的五种状态: 新建状态(New) 就绪状态(Runnable) 运行状态(Running) 阻塞状态(Blocked) 凋亡状态(Dead) 其中阻塞状态(Blocked)又分为三种: ...
- java学习笔记(5)多线程
一.简介(过段时间再写,多线程难度有点大) --------------------------------------- 1.进程:运行时的概念,运行的应用程序 2.线程:应用程序内部并发执行的代码 ...
- Java 学习笔记(11)——多线程
Java内部提供了针对多线程的支持,线程是CPU执行的最小单位,在多核CPU中使用多线程,能够做到多个任务并行执行,提高效率. 使用多线程的方法 创建Thread类的子类,并重写run方法,在需要启动 ...
- Java学习笔记:多线程(二)
与线程生命周期相关的方法: sleep 调用sleep方法会进入计时等待状态,等待时间到了,进入就绪状态. yield 调用yield方法会让别的线程执行,但是不确保真正让出.较少使用,官方注释都说 ...
- 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁
什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...
- Java学习笔记-多线程-创建线程的方式
创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
- 20145330第六周《Java学习笔记》
20145330第六周<Java学习笔记> . 这周算是很忙碌的一周.因为第六周陆续很多实验都开始进行,开始要准备和预习的科目日渐增多,对Java分配的时间不知不觉就减少了,然而第十和十一 ...
随机推荐
- js防抖和节流
今天在网上看到的,里面的内容非常多.说下我自己的理解. 所谓的防抖就是利用延时器来使你的最后一次操作执行.而节流是利用时间差的办法,每一段时间执行一次.下面是我的代码: 这段代码是右侧的小滑块跟随页面 ...
- Daily Life 01
2019-03-03 我不擅于用文字记录自己的生活,因为很长时间一个人习惯了随意简单的生活,觉得很多事留给回忆就好,另一方面文笔不好,怕自己流出的文字不有趣,过于流水.有看过一些身边人写的随记,都有写 ...
- LOJ #6052. 「雅礼集训 2017 Day11」DIV
完了我是数学姿势越来越弱了,感觉这种CXRdalao秒掉的题我都要做好久 一些前置推导 首先我们很容易得出\((a+bi)(c+di)=k \Leftrightarrow ac-bd=k,ad+bc= ...
- iOS开发之Masonry框架源码解析
Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁.Masonry简化了NSLayoutConstraint的使用方式,让 ...
- 流行的报表生成工具-JXLS
如果你还在为灵活的生成各种复杂报表犯愁,在为常用报表工具消耗大量内存担心.我推荐一个很好用的开源的Java报表生成工具. 本工具封装了强大的POI.但与POI不同的是,它可以用很简洁的代码生成复查的, ...
- #Java学习之路——基础阶段(第十一篇)
我的学习阶段是跟着CZBK黑马的双源课程,学习目标以及博客是为了审查自己的学习情况,毕竟看一遍,敲一遍,和自己归纳总结一遍有着很大的区别,在此期间我会参杂Java疯狂讲义(第四版)里面的内容. 前言: ...
- Dom4J配合XPath解析schema约束的xml配置文件问题
如果一个xml文件没有引入约束,或者引入的是DTD约束时,那么使用dom4j和xpath是可以正常解析的,不引入约束的情况本文不再展示. 引入DTD约束的情况 mybook.dtd: <?xml ...
- 用pyinstaller打包python程序,解决打包时的错误:Cannot find existing PyQt5 plugin directories
解决方法就是用everything搜索PyQt5,找到 /Library/plugins路径下的PyQt5文件夹,将里面的dll动态库pyqt5qmlplugin.dll复制出来 按照错误提示的路径, ...
- 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十一║Vue实战:开发环境搭建【详细版】
缘起 哈喽大家好,兜兜转转终于来到了Vue实战环节,前边的 6 篇关于Vue基础文章我刚刚简单看了看,感觉写的还是不行呀,不是很系统,所以大家可能看上去比较累,还是得抽时间去润润色,修改修改语句和样式 ...
- 从零开始学习PYTHON3讲义(十四)写一个mp3播放器
<从零开始PYTHON3>第十四讲 通常来说,Python解释执行,运行速度慢,并不适合完整的开发游戏.随着电脑速度的快速提高,这种情况有所好转,但开发游戏仍然不是Python的重点工作. ...