多线程
1、什么是进程?什么是线程?
进程是一个应用程序(1个进程是一个软件)
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程。java中的多线程机制,目的就是为了提高程序的处理效率。
例:
package javaCoreTest;
/*
* 判断以下程序出垃圾回收线程之外有几个线程?
* 1个线程(因为程序只有1个栈)
*
* main begin
m1 begin
m2 begin
m3 execute
m2 over
m1 over
main over
一个栈中,自上而下的顺序依次逐行执行!
*/
public class ThreadTest01 {
public static void main(String [] args) {
System.out.println("main begin");
m1();
System.out.println("main over");
}
private static void m1() {
// TODO Auto-generated method stub
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
private static void m2() {
// TODO Auto-generated method stub
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
private static void m3() {
// TODO Auto-generated method stub
System.out.println("m3 execute");
}
}
2、对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车之后,会先启动JVM,而JVM就是一个进程。
JVM在启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责 看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,
一个是垃圾回收线程,一个是执行main方法的主线程。
3、两个进程之间是相互独立的,不共享资源。
线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
4、使用了多线程机制之后,main方法结束是不是有可能程序也不会结束?
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
5、对于单核的CPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没有问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时在做。
6、java 语言中实现线程有两种方式,有哪两种呢?
java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了。
-
第一种:
编写一个类,直接继承java.lang.Thread,重写run方法。
//定义线程类
public class MyThread extends Thread{
public void run(){
}
}
//创建线程对象
MyThread myThread = new MyThread();
//启动线程
my.Thread.start();
例:
package javaCoreTest;
/*
* 实现线程的第一种方式:
* 编写一个类,直接继承java.lang.Thread,重写run()方法
*
* 怎么创建线程对象?
* new就行了
*怎么启动线程?
* 调用线程对象的start()方法
*/
public class ThreadTest02 {
public static void main(String [] args) {
//这里是main方法,属于主线程,在主栈中运行
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
//如果直接调用run方法,不会启动线程,不会分配新的分支栈(这种方式是单线程)
//myThread.run();
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务【开辟一个新的栈空间】完成之后,瞬间就结束了
//线程就启动成功,会自动调用run方法,并且run方法在分支栈的底部(压栈)
//run方法在分支栈的底部,main方法在主栈的底部,run和main是平级的。
myThread.start();
//这里的代码还是运行在主线程中
for(int i = 0; i < 1000; i++) {
System.out.println("主线程=====" + i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
// 编写程序,这段程序运行在分支线程中()
super.run();
for(int i = 0; i < 1000; i++) {
System.out.println("分支线程=====" + i);
}
}
}
-
第二种:
编写一个类,实现java.lang.Runnable接口
//定义一个可运行的类
public class MyRunnable implements Runnable{
public void run(){
}
}
//创建一个线程对象
Thread t = new Thread(new MyRunnable());
//启动线程
t.start();
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。
例:
package javaCoreTest;
/*
* 实现线程的第二种方式:编写一个类,实现java.lang.Runnable接口
*/
public class ThreadTest03 {
public static void main(String [] args) {
//创建一个可运行的对象
//MyRunnable t = new MyRunnable();
//将可运行的对象封装成一个线程对象
//Thread tt = new Thread(t);
//合并以上代码
Thread tt = new Thread(new MyRunnable());
//启动线程
tt.start();
for(int i = 0; i < 1000; i++) {
System.out.println("主线程=====" + i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 1000; i++) {
System.out.println("分支线程=====" + i);
}
}
}
package javaCoreTest;
/*
* 采用匿名内部类
*/
public class ThreadTest04 {
public static void main(String [] args) {
//创建线程对象,采用匿名内部类的方式
Thread t = new Thread (new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 1000; i++) {
System.out.println("分支线程====" + i);
}
}
});
t.start();
for(int i = 0; i < 1000; i++) {
System.out.println("主线程====" + i);
}
}
}
7、线程的生命周期
-
新建状态
-
就绪状态
-
运行状态
-
阻塞状态
-
死亡状态
package javaCoreTest;
/*
* 1.怎么获取当前线程对象?
* Thread t = Thread.currentThread();
* 返回值t就是当前线程。
*
* 2.获取线程对象的名字?
* String name = 线程对象.getName();
*
* 3.修改线程对象的名字?
* 线程对象.setName("线程名字");
*
* 4.当线程没有设置名字的时候,默认的名字有什么规律?(了解)
* Thread-0
Thread-1
Thread-2
Thread-3
Thread-4
.........
*/
public class ThreadTest05 {
public static void main(String [] args) {
//currentThread就是当前线程对象
//这个代码出现在main方法当中,所以当前线程就是主线程。
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());//main
//创建线程对象
MyThread05 t = new MyThread05();
//设置线程的名字--setName方法
//t.setName("tttt");
//获取线程的名字--getName方法
String tName = t.getName();
System.out.println(tName);//Thread-0
MyThread05 t2 = new MyThread05();
System.out.println(t2.getName());//Thread-1
//启动线程
t.start();
}
}
class MyThread05 extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
for(int i = 0; i < 1000; i++) {
//currentThread就是当前线程对象,那么当前线程是谁呢?
//当t线程执行run方法,那么这个当前线程就是t
//当t2线程执行run方法,那么这个当前线程就是t2
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());
System.out.println("分支线程====" + i);
}
}
}
package javaCoreTest;
/*
* 关于线程的sleep方法:
* static void sleep (long millis)
* 1、静态方法:Thread.sleep(1000);
* 2、参数是毫秒
* 3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占用CPU时间片,让给其他线程使用。
*/
public class ThreadTest06 {
public static void main(String [] args) {
//让当前线程进入休眠,睡眠5秒
/*try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
//5秒之后执行这里的代码
//System.out.println("hello world!");
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "====>" + i);
//睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package javaCoreTest;
/*
* 关于Thread.sleep()方法的一个面试题:
*/
public class ThreadTest07 {
public static void main(String [] args) {
//创建线程对象
Thread t = new MyThread07();
t.setName("t");
t.start();
//调用sleep方法
try {
//问题:这行代码会让线程t进入休眠状态吗?
//不会。
t.sleep(1000 * 5);//在执行的时候还是会转换成:Thread.sleep(1000 * 5);
//这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠状态。
//hello world5秒后输出
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//5秒后执行
System.out.println("hello world!");
}
}
class MyThread07 extends Thread{
public void run() {
for(int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "=====>" + i);
}
}
}
package javaCoreTest;
/*
* sleep睡眠太久了,如果希望半道上醒来,应该怎么办?怎么唤醒一个正在睡眠的线程?
* 注意:这个不是中断线程的执行,是中止线程的睡眠。
*/
public class ThreadTest08 {
public static void main(String [] args) {
Thread t = new Thread(new MyRunnable08());
t.setName("t");
t.start();
//希望5秒之后,t线程醒来
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//中断t线程的睡眠(这种中断睡眠的方式依靠了java的异常处理机制)
t.interrupt();//干扰
}
}
class MyRunnable08 implements Runnable{
//重点:run()当中的异常不能throws,只能try..catch
//因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName() + "=====>+ begin" );
//睡眠1年
try {
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
//打印异常信息【如果不想显示可以注释掉】
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "=====>+ end");
}
}
package javaCoreTest;
/*
* 在java中怎么强行终止一个线程的执行?
* 这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了。
* 线程没有保存的数据将会丢失,不建议使用。
*/
public class ThreadTest09 {
public static void main(String [] args) {
Thread t = new Thread(new MyRunnable09());
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//5秒之后强行终止t线程
t.stop();//已过时,不建议使用
}
}
class MyRunnable09 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "=====>" + i);
try {
//睡1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package javaCoreTest;
/*
* 怎么合理的终止一个线程的执行?
* 以下这种方式很常用。
*/
public class ThreadTest10 {
public static void main(String [] args) {
MyRunnable10 r = new MyRunnable10();
Thread t = new Thread(r);
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//终止线程
//想要什么时候终止t的执行,就把标记改为false,结束。
r.run = false;
}
}
class MyRunnable10 implements Runnable{
boolean run = true;
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 10; i++) {
if(run) {
System.out.println(Thread.currentThread().getName() + "====>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
//在结束之前没有保存的数据可以在这里保存
//终止当前线程
return;
}
}
}
}
8、线程的调度(了解)
(1)、常见的线程调度模型
-
*抢占式调度模型
哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些,java采用这种抢占式调度模型。
-
*均分式调度模型
平均分配CPU时间片,每个线程占用的CPU时间片时间长度一样。平均分配,一切平等。
有些编程语言采用的是这种方式。(2)、java中提供了哪些方法是和线程调度有关系的呢?
-
实例方法:
void setPriority()设置线程优先级 int getPriority()获取线程优先级 最低优先级1 默认优先级5 最高优先级10 优先级高的获取CPU 时间片可能会多一些。 (但也不完全是,大概率是多的)
-
例:
package javaCoreTest;
/*
* 了解:关于线程的优先级
*/
public class ThreadTest11 {
public static void main(String [] args) {
//设置主线程的优先级为1
Thread.currentThread().setPriority(1);
// System.out.println("最高优先级" + Thread.MAX_PRIORITY);
// System.out.println("最低优先级" + Thread.MIN_PRIORITY);
// System.out.println("默认优先级" + Thread.NORM_PRIORITY);
//获取当前线程对象,获取当前线程优先级
Thread currentThread = Thread.currentThread();
//main线程的默认优先级是:5
// System.out.println(currentThread.getName() + "线程的默认优先级是" + currentThread.getPriority());
Thread t = new Thread(new MyRunnable11());
//设置分支线程优先级为10
t.setPriority(10);
t.setName("t");
t.start();
//优先级较高,只是抢到的CPU时间片相对多一些
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
}
}
}
class MyRunnable11 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
//获取线程优先级
// System.out.println(Thread.currentThread().getName() + "线程的默认优先级:" + Thread.currentThread().getPriority());
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
}
}
}
- 静态方法:
static void yield()让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法,让当前线程从“运行状态”
回到“就绪状态”,让给其他线程使用。
注意:在回到就绪状态之后,有可能还会再次抢到。
例:
package javaCoreTest;
/*
* 让位,当前线程暂停,回到就绪状态,让给其他线程
* 静态方法:Thread.yield();
*/
public class ThreadTest12 {
public static void main(String [] args) {
Thread thread = new Thread(new MyRunnable12());
thread .setName("t");
thread.start();
for(int i = 1; i <= 1000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class MyRunnable12 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 1; i <= 1000; i++) {
//每100个让位1次
if(i % 100 == 0) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
- 实例方法:
void join() 合并线程
例:
class MyThread1 extends Thread{
public void doSome(){
MyThread2 t = new MyThread2();
t.join();//当前线程进入阻塞,t线程执行,直到t线程结束,
//当前线程才可以继续。
}
}
class MyThread2 extends Thread{
}
例:
package javaCoreTest;
/*
* 合并线程
*/
public class ThreadTest13 {
public static void main(String [] args) {
System.out.println("main begin");
Thread t = new Thread(new MyRunnable13());
t.setName("t");
t.start();
//合并线程
try {
t.join();//t合并到当前线程中,当前线程受阻塞,t线程执行,直到结束
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable13 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 1; i <= 1000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
9、关于多线程并发环境下,数据的安全问题
(1)为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义、线程的对象创建、线程的启动等,都已经实现了,这些代码我们都不需要编写。
最重要的是:编写的程序需要放到一个多线程的环境下运行,更需要关注的是这些数据在多线程并发的环境下是否是安全的。
(2)什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
*多线程并发
*有共享数据
*共享数据有修改行为
满足以上三个条件,存在线程安全问题
(3)怎么解决线程安全问题?
线程排队执行(不能并发)
用排队执行解决线程安全问题,这种机制被称为线程同步机制。
线程同步就是线程排队,线程排队就会牺牲一部分效率。
-
补充两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做异步编程模型。
其实就是:多线程并发(效率较高)异步就是并发。
同步编程模型:
线程1和线程2,在线程1执行的时候,必须等待线程2执行结束,或者说在线程2执行的时候,必须等待线程1执行结束,两个线程之间发生了等待关系,这就是同步编程模型。(效率较低)
其实就是:线程排队执行同步就是排队。
-
补充Java中的三大变量?
实例变量:在堆中 静态变量:在方法区 局部变量:在栈中 以上三大变量中:局部变量永远都不会存在线程安全问题, 因为局部变量不共享(一个线程一个栈)局部变量在栈中, 所以局部变量永远都不会共享。 实例变量在堆中,堆只有一个 静态变量在方法区中,方法区只有一个 堆和方法区都是多线程共享的,所有可能存在线程安全问题。
10、以后的开发中,应该怎么解决线程安全问题?
是一上来就选择线程同步吗?synchronized
不是,线程同步会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制。
-
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
-
第二种方案:
如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
-
第三种方案:
如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
面试题1:
package javaCoreTest;
//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需要,因为doOther方法没有synchronized
public class ThreadExam01 {
public static void main(String [] args) {
MyClass mClass = new MyClass();
Thread t1 = new MyThread1(mClass);
Thread t2 = new MyThread1(mClass);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//这个睡眠的作用:保证t1线程先执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
}
class MyThread1 extends Thread{
private MyClass mClass;
public MyThread1(MyClass mClass) {
super();
this.mClass = mClass;
}
public void run() {
if(Thread.currentThread().getName().equals("t1")) {
mClass.doSome();
}
if(Thread.currentThread().getName().equals("t2")) {
mClass.doOther();
}
}
}
class MyClass{
public synchronized void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("doSome end");
}
public void doOther() {
System.out.println("doOther begin");
System.out.println("doOther end");
}
}
面试题2:
package javaCoreTest;
//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//需要,因为MClass只有一个
public class ThreadExam02 {
public static void main(String [] args) {
MyClass2 mClass = new MyClass2();
Thread t1 = new MyThread2(mClass);
Thread t2 = new MyThread2(mClass);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//这个睡眠的作用:保证t1线程先执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
}
class MyThread2 extends Thread{
private MyClass2 mClass;
public MyThread2(MyClass2 mClass2) {
super();
this.mClass = mClass2;
}
public void run() {
if(Thread.currentThread().getName().equals("t1")) {
mClass.doSome();
}
if(Thread.currentThread().getName().equals("t2")) {
mClass.doOther();
}
}
}
class MyClass2{
public synchronized void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("doSome end");
}
public synchronized void doOther() {
System.out.println("doOther begin");
System.out.println("doOther end");
}
}
面试题3:
package javaCoreTest;
//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需要,因为MClass对象是两个,两把锁
public class ThreadExam03 {
public static void main(String [] args) {
MyClass3 mClass1 = new MyClass3();
MyClass3 mClass2 = new MyClass3();
Thread t1 = new MyThread3(mClass1);
Thread t2 = new MyThread3(mClass2);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//这个睡眠的作用:保证t1线程先执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
}
class MyThread3 extends Thread{
private MyClass3 mClass;
public MyThread3(MyClass3 mClass1) {
super();
this.mClass = mClass1;
}
public void run() {
if(Thread.currentThread().getName().equals("t1")) {
mClass.doSome();
}
if(Thread.currentThread().getName().equals("t2")) {
mClass.doOther();
}
}
}
class MyClass3{
public synchronized void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("doSome end");
}
public synchronized void doOther() {
System.out.println("doOther begin");
System.out.println("doOther end");
}
}
面试题4:
package javaCoreTest;
//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//需要,因为静态方法是类锁,不管创建了几个对象,类锁只有一把
public class ThreadExam04 {
public static void main(String [] args) {
MyClass4 mClass1 = new MyClass4();
MyClass4 mClass2 = new MyClass4();
Thread t1 = new MyThread4(mClass1);
Thread t2 = new MyThread4(mClass2);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//这个睡眠的作用:保证t1线程先执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
}
class MyThread4 extends Thread{
private MyClass4 mClass;
public MyThread4(MyClass4 mClass1) {
super();
this.mClass = mClass1;
}
public void run() {
if(Thread.currentThread().getName().equals("t1")) {
mClass.doSome();
}
if(Thread.currentThread().getName().equals("t2")) {
mClass.doOther();
}
}
}
class MyClass4{
//synchronized出现在静态方法中,找类锁
public synchronized static void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("doSome end");
}
public synchronized static void doOther() {
System.out.println("doOther begin");
System.out.println("doOther end");
}
}
关于死锁:
package javaCoreTest;
/*
* 死锁代码要会写。
* 一般面试官要求程序员会写,因为只有会写,才会在以后的开发中注意这个事
* 因为死锁很难调试。
*
* synchronized在开发中最好不要嵌套使用,一不小心可能会导致死锁现象的发生
*/
public class DeadLock01 {
public static void main(String [] args) {
Object o1 = new Object();
Object o2 = new Object();
//t1和t2两个线程共享o1,o2
Thread t1 = new MyThread001(o1, o2);
Thread t2 = new MyThread002(o1, o2);
t1.start();
t2.start();
}
}
class MyThread001 extends Thread{
Object o1;
Object o2;
public MyThread001(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o1) {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class MyThread002 extends Thread {
Object o1;
Object o2;
public MyThread002(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o1) {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
11、守护线程
(1)java语言中线程分为两大类:
*用户线程
*守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)
(2)守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程。
(3)守护线程用在什么地方呢?
每天零点的时候系统数据自动备份。
这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,每到零点的时候就备份一次,所有的用户线程如果结束了,守护线程自动退出,没必要进行数据备份了。
例:
package javaCoreTest;
/*
* 守护线程
*/
public class ThreadTest14 {
public static void main(String [] args) {
Thread thread = new BakDataThread();
thread.setName("备份数据的线程");
//启动线程之前,将线程设置为守护线程
thread.setDaemon(true);
thread.start();
//主线程:是用户线程
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread{
int i = 0;
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
while(true) {
System.out.println(Thread.currentThread().getName() + "====>" + i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
12、定时器
(1)定时器的作用:
间隔特定的时间,执行特定的程序。
每周要进行银行账户的总账操作。
每天要进行数据的备份操作。
(2)在java中可以采用多种方式实现:
-
*可以使用sleep方法,睡眠,设置睡眠时间,每到某个时间点醒来,执行任务。这种方式是最原始的定时器(比较low)。
-
*在java类库中已经写好了一个定时器,java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在很多高级框架都是支持定时任务的。
-
*在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
例:
package javaCoreTest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/*
* 使用定时器指定定时任务
*/
public class TimerTest01 {
public static void main(String [] args) throws ParseException {
//创建定时器对象
Timer timer = new Timer();
//Timer timer = new Timer(true);//守护线程的方式
//指定定时任务
//timer.schedule(定时任务,第一次执行,间隔多久执行一次)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
Date firstDate = simpleDateFormat.parse("2021-02-19 15:40:00");
timer.schedule(new LogTimerTask(), firstDate, 1000 * 10);
//也可以用匿名内部类的方式
/*
* timer.schedule(new LogTimerTask() {
* public void run() {
*
* }
* }, firstTime, period);
*/
}
}
class LogTimerTask extends TimerTask{
@Override
public void run() {
// TODO Auto-generated method stub
//编写需要执行的任务
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
String strTimeString = simpleDateFormat.format(new Date());
System.out.println(strTimeString + "成功完成了一次数据备份!");
}
}
13、实现线程的第三种方式:FutureTask方式,实现Callable接口(JDK8新特性)
这种方式实现的线程可以获取线程的返回值。
之前的那两种方式是无法获得返回值的,因为run方法返回void。
思考:
系统委派一个线程去执行任务,该线程执行完任务后,可以会有一个执行结果,我们要怎么样才能拿到执行结果?
用实现Callable接口的方式。
例:
package javaCoreTest;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/*
* 实现线程的第三种方式:
* 实现Callable接口
*
* 这种方式的优点是:可以获取到线程执行的结果
* 这种方式的缺点是:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
*/
public class TreadTest15 {
public static void main(String [] args) throws Exception{
//第一步:创建一个“未来任务类”对象
//参数非常重要,需要给一个Callable接口实现类对象
FutureTask task = new FutureTask(new Callable() {
public Object call() throws Exception {
//call()方法就相当于run方法,只不过这个有返回值
//线程执行一个任务,执行之后可能会有一个执行结果
//模拟执行
System.out.println("call method begin");
Thread.sleep(10000);
System.out.println("call method end");
int a = 100;
int b = 200;
return a + b;//自动装箱(300结果变成Integer)
}
});
//创建线程对象
Thread thread = new Thread(task);
//启动线程
thread.start();
//这里是main方法,这是在主线程中
//在主线程中,怎么获取thread线程的返回结果?
//get方法的执行会导致“当前线程阻塞”
Object object = task.get();
System.out.println("线程执行结果:" + object);
//main方法这里的程序要想执行必须等待get()方法的结束
//而get方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
//而另一个线程执行是需要时间的
System.out.println("hello world!");
}
}
14、关于Object类中的wait和notify方法(生产者和消费者模式)
(1)wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为他们是Object类中自带的。不通过线程对象调用。
例:t线程在o对象上活动,t 线程是当前线程对象,当调用o.wait()方法之后,t线程进入无限期等待,直到最终调用o.notify()方法,o.notify()方法的调用可以让正在o对象上等待的线程唤醒。
(2)wait方法有何作用?
Object o = new Object();
o.wait();
表示:
让正在o对象上活动的线程进入等待状态,无期限的等待,直到被唤醒为止。
o.wait();方法的调用会让“当前线程(正在o对象上活动的线程)”进入等待状态。
(3)notify方法有何作用?
Object o = new Object();
o. notify ();
表示:
唤醒正在o对象上等待的线程
有一个notifyAll方法:
这个方法是唤醒o对象上处于等待的所有线程。
例:
package javaCoreTest;
import java.awt.List;
import java.util.ArrayList;
/*
* 1、使用wait方法和notify方法实现“生产者和消费者模式”
*
* 2、什么是“生产者和消费者”模式?
* 生产线程负责生产,消费线程负责消费。
* 生产线程和消费线程要达到均衡
* 这是一种特殊的业务需求,在这种特殊的情况下,需要使用wait方法和notify方法。
*
* 3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法
*
* 4、wait方法和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全问题。
*
* 5、wait方法作用:
* o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t 线程之前占用的o对象的锁
*
* 6、notify方法作用:
* o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占用的锁。
*
* 7、模拟这样一个需求:
* 仓库我们采用List集合
* List集合中假设只能存储1个元素
* 1个元素就表示仓库满了
* 如果List集合中元素个数为0,就表示仓库空了。
* 保证List集合中永远都是最多存储1个元素
*
* 必须达到这样的效果:生产一个消费一个。
*/
public class ThreadTest16 {
public static void main(String [] args) {
//创建一个仓库对象,共享的
ArrayList list = new ArrayList();
//创建两个线程对象
//生产者线程
Thread t1 = new Thread(new Producer(list));
//消费者线程
Thread t2 = new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
//生产线程
class Producer implements Runnable{
//仓库
private ArrayList list;
public Producer(ArrayList list2) {
super();
this.list = list2;
}
@Override
public void run() {
// TODO Auto-generated method stub
//一直生产(使用死循环)
while(true) {
//给仓库list对象加锁
synchronized (list) {
if(list.size() > 0) {
try {
//当前线程进入等待状态,并释放list集合的锁
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果程序能够执行到这里,说明仓库是空的,可以生产
Object object = new Object();
list.add(object);
System.out.println(Thread.currentThread().getName() + "===>" + object);
//唤醒消费者消费
list.notify();
}
}
}
}
//消费线程
class Consumer implements Runnable{
//仓库
private ArrayList list;
public Consumer(ArrayList list2) {
super();
this.list = list2;
}
@Override
public void run() {
// TODO Auto-generated method stub
//一直消费
while(true) {
synchronized (list) {
if(list.size() == 0) {
try {
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//程序能够执行到此处,说明仓库中有数据,进行消费。
Object object = list.remove(0);
System.out.println(Thread.currentThread().getName() + "---->" + object);
//唤醒生产者生产
list.notify();
}
}
}
}
实现一个账户的取款:
package javaCoreTest.threadSafe;
public class Test {
public static void main(String [] args) {
//创建账户对象(只创建1个)
Account act = new Account("act-001", 10000);
//创建两个线程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
//设置name
t1.setName("t1");
t2.setName("t2");
//启动线程取款
t1.start();
t2.start();
}
}
//账户类
//没有线程同步,会出现安全问题
class Account {
//账号
private String actnoString;
//余额
private double balance;
//对象
Object obj1 = new Object();
public Account(String actnoString, double balance) {
this.actnoString = actnoString;
this.balance = balance;
}
public Account() {
// TODO Auto-generated constructor stub
}
public String getActnoString() {
return actnoString;
}
public void setActnoString(String actnoString) {
this.actnoString = actnoString;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法
/*
* 在实例方法上可以使用synchronized吗?可以
* synchronized出现在实例方法上,一定锁的是this,不能是其他对象了。
* 这种方式不灵活。
*
* 另外一个缺点:synchronized出现在实例方法上,表示整个方法体都需要同步,
* 可能会无故扩大同步的范围,导致程序的执行效率降低。
*
* 这种方式不常用。
*/
public synchronized void withdraw(double money) {
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//更新余额
this.setBalance(after);
}
}
//账户线程
class AccountThread extends Thread{
//两个线程必须共享同一个账号对象
private Account act;
//通过构造方法传递过来账号对象
public AccountThread(Account act) {
this.act = act;
}
@Override
public void run() {
//run方法的执行表示取款操作
//假设取款5000
double money = 5000;
//取款
//多线程并发执行这个方法
//synchronized (act) {//这种方式也可以,只不过扩大了同步的范围,效率变低了
act.withdraw(money);
//}
System.out.println(Thread.currentThread().getName() + "账户" + act.getActnoString() + "取款成功,余额" + act.getBalance());
}
}