多线程
一、线程的概述
1.1什么是进程?什么是线程?
进程是一个应用程序(一个进程是一个软件)。
线程是个进程中的执行场景/执行单元。
一个进程可以启动多个线程。
1.2 对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld回车之后。会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
最起码,现在java程序中至少有两个线程并发,一个是垃圾回收线程,一个 是执行main方法的主线程。
1.3 进程和线程的关系?举个例子
阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
京东:进程
刘强东:京东的一个线程
妹妹:京东的一个线程
进程可以看作是现实生活当中的公司。线程可以看作是公司当中的某个员工。
注意:
进程A和进程B的内存独立不共享(阿里巴巴和京东资源不会共享!)
魔兽游戏是一个进程,酷狗音乐是一个进程,这两个进程是独立的,
不共享资源。
线程A和线程B呢?
在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内 存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
火车站,可以看作是一个进程。
火车站的每一个售票窗口可以看作是一个线程。我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
1.4 思考问题
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。
1.5 分析:对于单核的cpu来说,真的可以做到真正的多线程并发吗?
对于多核的cpu电脑来说,真正的多线程并发是没问题的。
4核cpu表示同一时间点上,可以真正的有4个进程并发执行。
什么是真正的多线程并发?
t1线程执行t1的。t2线程执行t2的。t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。(实际上也就是并行)
单核的cpu表示只有一个大脑:
不能真正的做到多线程并发,但是可以做到给人一种"多线程并发"的感觉。对于单核的cpu来说,在某一个时间点上实际上只能处理一件事,但是由于cpu的处理速度极快,多个线程之间频繁的切换执行,给人的感觉是:多个事情同时在做!!!
单核CPU:只能并发
多核CPU:并行+并发
二、实现线程的三种方式
2.1 继承Thread类
步骤:
1.编写一个类,直接继承java.lang.Thread,重写run()方法
2.调用start()方法启动线程
package com.bjpowernode.java.thread;
/*
实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法
怎么创建线程对象?new就行了
怎么启动线程呢?调用start()方法
*/
public class ThreadTest02 {
public static void main(String[] args) {
//这里是main方法,这里的代码属于主线程,在主栈中运行。
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
//这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了
//启动工程的线程会自动调用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() {
//编写程序,这段程序运行在分支线程中(分支栈)
for (int i = 0; i < 1000; i++) {
System.out.println("分支线程===》" + i);
}
}
}
2.2 实现Runnale接口
步骤:
1.编写一个类,实现java.lang.Runnable接口,实现run()方法。
2.创建1中的类,将其作为参数传到Thread类的构造器中,调用Thread类中的start()方法启动线程。
注意:实现Runnable接口的方式比较常用,因为一个类实现了接口,他还可以继承其他的类,更灵活。
package com.bjpowernode.java.thread;
/*
实现线程的第二种方式:编写一个类实现java.lang.Runnble接口
*/
public class TreadTest03 {
public static void main(String[] args) {
//创建一个可运行的对象
MyRunnable r = new MyRunnable();
//将可运行的对象封装成一个线程对象
//Thread t = new Thread(r);
//合并代码
Thread t = new Thread(new MyRunnable());
//启动线程
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程===》" + i);
}
}
}
//这并不是一个线程类,是一个可运行的类。它还不是一个线程
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("分支线程===》" + i);
}
}
}
2.3 采用匿名内部类的方式实现多线程
package com.bjpowernode.java.thread;
/*
采用匿名内部类实现多线程
*/
public class ThreadTest04 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("分支线程:===》" + i);
}
}
}).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程:===》" + i);
}
}
}
2.4 实现Callable接口(JDK8新特性)
实现Callable接口这种方式可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程的返回值的,因为run()方法返回void
思考:
系统委派一个线程区执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
使用第三种方式:实现Callable接口方式。
package com.bjpowernode.java.thread;
//JUC包下的,属于java的并发包,老JDK中没有这个包,新特性
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest15 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//第一步:创建一个“未来任务类对象”
//参数非常重要,需要给一个Callable接口实现类对象
FutureTask futureTask = new FutureTask(new Callable() {
//call()方法就相当于run()方法。只不过这个有返回值
@Override
public Object call() throws Exception {
//线程执行一个任务,执行之后可能会有一个执行结果
//模拟执行
System.out.println("call method begin");
Thread.sleep(1000);
System.out.println("call method end");
int a = 100;
int b = 200;
return a + b;
}
});
//第二步 创建线程对象
Thread thread = new Thread(futureTask);
//启动线程
thread.start();
//这里是main方法,这是在主线程中。
//在主线程中,怎么获取t线程的返回结果。
//通过FutureTask类的get()方法就可以获取线程的返回结果
//get()方法的执行会导致当前线程的阻塞
Object o = futureTask.get();
System.out.println(o);
//main方法这里的程序要想执行必须等待get()方法的结束
//而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果
//另一个线程执行是需要时间的
}
}
三、线程的生命周期
四、获取线程的名字
4.1获取线程对象的名字
String name = 线程对象.getName();
4.2修改线程对象的名字
线程对象.setName(String name);
4.3默认线程的名字
Thread-0
Thread-1
Thread-2
Thread-3
五、获取当前线程
5.1 获取当前线程对象
Thread t = Thread.currentThread();
注意:currentThread()是Thread类中的一个static方法,当方法出现在主线程中就获取到主线程,出现在分支线程中就获取到分支线程。(类似this)
5.2 获取当前相乘的名字
Thread t = Thread.currentThread();
String name = t.getName();
六、sleep()方法
6.1 static void sleep(long mills)
①静态方法:Thread.sleep(long millis)
②参数是毫秒
③作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有cpu时间片,让给其他线程使用。
(重点)
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
④Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
6.2 面试题
package com.bjpowernode.java.thread;
public class ThreadTest07 {
public static void main(String[] args) {
//创建线程对象
MyThread3 t = new MyThread3();
t.setName("t");
t.start();
//调用sleep()方法
try {
//会让线程t进入休眠状态吗
t.sleep(1000 * 5);//在执行的时候还是会转换成:Thread.sleep(1000*5);
//这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后才会执行
System.out.println("hello world");
}
}
class MyThread3 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "===>" + i);
}
}
}
七、中止线程的睡眠
线程对象.interrupt():中断线程的睡眠(这种中断睡眠的方式依靠了java的异常处理机制)
package com.bjpowernode.java.thread;
/*
sleep睡眠太久了,如果希望半道上醒来,应该怎么办?也就是说怎么叫醒一个正在睡眠的线程
注意:这个不是终端线程的执行,是终止线程的睡眠
*/
public class ThreadTest08 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName("t");
t.start();
//希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了)
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终端t线程的睡眠(这种中断睡眠的方式依靠了java的异常处理机制)
t.interrupt();
}
}
class MyRunnable2 implements Runnable{
//重点:run()当中的异常不能throws,只能try{}catch{}
//因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"==>begin");
//睡眠一年
try {
Thread.sleep(1000*60*60*24*365);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"==>end");
}
}
八、强行终止一个线程的执行
8.1 stop()方法
注意:这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了, 线程没有保存数据将会丢失。不建议使用
package com.bjpowernode.java.thread;
/*
在java中怎么强行终止一个线程的执行
这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了
线程没有保存数据将会丢失。不建议使用
*/
public class ThreadTest09 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable3());
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后强行终止t线程
t.stop();//已过时(不建议使用,因为此种方式容易造成数据丢失)
}
}
class MyRunnable3 implements Runnable {
@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();
}
}
}
}
8.2 合理的终止一个线程
package com.bjpowernode.java.thread;
public class ThreadTest10 {
public static void main(String[] args) {
MyThread4 myThread4 = new MyThread4();
Thread thread = new Thread(myThread4);
thread.start();
//模拟5秒
try {
thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread4.run = false;
}
}
class MyThread4 implements Runnable {
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (run) {
System.out.println(Thread.currentThread().getName() + "==>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//return 就结束了,你在结束之前还有什么没保存的。
//在这里可以保存
//终止当前线程
return;
}
}
}
}
九、线程调度概述
9.1 常见的线程调度有哪些
抢占式调度模型:
哪个线程的优先级比较高,抢到cpu时间片的概率就高一些/多一些。java采用的就是抢占式调度模型。
均分式调度模型:
平均分配cpu时间片。每个线程占有的cpu时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的就是这种方式。
9.2 java中提供了哪些方法是和线程调度有关系的呢?
实例方法:
void setPriority(int newPriority):设置线程的优先级
int getPriority():获取线程的优先级
最低优先级:1
默认优先级:5
最高优先级:10
优先级比较高的获取cpu时间片可能会多一些(但也不完全是,大概率是多的)
静态方法:
static void yield():让位方法
暂停当前正在执行的线程对象,并执行其他线程,yield()方法不是阻塞方法。让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从"运行状态"回到"就绪状态"。
注意:在回到就绪之后,有可能还会再次抢到。
实例方法:
void join():合并线程
class MyThread1 extends thread{
public void doSome(){
MyThread2 t = new MyThread2();
//当前线程进入阻塞状态,t线程执行,直到t线程结束,当前线程才能继续执行。
t.join();
}
}
class MyThead2 extends Thread{
}
9.3 关于线程的优先级
package com.bjpowernode.java.thread;
import static java.lang.Thread.currentThread;
public class ThreadTest11 {
public static void main(String[] args) {
//设置当前线程的优先级是10
currentThread().setPriority(Thread.MAX_PRIORITY);
//获取当前线程的优先级
System.out.println(currentThread().getName() + "线程的优先级是:" + currentThread().getPriority());
MyRunnable4 myRunnable4 = new MyRunnable4();
Thread thread = new Thread(myRunnable4);
//thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
}
class MyRunnable4 implements Runnable {
@Override
public void run() {
//获取当前线程的优先级
System.out.println(currentThread().getName() + "线程优先级是:" + currentThread().getPriority());
}
}
9.4 线程让位 Thread.yield()
让位:当前线程暂停回到就绪状态,让给其他线程。
静态方法:Thread.yield()
package com.bjpowernode.java.thread;
public class ThreadTest12 {
public static void main(String[] args) {
//开启一个分支线程
Thread thread = new Thread(new MyRunnable6());
thread.setName("t");
thread.start();
//主线程
for (int i = 1; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class MyRunnable6 implements Runnable {
@Override
public void run() {
for (int i = 1; i < 10000; i++) {
//每100个让位一次
if(i%100==0){
Thread.yield();//静态方法让位 当前线程暂停一下让给主线程
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
9.5 线程合并 Thread.join()
Thread.join():静态方法 将当前线程合并到另一个线程中,另一个线程受阻,直到当前线程结束。(也可以理解为强行插队)
package com.bjpowernode.java.thread;
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin");
//开启一个分支线程
Thread thread = new Thread(new MyRunnable7());
thread.setName("t");
thread.start();
//合并线程
try {
thread.join();//t线程合并到当前线程中,当前线程受阻塞,t线程执行直到结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable7 implements Runnable {
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "==>" + i);
}
}
}
十、线程安全问题(synchronized)
10.1为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道 ,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的(重点)
10.2 什么时候数据在多线程并发的环境下会存在安全问题?
三个条件(重点):
条件1:多线程并发
条件2:有共享数据
条件3:共享数据有修改的行为。
满足以上三个条件之后,就会存在线程安全问题。
10.3 怎么解决线程问题
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行(不能并发)
用排队执行解决线程安全问题。
这种机制被称为:线程同步机制
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事。
10.4 异步编程模型和同步编程模型
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是多线程并发(效率较高,但线程不安全)
异步就是并发
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。
效率较低(但是线程安全)。
同步就是排队。
10.5 模拟线程安全问题
//银行账户类
package com.bjpowernode.java.threadsafe;
/*
银行账户类:
*/
public class Account {
private int actno;//账号
private double balance;//余额
public Account() {
}
public Account(int actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public int getActno() {
return actno;
}
public void setActno(int actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法
public void withdraw(double monkey){
//取款之前的余额
double before = this.getBalance();
//取款之后的余额
double after = before - monkey;
//模拟一下网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
this.setBalance(after);
}
@Override
public String toString() {
return "Account{" +
"actno=" + actno +
", balance=" + balance +
'}';
}
}
//账户线程类,run()方法执行取款操作
package com.bjpowernode.java.threadsafe;
public class AccountThread extends Thread {
//两个线程必须共享同一个账户对象
private Account act;
//通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act = act;
}
@Override
public void run() {
//run()方法的执行表示取款操作
//假设取款5000
double monkey = 5000;
act.withdraw(monkey);
System.out.println("账户"+act.getActno()+"取款成功,余额"+act.getBalance());
}
}
//测试类
package com.bjpowernode.java.threadsafe;
public class Test {
public static void main(String[] args) {
//创建账户对象(只创建一个)
Account account = new Account(110, 10000);
//创建两个分支线程
AccountThread t1 = new AccountThread(account);
AccountThread t2 = new AccountThread(account);
//给两个线程命名
t1.setName("t1");
t2.setName("t2");
//启动两个线程
t1.start();
t2.start();
}
}
结果:
账户110取款成功,余额5000.0
账户110取款成功,余额5000.0
出现了线程安全问题
10.6 使用同步代码块的方式解决线程安全
语法:
synchronized(同步监视器){
//将需要同步的代码全部卸载代码块中
}
注意:
synchronized后面的小括号中传的这个“数据”是相当关键的。这个数据必须是一个对象,这个对象必须是多线程共享的对象。才能达到多线程排队
()中写什么?
那要看你想让哪些线程同步。
假设t1、t2 t3 t4 t5有五个线程,你只希望t1 t2 t3排队,t4 t5不排队怎么办?你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说是不共享的。
package com.bjpowernode.java.threadsafe2;
/*
银行账户类:
使用同步机制来解决线程安全问题
*/
public class Account {
private int actno;//账号
private double balance;//余额
public Account() {
}
public Account(int actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public int getActno() {
return actno;
}
public void setActno(int actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法
public void withdraw(double monkey) {
//一以下这几行代码必须是线程排队的,不能并发
//一个线程把这里的代码全部执行结束之后,另一个线程才能进来
/*
线程同步机制的语法是:
synchronized(){
//线程同步代码块
}
synchronized后面的小括号中传的这个“数据”是相当关键的。
这个数据必须是多线程共享的数据。才能达到多线程排队(重点)
()中写什么?
那要看你想让哪些线程同步。
假设t1、t2 t3 t4 t5有五个线程,你只希望t1 t2 t3排队,t4 t5不排队怎么办?
你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说是不共享的。
这里的共享对象是:账户对象。
账户对象是共享的,那么this就是账户对象
不一定是this,这里只要是多线程共享的那个对象就行。
*/
synchronized (this) {
double before = this.getBalance();
double after = before - monkey;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
@Override
public String toString() {
return "Account{" +
"actno=" + actno +
", balance=" + balance +
'}';
}
}
10.7 对synchronized的理解
synchronized(同步监视器){}:
1.同步监视器一般情况下,是需要使用同步的多个线程之间共享的对象。
这样的话不同的同步线程之间是不需要等待的。使用各自的共享对象就可以进入同步代码块之中(建议使用)
2.当然也可以是所有线程共享的对象。
如果是这样的话,即使是不同的同步线程之间也是需要等待的,换而言之就是所有线程都需要等待。
10.8 哪些变量有线程安全问题?
java中有三大变量
成员变量:在堆中
静态变量:在方法区中
局部变量:在栈中
以上三大变量中:
局部变量和常量永远都不会存在线程安全问题。
因为局部变量不共享(一个线程一个栈)
实例变量在堆中,堆只有一个。
静态变量在方法区中,方法区只有一个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
10.9 同步方法(实例方法)
synchronized使用在实例方法上时,锁只能是this,不能是其他对象。
package com.bjpowernode.java.threadsafe3;
public class Account {
private int actno;//账号
private double balance;//余额
public Account() {
}
public Account(int actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public int getActno() {
return actno;
}
public void setActno(int actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法
/*
在实例方法上可以使用synchronized吗?可以的
synchronized出现在实例方法上,一定锁的是this
没的挑。只能是this.不能是其他对象了。
所以这种方式不灵活
另外还有一个缺点:synchronized出现在实例方法上,
表示整个方法体都需要同步,可能会无故的扩大同步的范围,
导致程序执行效率的降低。所以这种方式不灵活
synchronized使用在实例方法上有什么优点?
代码写少了。节俭了
如果共享的对象就是this,并且需要同步的代码块是整个方法体
建议使用这种方式
*/
public synchronized void withdraw(double money){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
@Override
public String toString() {
return "Account{" +
"actno=" + actno +
", balance=" + balance +
'}';
}
}
10.10 面试题
package com.bjpowernode.java.exam;
//面试题:doOther()方法的执行需不需要等待doSome()方法的结束
//不需要,因为doOther()方法没有synchronized的,也就意味着不需要锁。
//就可以直接执行了
public class Exam01 {
public static void main(String[] args) {
//创建一个MyClass对象
MyClass mc = new MyClass();
//创建两个线程
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
//给t1 t2线程设置名字
t1.setName("t1");
t2.setName("t2");
//开启两个线程
t1.start();
try {
Thread.sleep(1000);//这个线程的作用是保证t1线程先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1"){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2"){
mc.doOther();
}
}
}
class MyClass {
//synchronized出现在实例方法上,表示锁是this
public synchronized void doSome() {
System.out.println("doSome begin");
//睡眠十秒
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
package com.bjpowernode.java.exam2;
//面试题:doOther()方法的执行是否需要等待doSome()方法的结束
//需要;因为doSome()方法和doOther()都是synchronized方法
//使用的是同一个this监视器,一个方法占用锁资源,另一个方法
//就需要等待锁资源
public class Exam01 {
public static void main(String[] args) {
//创建一个MyClass对象
MyClass mc = new MyClass();
//创建两个线程
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
//给t1 t2线程设置名字
t1.setName("t1");
t2.setName("t2");
//开启两个线程
t1.start();
try {
Thread.sleep(1000);//这个线程的作用是保证t1线程先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass {
//synchronized出现在实例方法上,表示锁是this
public synchronized void doSome() {
System.out.println("doSome begin");
//睡眠十秒
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
package com.bjpowernode.java.exam3;
//面试题:doOther()方法的执行是否需要等待doSome()方法的结束
/*
不需要;因为现在MyClass 对象有两个是两把锁,调用方法时this实际上表示的是不同的对象,那么不同锁资源之间实际上是异步操作
*/
public class Exam01 {
public static void main(String[] args) {
//创建一个MyClass对象
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
//创建两个线程
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
//给t1 t2线程设置名字
t1.setName("t1");
t2.setName("t2");
//开启两个线程
t1.start();
try {
Thread.sleep(1000);//这个线程的作用是保证t1线程先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass {
//synchronized出现在实例方法上,表示锁是this
public synchronized void doSome() {
System.out.println("doSome begin");
//睡眠十秒
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
package com.bjpowernode.java.exam4;
//面试题:doOther()方法的执行是否需要等待doSome()方法的结束
/*
需要;因为此时的doOther()和doSome()是静态的同步方法
也就是说此时的锁资源是运行是类,所以此时无论两个线程
是否操作共享数据,都需要同步
*/
public class Exam01 {
public static void main(String[] args) {
//创建一个MyClass对象
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
//创建两个线程
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
//给t1 t2线程设置名字
t1.setName("t1");
t2.setName("t2");
//开启两个线程
t1.start();
try {
Thread.sleep(1000);//这个线程的作用是保证t1线程先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass {
//synchronized出现在静态方法上时,表示锁是运行时类
public static synchronized void doSome() {
System.out.println("doSome begin");
//睡眠十秒
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public static synchronized void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
10.11 在开发中应该怎么解决线程安全问题
是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步 机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
十一、死锁
package com.bjpowernode.java.deadlock;
/*
死锁:代码要求会写
一般面试官要求会写。只有会写的,才会在以后的开发中注意这个事儿
因为死锁很难调试
synchronized在开发中最好不要嵌套使用,一不小心就可能导致死锁情况的发生
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
//创建两个线程,连个线程共享o1 o2;
MyThread1 t1 = new MyThread1(o1, o2);
MyThread1 t2 = new MyThread1(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;
}
@Override
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;
}
@Override
public void run() {
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
十二、 守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程
守护线程用在什么地方法呢?
每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果都结束了,守护线程自动退出,没有必要进行数据备份了。
package com.bjpowernode.java.thread;
/*
守护线程
*/
public class ThreadTest14 {
public static void main(String[] args) {
BakThread bakThread = new BakThread();
bakThread.setName("备份数据的线程");
//启动线程之前,将线程设置为守护线程
bakThread.setDaemon(true);//这一句代码必须放在启动线程之前
bakThread.start();
//主线程:主线程是用户线程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakThread extends Thread {
@Override
public void run() {
int i = 0;
//即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动终止
while (true) {
System.out.println(Thread.currentThread().getName() + "-->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
十三、定时器
定时器的作用:
间隔特定的时间,执行特定的程序。
例如:每周要进行银行账户的总账操作。
每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在Java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用,不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际开发中,目前使用较多的是spring框架中的springTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
package com.bjpowernode.java.thread;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/*
使用定时器指定定时任务
*/
public class TimerTest {
public static void main(String[] args) throws ParseException {
//创建定时器对象
Timer timer = new Timer();
//Timer timer1 = new Timer(true);//这种方式是指创建一个定时器对象,并指定该对象是一个守护线程
//指定定时任务timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date parse = simpleDateFormat.parse("2021-2-4 13:10:00");
//timer.schedule(new LogTimerTask(), parse, 1000 * 10);
//使用匿名内部类的方式
timer.schedule(new TimerTask() {
@Override
public void run() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(new Date());
System.out.println(format+"成功完成了一次数据备份");
}
}, parse, 1000 * 10);
}
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{
@Override
public void run() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(new Date());
System.out.println(format+"成功完成了一次数据备份");
}
}
十四、wait()、notify()、notifyAll()
第一:wait()和notify()方法不是线程对象的方法,是Java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
wait()、notify()都不是通过线程对象调用的。
第二:wait()方法作用?
Object o = new Object();
o.wait();
表示:
让正在o对象上活动的线程进入等待状态,无期限的等待,直到被唤醒为止。o.wait()方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。
第三:notify()方法作用?
Object o = new Object();
o.notify();
表示:
唤醒正在o对象上等待的线程。
还有一个notifyAll()方法:
这个方法是唤醒o对象上处于等待的所有线程。
重点:o.wait()方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁。
o.notify()方法只会通知,不会释放之前占有的o对象的锁。
注意:(重点)
被notify唤醒的线程并不会立马执行,直到当前线程放弃锁资源的时候,唤醒的线程才能继续执行。
14.1 生产者和消费者问题
生产一个消费一个
package com.bjpowernode.java.thread;
import java.util.ArrayList;
import java.util.List;
/*
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) {
//创建一个仓库对象,共享的
List 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 List list;
public Producer(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.notify();
}
}
}
}
//消费线程
class Consumer implements Runnable {
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
//一直消费
while (true) {
synchronized (list) {
if (list.size() == 0) {
//仓库已经空了
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能执行到这里说明仓库里有数据,可以进行消费
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
list.notify();
}
}
}
}
ublic Producer(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.notify();
}
}
}
}
//消费线程
class Consumer implements Runnable {
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
//一直消费
while (true) {
synchronized (list) {
if (list.size() == 0) {
//仓库已经空了
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能执行到这里说明仓库里有数据,可以进行消费
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
list.notify();
}
}
}
}