一:线程的基础:
1.什么是进程?
一个进程对应一个应用程序。例如:在Windows操作系统启动Word加表示启动了一个进程。在java的开发环境下启动JVM,就表示启动了一个进程。
现代的计算机都是支持多进程的,在同一个操作系统中,可以同时启动多个进程。
2.多进程有什么作用?
单进程计算机只能做一件事情。玩电脑,一边玩游戏(游戏进程)一边听音乐(音乐进程)。
对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程是同时在运行吗?
不是,因为计算机的CPU只能在某个时间点上做一件事情。由于计算机的处理速度极高,在进程中频繁的切换执行,人无法感知,所以认为是“同时进行的”,实际上不是。
多进程的作用不是提高执行速度,而是提高CPU的使用率。
进程和进程之间的内存是独立的。
3.什么是线程?
线程是一个进程中的执行场景。一个进程可以启动多个线程。(进程是包含线程的)
例如:多台客户端电脑同时访问一台服务器上的应用程序。服务器CPU在多个线程中频繁切换。
4.多线程有什么作用? 多线程不是为了提高执行速度,而是为了提高应用程序的使用率。
线程和线程共享“堆内存和方法区内存”,栈内存是独立的。
5.java程序的运行原理?
java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,表示启动了一个进程。
该进程会自动启动一个"主线程",然后主线程调用某个类的main方法。所以main方法运行在主线程中。在此之前的所有程序都是程序都是单线程的,前几章讲的程序都是在main方法中运行的。
分析以下程序有几个线程?
以下程序只有一个线程,就是主线程
main、m1、m2、m3 这四个方法在同一个栈空间中,
main方法先压栈,调用m1(),m1压栈;m1调用m2,m2压栈;m2调用m3,m3压栈在栈顶。
m3方法最后压栈,最先执行完后弹栈。
没有启动其他任何线程
public class ThreadTest01 {
public static void main(String[] args) {
m1();
}
public static void m1(){
m2();
}
public static void m2(){
m2();
}
public static void m3(){
System.out.println("m3...");
}
}
二:多线程的实现
在java语言中实现多线程的第一种方法:
第一步:继承 java.lang.Thread;
第二步:重写 run 方法。
package com.company01.Thread;
/*
如何创建线程?
如何启动线程?
*/
public class ThreadTest02 {
public static void main(String[] args) {
// 创建线程
Thread t = new Processor();
// 启动
t.start(); // 这段代码执行瞬间结束。告诉JVM再分配一个新的栈给t线程
// run不需要程序员手动调用,系统线程启动之后自动调用run方法。
// 【注意】这里如果写 t.run(); 这是普通方法调用
// 不会分配新的线程,此时mian和run在同一个栈中。
// t.run();
// 这段代码在主线程中运行
for(int i=0; i<10; i++){
System.out.println("main-->" + i);
}
// 有了多线程之后,main()方法结束只是主线程栈中没有方法栈帧了。
// 但是其他线程或者其他栈中还有栈帧。
// main方法结束,程序可能还在运行
// 本程序输出顺序不唯一
}
}
//定义一个线程
class Processor extends Thread{
// 重写run方法
public void run(){
for(int i=0; i<10; i++){
System.out.println("run-->" + i);
}
}
}
java中实现线程的第二种方法:
第一步:写一个类实现java.lang.Runnable; 接口
第二步:实现run方法
这种方式是推荐的,因为一个类实现接口之外,还保留了类的继承。
package com.company01.Thread;
public class ThreadTest03 {
public static void main(String[] args) {
// 创建线程
Thread t = new Thread(new Processor01());
// 启动
t.start();
}
}
// 这种方式是推荐的
class Processor01 implements Runnable{
public void run(){
for(int i=0; i<10; i++){
System.out.println("run-->" + i);
}
}
}
三:线程常用方法
package com.company01.Thread;
/*
本代码讲解三个方法:
1.获取当前线程对象 Thread.currentThread()
2.给线程取名字 线程引用.setName("t1");
3.获取线程名字 线程引用.getName();
*/
public class ThreadTest04 {
public static void main(String[] args) {
// 如何获取当前线程对象?
Thread t = Thread.currentThread(); // t保存的内存地址指向的线程是“主线程对象”
// 获取线程的名字
System.out.println(t.getName()); // main
Processor02 p = new Processor02();
Thread t1 = new Thread(p);
System.out.println(t1.getName()); // Thread-0
Thread t2 = new Thread(new Processor02());
System.out.println(t2.getName()); // Thread-1
// 设置线程的名字
t2.setName("t2");
System.out.println(t2.getName()); // t2
}
}
class Processor02 implements Runnable{
public void run(){
Thread t = Thread.currentThread(); // t保存的内存地址指向的线程是“当前线程对象”
System.out.println(t.getName());
}
}
四:线程优先级
通常我们的计算机只有一个CPU,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是
使用权,才可以执行指令。在单CPU的机器上线程不是并行运行的,只有在多个CPU上线程才可以并行。
JVM虚拟机要负责线程的调度,取得CPU的使用权,目前有两种调度模型:分时调度模型和抢占调度模型。
Java使用抢占式调度模型。
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级
高的线程获取的CPU时间片相对多一些。
优先级:1-10
最低:1
最高:10
默认:5
package com.company01.Thread;
/*
*/
public class ThreadTest05 {
public static void main(String[] args) {
System.out.println(Thread.MAX_PRIORITY); // 10
System.out.println(Thread.MIN_PRIORITY); // 1
System.out.println(Thread.NORM_PRIORITY); // 5
// 注意此处Processor implements Runnable与Processor extends Thread的区别
Thread t1 = new Thread(new Processor03());
t1.setName("t1");
Thread t2 = new Thread(new Processor03());
t2.setName("t2");
System.out.println(t1.getPriority()); // 5
System.out.println(t2.getPriority()); // 5
// 设置优先级
t1.setPriority(1);
t2.setPriority(10);
// 启动线程
t1.start();
t2.start();
}
}
class Processor03 implements Runnable{
public void run(){
for(int i=0; i<20; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
五:线程阻塞方法sleep()方法
package com.company01.Thread;
/*
1.Thread.sleep(毫秒);
2.sleep方法是一个静态方法。
3.该方法的作用:阻塞当前线程。腾出CPU让给其他线程
*/
public class ThreadTest06 {
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Processor04();
t1.setName("t1");
t1.start();
//阻塞主线程
for(int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
Thread.sleep(500);
}
}
}
class Processor04 extends Thread{
// 重写的方法不能比原先的方法抛出更加宽泛的异常
// 而原始的Thread中run()根本就没有抛出异常,所以run方法不能用throws,只能用try...catch...
public void run(){
for(int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
try {
Thread.sleep(1000); // 让当前线程阻塞1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:
package com.company01.Thread;
public class ThreadTest07 {
public static void main(String[] args) throws InterruptedException{
// 创建线程
Thread t = new Processor05();
t.setName("t");
// 启动线程
t.start();
// 休眠
t.sleep(5000); // sleep()方法是静态方法,需要类名.的方式调用
// 此处等同于 Thread.sleep(5000); 阻塞的还是当前线程,和t线程无关
System.out.println("Hello World!");
A a = null;
a.m1(); // 此处不会报空指针异常,m1()是静态方法,代码上写的是"a.m1()",实际上调用的是A.m1()
}
}
class Processor05 extends Thread{
public void run(){
for(int i=0; i<100; i++){
System.out.println(Thread.currentThread().getName() + "-->" +i);
}
}
}
class A{
public static void m1(){}
}
六:打断线程休眠
package com.company01.Thread;
/*
某线程正在休眠,如何打断它的休眠
以下方式依靠的是异常处理机制
*/
public class ThreadTest08 {
public static void main(String[] args) throws InterruptedException{
// 需求:启动线程,5s之后打断线程的休眠
Thread t = new Thread(new Processor06());
t.setName("t");
t.start();
// 5点之后
Thread.sleep(5000);
// 打断t的休眠
t.interrupt(); // 利用的是异常处理机制 控制栏会输出异常
}
}
class Processor06 implements Runnable{
public void run(){
try{
Thread.sleep(100000000000L);
}catch(InterruptedException e){
e.printStackTrace();
}
for(int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
动手例子:
package com.company01.Thread;
/*
如何正确的更好的终止一个正在执行的线程。
需求:线程启动五秒之后终止。
*/
public class ThreadTest09 {
public static void main(String[] args) throws InterruptedException{
Processor07 p = new Processor07();
Thread t = new Thread(p);
t.setName("t");
t.start();
// 5S之后终止
Thread.sleep(5000);
p.run = false;
}
}
class Processor07 implements Runnable{
boolean run = true;
public void run(){
for(int i=0; i<10; i++){
if(run){
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "-->" + i);
}catch(InterruptedException e){
e.printStackTrace();
}
}else{
return;
}
}
}
}
Thread.yield()方法
package com.company01.Thread;
/*
Thread.yield()
1、该方法是一个静态方法
2、作用:给 同一个优先级的线程 让位,但是让位时间不固定。
3、它与sleep()类似,只是不能由用户指定暂停多长时间
*/
public class ThreadTest10 {
public static void main(String[] args) {
Thread t = new Processor08();
t.setName("t");
t.start();
// 主线程中
for(int i=0; i<100; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class Processor08 extends Thread{
public void run(){
for(int i=0; i<100; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
if(i%20 == 0){
Thread.yield(); // for每循环20次 停一次 但是好像计算机运行太快了 没有预期效果
}
}
}
}
join() 合并线程
package com.company01.Thread;
/*
(Thread引用).join(); 成员方法
*/
public class ThreadTest11 {
public static void main(String[] args) throws InterruptedException{
Thread t = new Thread(new Processor10());
t.setName("t");
t.start();
// 合并线程
t.join(); // t和主线程合并,变为单线程的程序,两个栈空间变为一个栈区,需要等到t线程执行完后,才能继续下面的语句
// 主线程
for(int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class Processor10 implements Runnable{
public void run(){
for(int i=0; i<50; i++){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
七:线程同步机制
package com.company01.Thread;
/*
以t1和t2为例:
异步编程模型:t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁
同步编程模型:t1线程和t2线程执行,当t1线程必须等t2线程执行结束之后,t1线程才能执行,这是同步编程模型。
什么时候要同步呢?为什么要引入线程同步呢?
1.为了数据的安全。尽管应用程序的使用降低,但是为了保证数据是安全的,必须加入线程同步机制。
线程同步机制是程序变成了(等同)单线程。
例如两个线程访问银行账户,银行存款1000,线程一取款500,在账户还没有更新时,如果采用非同步,
线程二此时读取余额仍然为1000,所以需要采用同步编程模型,只有在线程一执行完后才能执行线程二。
2.什么条件下要使用线程同步?
第一:必须是多线程环境
第二:多线程环境共享同一个数据
第三:共享的数据涉及到修改操作
以下程序演示取款例子。以下程序不使用线程同步机制,多线程同时对同一个账户进行取款操作,会出现什么问题?
*/
public class ThreadTest12 {
public static void main(String[] args) {
// 创造一个公共的账户
Account act = new Account("act-001", 5000.0);
//创建线程,共享同一个账户,对同一个账户进行取款
/*
ProcessorTest pt = new ProcessorTest(act);
Thread t1 = new Thread(pt);
Thread t2 = new Thread(pt);
*/
// 或者
Thread t1 = new Thread(new ProcessorTest(act));
Thread t2 = new Thread(new ProcessorTest(act));
t1.start();
t2.start();
/*
取款1000.00成功,余额:4000.0
取款1000.00成功,余额:4000.0
*/
}
}
// 取款线程
class ProcessorTest implements Runnable{
// 账户
Account act;
// Constructor
ProcessorTest(Account act){
this.act = act;
}
public void run(){
act.withdraw(1000);
System.out.println("取款1000.00成功,余额:" + act.getBalance());
}
}
// 账户
class Account{
private String actno;
private double balance;
public Account(){}
public Account(String actno, double balance){
this.actno = actno;
this.balance = balance;
}
// setter and getter
public void setActno(String actno){
this.actno = actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public String getActno(){
return this.actno;
}
public double getBalance(){
return this.balance;
}
// 对外提供一个取款的方法
public void withdraw(double money){ // 对象当前账户取款
double after = balance - money;
// 更新
// 延迟
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
this.setBalance(after);
}
}
线程同步编程实例一
package com.company01.Thread;
/*
以下程序演示取款例子。以下程序使用线程同步机制.
与上一个例子相比:
ThreadTest12执行时间需要1秒
而ThreadTest13需要2秒,但是数据是安全的
把需要同步的代码,放到同步语句块中(synchronized(){}语句块中)
原理:t1线程和t2线程
t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
如果找到this对象锁,则会进入同步语句块中执行程序。当同步语句块中的代码
执行结束之后,t1线程归还this的对象锁。
在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到了
synchronized关键字,所以也去找this的对象锁,但是该对象锁被t1线程
持有,只能在这等到this对象锁的归还。
所以永远只有一个线程在执行
*/
public class ThreadTest13 {
public static void main(String[] args) {
// 创造一个公共的账户
Account01 act = new Account01("act-001", 5000.0);
//创建线程,共享同一个账户,对同一个账户进行取款
Thread t1 = new Thread(new ProcessorTest01(act));
Thread t2 = new Thread(new ProcessorTest01(act));
t1.start();
t2.start();
t1.setName("t1");
t2.setName("t2");
/*
取款1000.00成功,余额:4000.0
取款1000.00成功,余额:3000.0
*/
}
}
// 取款线程
class ProcessorTest01 implements Runnable{
// 账户
Account01 act;
// Constructor
ProcessorTest01(Account01 act){
this.act = act;
}
public void run(){
act.withdraw(1000);
System.out.println(Thread.currentThread().getName() + "取款1000.00成功,余额:" + act.getBalance());
}
}
// 账户
class Account01{
private String actno;
private double balance;
public Account01(){}
public Account01(String actno, double balance){
this.actno = actno;
this.balance = balance;
}
// setter and getter
public void setActno(String actno){
this.actno = actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public String getActno(){
return this.actno;
}
public double getBalance(){
return this.balance;
}
// 对外提供一个取款的方法
public void withdraw(double money){ // 对象当前账户取款
//把需要同步的代码,放到同步语句块中
/*
原理:t1线程和t2线程
t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
如果找到this对象锁,则会进入同步语句块中执行程序。当同步语句块中的代码
执行结束之后,t1线程归还this的对象锁。
在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到了
synchronized关键字,所以也去找this的对象锁,但是该对象锁被t1线程
持有,只能在这等到this对象锁的归还。
*/
synchronized(this){
double after = balance - money;
// 更新
// 延迟
try{
Thread.sleep(1000);
// 【注意】如果这里没有使用sleep,会导致控制台打印输出顺序混乱
// 同步只是到了this.setBalance,也就是只是将线程中run()方法的act.withdraw(1000);这一句同步
// 并没有将输出也列入到同步中,因此输出这一句话仍然是多线程,可能是t1先输出,也可能是t2先先输出
// 而这里加入1S的延迟,在t1延迟结束后执行完setBalance归还对象锁,t2获得对象锁,t2就进入了1S的延迟
// 这1S的时间足以让 t1线程执行完run里面方法,完成控制台的打印输出,1S后t2才能完成打印输出。
}catch(InterruptedException e){
e.printStackTrace();
}
this.setBalance(after);
}
}
}
线程同步编程实例二
package com.company01.Thread;
/*
以下程序演示取款例子。以下程序使用线程同步机制.
与上一个例子相比:
ThreadTest12执行时间需要1秒
而ThreadTest13需要2秒,但是数据是安全的
把需要同步的代码,放到同步语句块中(synchronized(){}语句块中)
原理:t1线程和t2线程
t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
如果找到this对象锁,则会进入同步语句块中执行程序。当同步语句块中的代码
执行结束之后,t1线程归还this的对象锁。
在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到了
synchronized关键字,所以也去找this的对象锁,但是该对象锁被t1线程
持有,只能在这等到this对象锁的归还。
所以永远只有一个线程在执行
*/
public class ThreadTest14 {
public static void main(String[] args) {
// 创造一个公共的账户
Account02 act = new Account02("act-001", 5000.0);
//创建线程,共享同一个账户,对同一个账户进行取款
Thread t1 = new Thread(new ProcessorTest02(act));
Thread t2 = new Thread(new ProcessorTest02(act));
t1.start();
t2.start();
t1.setName("t1");
t2.setName("t2");
/*
取款1000.00成功,余额:4000.0
取款1000.00成功,余额:3000.0
*/
}
}
// 取款线程
class ProcessorTest02 implements Runnable{
// 账户
Account02 act;
// Constructor
ProcessorTest02(Account02 act){
this.act = act;
}
public void run(){
act.withdraw(1000);
System.out.println(Thread.currentThread().getName() + "取款1000.00成功,余额:" + act.getBalance());
}
}
// 账户
class Account02{
private String actno;
private double balance;
public Account02(){}
public Account02(String actno, double balance){
this.actno = actno;
this.balance = balance;
}
// setter and getter
public void setActno(String actno){
this.actno = actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public String getActno(){
return this.actno;
}
public double getBalance(){
return this.balance;
}
// 对外提供一个取款的方法
// 这里也可以在方法的修饰符列表中加入关键字synchronized
// 这时整个方法都是同步编程的
public synchronized void withdraw(double money){ // 对象当前账户取款
double after = balance - money;
// 更新
// 延迟
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
this.setBalance(after);
}
}
面试题
package com.company01.Thread;
/*
面试题
*/
public class ThreadTest15 {
public static void main(String[] args) throws InterruptedException{
MyClass mc = new MyClass();
Thread t1 = new Thread(new Processor13(mc));
t1.setName("t1");
Thread t2 = new Thread(new Processor13(mc));
t2.setName("t2");
/*
// 如果这里是这样写的
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new Thread(new Processor13(mc1));
Thread t2 = new Thread(new Processor13(mc2));
// 此时无论m2有没有synchronized修饰,m1的执行并不会影响m2
// 因为他们两个线程没有共享同一个对象,两个线程的对象锁不一样
*/
t1.start();
Thread.sleep(1000);
t2.start(); // m2方法上没有synchronized关键字,调用的时候不需要找对象锁。
// 所以这里是m2执行不需要等m1结束
// 如果在m2方法加上synchrinized关键字,m1和m2共享同一个对象,就需要对象锁
/*
m2.....
m1.....
*/
}
}
class Processor13 implements Runnable{
MyClass mc;
Processor13(MyClass mc){
this.mc = mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.m1();
}
if(Thread.currentThread().getName().equals("t2")){
mc.m2();
}
}
}
class MyClass {
public synchronized void m1(){
// 休眠
try{
Thread.sleep(10000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("m1.....");
}
public void m2(){
System.out.println("m2.....");
}
}
类锁
例子一
package com.company01.Thread;
/*
类锁,类只有一个,所以锁是类级别,只有一个
*/
public class ThreadTest16 {
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(new Processor14());
Thread t2 = new Thread(new Processor14());
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class Processor14 implements Runnable{
public void run(){
if("t1".equals(Thread.currentThread().getName())){
MyClass01.m1();
}
if("t2".equals(Thread.currentThread().getName())){
MyClass01.m2();
}
}
}
class MyClass01{
// synchronized 添加到静态方法上,线程执行此方法的时候会找类锁。
public synchronized static void m1(){
try{
Thread.sleep(10000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("m1.....");
}
// 不会等m1结束,因为该方法没有被synchronized修饰
/*
public static void m2(){
System.out.println("m2.....");
}
*/
// m2方法等m1结束之后才能执行,该方法有syschronized
// 线程执行该代码需要“类锁”,而类锁只有一个。
public synchronized static void m2(){
System.out.println("m2.....");
}
}
例子二
package com.company01.Thread;
/*
类锁,类只有一个,所以锁是类级别,只有一个
*/
public class ThreadTest17 {
public static void main(String[] args) throws InterruptedException{
MyClass02 mc1 = new MyClass02();
MyClass02 mc2 = new MyClass02();
Thread t1 = new Thread(new Processor15(mc1));
Thread t2 = new Thread(new Processor15(mc2));
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class Processor15 implements Runnable{
MyClass02 mc;
Processor15(MyClass02 mc){
this.mc = mc;
}
public void run(){
if("t1".equals(Thread.currentThread().getName())){
mc.m1(); // 尽管静态方法也可以用 “引用.”的方法调用
// 但是底层用的还是 “类名.”的方式
// 用的还是 类锁 ,和对象锁无关
}
if("t2".equals(Thread.currentThread().getName())){
mc.m2();
}
}
}
class MyClass02{
// synchronized 添加到静态方法上,线程执行此方法的时候会找类锁。
public synchronized static void m1(){
try{
Thread.sleep(10000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("m1.....");
}
// 要等m1执行完之后,归还类锁,才能继续执行m2方法
public synchronized static void m2(){
System.out.println("m2.....");
}
}
死锁
package com.company01.Thread;
/*
死锁:
线程1和线程2都想锁住对象1和对象2
线程1先锁住对象1,线程2先锁住对象2
线程1想锁住对象2,需要线程2先归还对象2的对象锁
线程2想锁住对象1,需要线程1先归还对象1的锁
*/
public class ThreadTest18 {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread(new T1(o1, o2));
Thread t2 = new Thread(new T2(o1, o2));
t1.start();
t2.start();
}
}
class T1 implements Runnable{
Object o1;
Object o2;
T1(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 T2 implements Runnable{
Object o1;
Object o2;
T2(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){
}
}
}
}
守护线程
package com.company01.Thread;
/*
守护线程:
从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。
守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期,
只要有一个用户线程存在,那么守护线程就不会结束。
例如java中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,
它才会结束。
守护线程一般是无限循环执行的。
java在执行的时候,会启动JVM虚拟机进程,而JVM在启动的时候,不只是启动一个线程,
其中有一个主线程,还有垃圾回收线程,他们在不同的栈空间里面。
*/
public class ThreadTest19 {
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Processor17();
t1.setName("t1");
// 设置t1线程为守护线程
t1.setDaemon(true);
t1.start();
// 主线程
for(int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
Thread.sleep(1000);
}
}
}
/*
若这里t1为普通线程:
run()函数里面while(true),该线程永远都不会结束,无线循环下去。
而这里将t1设置为守护线程之后:
在主线程结束之后,程序里面再也没有其他用户线程,守护线程就会自动退出。
*/
class Processor17 extends Thread{
public void run(){
int i = 0;
while(true){
i++;
System.out.println(Thread.currentThread().getName() + "-->" + i);
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
八:定时器
package com.company01.Thread;
/*
关于定时器的应用:
作用:每隔一段固定的时间执行一段固定的代码。
构造方法:
Timer() 创建一个新的定时器
Timer(boolean isDaemon) 创建一个新的定时器,可以指定其相关的线程作为守护程序运行
Timer(String name) 创建一个新的定时器,其相关的线程具有指定的名称
Timer(String name, boolean isDaemon)
*/
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.text.*;
public class TimerTest01 {
public static void main(String[] args) throws ParseException {
// 1.创建定时器
Timer t = new Timer();
// 2.指定定时任务
// schedule(TimerTask task, Data firstTimer, long period)
t.schedule(
new LogTimerTask(),
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").parse("2019-12-18 20:30:00 000"),
1000*2
);
}
}
// 指定任务
class LogTimerTask extends TimerTask{
public void run(){
// 输出当前时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
}
}