目录
一、多线程
1、线程的基础知识
1.1 什么是进程?什么是线程?
进程是一个应用程序(一个进程是一个软件)
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程。
1.2 进程和线程的关系
举例:
阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
京东:进程
刘强东:京东的一个线程
进程可以看作是现实生活当中的公司
线程可以看作是公司当中的某个公司
注意:
线程A和线程B的内存独立不共享。(阿里和京东资源不共享)
在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈
java中之所以有多线程机制,目的就是为了提高程序的处理效率
思考:在使用了多线程之后,main方法结束,只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈
1.3 多线程并发的理解
2、实现多线程
java语言中,实现线程有两种方式。
java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就可以
2.1 第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法
package 多线程;
/**
* 实现线程的第一种方式
* 编写一个类,继承Thread
*
*
* 注意:
* 方法体当中的代码永远都是自上而下执行的
*
*
* 以下程序的输出的结果有以下的特点:
* 有先有后
* 有多又少
*/
public class ThreadTest02 {
public static void main(String[] args) {
//这里是main方法,这里的代码属于主线程,在主栈里运行
//新建一个分支线程对象
MyThread myThread=new MyThread();
//启动线程
//myThread.run(); //不会启动线程,不会分配新的分支栈,因此这种方式还是在主栈中,就是单线程
//调用start方法,start()方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间这段代码完成之后,瞬间就结束了。
//这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟出来,start()方法就结束了。线程就启动成功了。
//启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
//run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的
myThread.start(); //这行代码不结束,下面的代码不会执行,但这行代码执行很快就结束了,
//这里的代码还是主线程中
for(int i=0;i<100;i++){
System.out.println("主线程---"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//编写程序,这段程序运行在分支线程种(分支线)
for(int i=0;i<100;i++){
System.out.println("分支线程---"+i);
}
}
}
可以看见,使用start()方法,主线程和分支线程并发进行,互不干扰
package 多线程;
/**
* 实现线程的第一种方式
* 编写一个类,继承Thread
*
*
* 注意:
* 方法体当中的代码永远都是自上而下执行的
*
*
* 以下程序的输出的结果有以下的特点:
* 有先有后
* 有多又少
*/
public class ThreadTest02 {
public static void main(String[] args) {
//这里是main方法,这里的代码属于主线程,在主栈里运行
//新建一个分支线程对象
MyThread myThread=new MyThread();
//启动线程
myThread.run(); //不会启动线程,不会分配新的分支栈,因此这种方式还是在主栈中,就是单线程
//调用start方法,start()方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间这段代码完成之后,瞬间就结束了。
//这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟出来,start()方法就结束了。线程就启动成功了。
//启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
//run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的
//myThread.start(); //这行代码不结束,下面的代码不会执行,但这行代码执行很快就结束了,
//这里的代码还是主线程中
for(int i=0;i<100;i++){
System.out.println("主线程---"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//编写程序,这段程序运行在分支线程种(分支线)
for(int i=0;i<100;i++){
System.out.println("分支线程---"+i);
}
}
}
可以看见,使用run();方法是没有启动新的线程的,因此不是多线程,只是一个方法的调用,先执行完分支线程的输出,然后才执行主线程的输出
2.2 第二种方式
编写一个类,实现java.lang.Runnable接口,实现run方法
package 多线程;
public class ThreadTest03 {
public static void main(String[] args) {
//创建一个可运行的对象
//MyRunnable r=new MyRunnable();
//将可运行的对象封装成一个线程对象
//Thread t=new Thread(r);
//合并代码
Thread t=new Thread(new MyThread());
//启动线程
t.start();
for(int i=0;i<100;i++){
System.out.println("主线程---"+i);
}
}
}
//这并不是一个线程类,只是一个可运行的类,它还不是一个线程
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("分支线程---"+i);
}
}
}
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承别的类(Java是单继承),更加灵活。
附:通过匿名内部类实现
package 多线程;
public class ThreadTest04 {
public static void main(String[] args) {
//采用匿名内部类的方式
//这里是通过一个没有名字的类,new出来的对象
/**注意:!!
* 这里参数放new接口实际上是new[匿名] implements Runnable,并不是真的用接口new对象
*/
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("分支线程---" + i);
}
}
});
//启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程---" + i);
}
}
}
2.3 第三种方式
实现Callable接口。(JDK8新特性)
这种方式实现的线程可以获取线程的返回值,之前的两种方式是不可以获取线程返回值的,因为run方法返回void。
系统委派一个线程去执行任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么才能拿到这个执行结果呢?使用第三种方式:实现Callable接口方式。
package 多线程;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;//JUC包下的,属于java的并发包,老JDK种没有这个包
/**
* 实现线程的第三种方法:
* 实现Callable接口
* 这种方式的优点:可以获取到线程的执行结果
* 这种方式的缺点:效率低,在获取t线程执行结果的时候,当前线程受阻
*/
public class ThreadTest14 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//第一步:创建一个“未来任务类”对象
//参数非常重要,需要给一个Callable接口实现类对象
//Callable是接口,new的话要使用匿名内部类
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception { //call()方法相当于方法,只不过有一个返回值
System.out.println("call method begin");
Thread.sleep(1000*10);
System.out.println("call method end");
int a=100;
int b=100;
return a+b;//自动装箱,返回一个Object对象,因此300结果自动变成Integer。
}
});
//创建线程对象
Thread t=new Thread(task);
//启动线程
t.start();
//这里是main方法,这是在主线程中,那么在主线程中,怎么获取t线程中的返回结果?
//使用get()方法会导致"当前线程阻塞"
Object obj=task.get();
System.out.println("线程执行结果"+obj);
//main方法这里的而程序要想执行必须等待get()方法的结束
//而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果,但是另一个线程的执行是需要时间的
System.out.println("Java");
}
}
3、线程的生命周期
新建状态,就绪状态,运行状态,阻塞状态,死亡状态
4、获取线程名字
package 多线程;
/**
* 1、怎么获取当前线程对象?
* Thread t=Thread.currentThread();
* 返回值t就是当前线程
*
* 2、获得线程对象名字。
* 线程对象.getName("名字");
*
* 3、修改线程对象名字
* 线程对象.setName("名字");
*
* 4、注意:当线程没有设置名字的时候,默认名字是Thread-0,Thread-1,Thread-2
*/
public class ThreadTest05 {
public void dosome(){
Thread.currentThread().getName();
}
public static void main(String[] args) {
//代码出现在main方法当中,所以当前线程就是主线程
Thread currentThread=Thread.currentThread();//这是个静态方法
System.out.println(currentThread.getName());
System.out.println("----"+currentThread.getName());//
//创建线程对象
MyThread2 t=new MyThread2();
MyThread2 t2=new MyThread2();
//获取线程名字
System.out.println(t.getName());
//设置线程的名字
t.setName("tttt");
String tName=t.getName();
System.out.println(tName);
t2.setName("qqqq");
//启动线程
t.start();//t线程启动
t2.start();
}
}
class MyThread2 extends Thread{
public void run(){
for(int i=0;i<100;i++){
//currentThread就是当前线程对象,当前线程是谁启动的,当前线程的名字就是谁的
//当t1线程执行run方法,那么这个线程就是t1,名字为tttt
//当t2线程执行run方法,那么这个线程就是t2,名字为qqqq
Thread currentThread=Thread.currentThread();
System.out.println(currentThread.getName()+i);//这个方法更加常用,下面两个方法有局限性
//System.out.println(this.getName()+i);
//System.out.println(super.getName()+i);
}
}
}
5、线程的sleep方法
package 多线程;
/**
* static void sleep(long millis)
* 1、静态方法:Thread.sleep(1000);
* 2、参数是毫秒
* 3、作用:让当前线程进入休眠,进入”阻塞状态“,放弃占有CPU时间片,让给其他线程使用。
* 这行代码出现在A线程种,A线程就会进入休眠
* 4、Thread.sleep()方法,可以做到这种效果:
* 间隔特定的时间,去执行一段 特点的代码,每隔多久执行一次
*/
public class ThreadTest06 {
public static void main(String[] args) {
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后执行这里的代码
System.out.println("hello world");
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
5.1 关于sleep方法的面试题
package 多线程;
/**
* 关于Thread.sleep()方法的一个面试题
*
* 注意:sleep方法出现在哪里哪里睡,
*/
public class ThreadTest07 {
public static void main(String[] args) {
Thread t=new MyThread3();
t.setName("t");
t.start();
try {
//问题:这行代码会让线程t进入休眠状态吗?
t.sleep(1000*5); //在执行的时候还是会转换为Thread.sleep(1000*5)
// 这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠
//这行代码出现在main方法种,main线程睡眠
}catch (Exception e){
e.printStackTrace();
}
System.out.println("hello world");
}
}
class MyThread3 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
//如果在这里使用sleep方法,那么就是该分支线程睡眠
System.out.println(Thread.currentThread().getName());
}
}
}
5.2 终止线程的睡眠
方法一:通过interrupt()方法
package 多线程;
/**
* sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程?
* 注意:这个不是终断线程的执行,是终断线程的睡眠
*/
public class ThreadTest08 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable2());
t.setName("ttt");
t.start();
//希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了)
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终断t线程的睡眠(这种终端睡眠的方式依靠了java的异常处理机制,会在sleep出报出异常来停止)
t.interrupt(); //干扰,一盆冷水过去!
}
}
class MyRunnable2 implements Runnable{
//重点:run方法当中的异常不能throw,只能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");
System.out.println(Thread.currentThread().getName());
//其他方法可以throws(这些不是继承的父类方法,而是自己的方法)
/*
public void doOther(){
}
*/
}
}
方法二:强行终止线程进行(合理方法)
package 多线程;
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 r=new MyRunnable4();
Thread t=new Thread(r);
t.setName("t");
t.start();
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程,你想什么时候终止t的执行,那么你把标记改为false就结束了
r.run=false;
}
}
class MyRunnable4 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;
}
}
}
}
6、线程的调度(了解)
6.1 常见线程调度模型
常见的线程调度模型有两种:抢占式调度线程和均分式调度模型
抢占式调度线程:哪个线程的优先级比较高,抢到CPU时间片的概率就高一些。java中采用的就是抢占式调度线程
均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。
6.2 java中提供给的与线程调度有关的方法
实例方法:
1、void setPriority(int newPriority) //设置线程的优先级
2、int getPriority() //获取线程优先级
最高优先级是10,最低优先级是1,默认优先级是5。
package 多线程;
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 MyRunnable5());
t.setPriority(10);
t.setName("t");
t.start();
//优先级较高的,只是抢到CPU的时间片相对多一些(大概率方向偏向于优先级比较高的)
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class MyRunnable5 implements Runnable{
@Override
public void run() {
//获取线程优先级,默认为5
//System.out.println(Thread.currentThread().getName()+"线程的默认优先级"+Thread.currentThread().getPriority());
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
3、void join() 合并线程
package 多线程;
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin");
Thread t=new Thread(new MyRunnable7());
t.setName("t");
t.start();
//合并线程,t合并到当前线程中,当前线程受堵塞
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable7 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
静态方法:
1、static void yield() //让位方法。暂停当前正在执行的线程对象,并且执行其他线程。yiled()方法不是阻塞方法,让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从"运行状态"回到就绪状态。
package 多线程;
/**
* 让位,让当前线程暂停,回到就绪状态,让给其他线程
* 静态方法:Thread.yield();
*/
public class ThreadTest12 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable6());
t.setName("t");
t.start();
for(int i=0;i<=100;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class MyRunnable6 implements Runnable{
@Override
public void run() {
for(int i=1;i<=100;i++){
//每10个让位一次
if(i%10==0){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
7、同步编程模型和异步编程模型
8、线程安全(重点)
8.1 存在的安全问题
从上图思考,什么时候数据在多线程并发的环境下会存在安全问题呢?
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为
满足以上3个条件以后,就会存在线程安全问题
8.2 解决线程安全问题
8.3 模拟账户取款安全问题
Test类:
package 线程安全测试;
public class Test {
public static void main(String[] args) {
//创建账户对象
Account act=new Account("act001",10000);
//创建两个线程
Thread t1=new AccountThread(act);
Thread t2=new AccountThread(act);
//设置name
t1.setName("t1");
t2.setName("t2");
//启动线程取款
t1.start();
t2.start();
}
}
Account类:
package 线程安全测试;
/**
* 银行账号
*/
public class Account {
//账号
private String actno;
//余额
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法:
public void withdraw(double money){
//t1和t2并发这个方法。(t1和t2是两个栈,两个栈操作堆中的同一个对象)
double before=this.getBalance();
double after=before-money;
//更新余额
//当t1执行到这里了,但是还没有来得及执行这行代码,t2线程进来withdraw方法了,此时一定出问题
//在这里模拟网络延迟,100%出现问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
AccountThread类:
package 线程安全测试;
public class AccountThread extends Thread{
//两个线程必须共享同一个账户对象
private Account act;
//通过构造方法传递
public AccountThread(Account act){
this.act=act;
}
public void run(){
//run方法的执行表示取款操作
//假设取款5000
double money=5000;
//多线程并发实现取款
act.withdraw(money);
System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额"+act.getBalance());
}
}
结果:
解决方法:增加synchronized () 方法
package 线程安全测试.解决方案;
/**
* 银行账号
* 不适用线程同步机制,多线程对同一个用户进行取款,出现线程安全问题
*/
public class Account {
//账号
private String actno;
//余额
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法:
public void withdraw(double money) {
//以下这几行代码必须是线程排队的,不能并发
//一个线程吧这里的代码全部执行结束之后,另一个线程才能进来
/**
* 线程同步机制的语法:
* synchronized () {
* //线程同步代码块
* }
*
* synchronized后面小括号中传的这个数据是十分重要的。
* 这个数据必须是多线程共享的数据,才能达到多线程排队
*
*
* ()中写什么?
* 那么看你想让哪些线程同步,假设有t1,t2,t3,t4,t5,有5个线程
* 如果你只希望t1,t2,t3排队,t4,t5不需要排队,你一定要在()中写一个t1,t2,t3共享的对象,而t4和t5不共享
*/
/*这里的共享对象是:账户对象。
* 账户对象是共享的,那么this就是账户对象
* 不一定是this,只要是多线程共享的那个对象
*/
/*
在java中,任何一个对象都有一把锁,其实这把锁就是标记(只是把他叫作锁)
100个对象,100把锁
以下代码的执行原理
1、假设t1和t2线程并发,开始执行以下代码的时候,肯定一个先一个后。
2、假设t1先执行了,遇到synchronized,这个时候自动找”后面共享对象“的对象锁,
找到之后,并占有这把锁的,直到同步代码块代码结束,这把锁才会释放
3、假设t1已经占有这把锁,此时t2也遇到了synchronized关键字,也去占有后面共享对象的这把锁,结这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
直到t1把同步代码结束了,t1会归还这把锁,此时t2才可以占有这把锁,进入同步代码块执行程序
需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的
*/
//synchronized (this) {//最好选这个,因为在这个例子中,别的线程操作的是别的账户,只要不是同时对同一个账户同时取款的都不需要等待
//this指向的Account只有一个,那么Account里面的所有成员变量都只有一个,因此都可以作为参数
//synchronized (actno) {//可以使用
//Object obj2=new Object();//不能使用,注意这里是局部变量,每一个对象new的时候都会创建一个新的obj2对象,不是共享对象
//synchronized (obj2){
//synchronized ("abc"){//可以使用,因为"abc"在字符串常量池中,而字符串常量池是共享的(此时所有线程都会同步)
synchronized (this) {
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
}
8.4 扩大同步范围
package 线程安全测试.解决方案;
public class AccountThread extends Thread{
//两个线程必须共享同一个账户对象
private Account act;
//通过构造方法传递
public AccountThread(Account act){
this.act=act;
}
public void run(){
//run方法的执行表示取款操作
//假设取款5000
double money=5000;
//多线程并发实现取款
synchronized (act) {//这种方法也可以,只不过扩大了同步范围,效率更低。
//synchronized (this){} 这里的this是AccountThread对象,这个对象不共享
act.withdraw(money);
}
System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额"+act.getBalance());
}
}
8.5 三大变量的安全问题
synchronized重点:
synchronized重点(转载)https://blog.csdn.net/qq_41279172/article/details/104308214
8.6 synchronized面试题1
package 线程安全测试.synchronized面试题目;
/**
* doOther方法执行的时候需要等待doSome结束吗?
* 不需要,因为doOther()方法没有使用synchronized,没有被锁
*/
public class Exam01 {
public static void main(String[] args) throws InterruptedException {
MyClass mc=new MyClass();
Thread t1=new MyThread(mc);
Thread t2=new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);//这个睡眠的作用是保证t1线程先进行
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc=mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
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{},执行doOther时就需要
public void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
8.7 synchronized面试题2
package 线程安全测试.synchronized面试题2.synchronized面试题目;
/**
* doOther方法执行的时候需要等待doSome结束吗?
* 需要,因为静态方法是类锁,类锁不管创建了几个对象,类锁只有一把。
*/
public class Exam01 {
public static void main(String[] args) throws InterruptedException {
MyClass mc=new MyClass();
Thread t1=new MyThread(mc);
Thread t2=new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);//这个睡眠的作用是保证t1线程先进行
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc=mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
public synchronized static 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{},执行doOther时就需要
public void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
8.8 死锁
package 线程安全测试.死锁;
/**
* 死锁代码要会写,一般面试官会要求你写
* 只有会写,才能在以后的开放中避免这种现象
* 死锁很难调试
*/
/**
* o1锁了以后睡了,o2锁了也睡了,醒来以后互相卡住,无法继续
* 因此 synchronized使用时不要嵌套使用
*/
public class DeadLock {
public static void main(String[] args) {
Object o1=new Object();
Object o2=new Object();
Thread t1=new MyThread1(o1,o2);
Thread t2=new MyThread2(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1=o1;
this.o2=o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread{
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1=o1;
this.o2=o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
8.9 以后开发中怎么解决线程安全问题
9、守护线程
9.1 守护线程概念:
9.2 守护线程实现
package 守护线程;
public class ThreadTest1 {
public static void main(String[] args) {
Thread t=new BakDateThread();
t.setName("备份数据的线程");
//启动线程之前,将线程变为守护线程,那么死循环在用户线程结束后会自动结束
t.setDaemon(true);
t.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 BakDateThread extends Thread{
public void run(){
int i=0;
while(true){
System.out.println(Thread.currentThread().getName()+"-->"+(++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
死循环自动终止了
10、定时器
package 计时器;
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 timer=new Timer(true); //将计数器作为守护线程的方式
//指定定时任务
//timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime =sdf.parse("2022-02-28 19:33:00");
timer.schedule(new LogTimeTask(),firstTime,1000*10);
//也可以使用匿名内部类实例化TimerTask抽象方法
timer.schedule(new TimerTask(){
@Override
public void run() {
}
},firstTime,1000*10);
}
}
}
}
//编写一个定时任务类
//假设这是ige记录日志的定时任务
//实例化抽象类TimeTask通过子类继承,然后实例化子类
class LogTimeTask extends TimerTask{
@Override
public void run() {
//编写你需要执行的任务就行了
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime=sdf.format(new Date());
System.out.println(strTime+"成功完成了一次数据备份");
}
}
11、Object类中的wait和notify方法
11.1 概述
11.2 生产者和消费者模式理解
11.3 wait和notify代码
要多理解此处代码和原理
package 多线程;
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对象上等待的线程唤醒,
*
* 7、模拟这样一个需求:
* 仓库我们采用List集合。
* 1个元素就表示仓库满了。
* 如果List集合中元素个数为0,就表示仓库空了。
* 保证List集合中永远都是最多存储1个元素
* 必须做到这种效果:生产1个消费一个
*
*/
public class ThreadTest15 {
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个元素
//当前线程进入等待状态,并且释放Producer之前占有的list集合的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能执行到这里说明仓库是空的,可以生产
Object obj=new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName()+"-->"+obj);
//唤醒生产者生产
list.notifyAll();
}
}
}
}
//消费线程
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){
//仓库已经空了,消费者线程等待,释放掉list集合的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能执行到此处说明仓库中有数据,进入消费
Object obj=list.remove(0);
System.out.println(Thread.currentThread().getName()+"-->"+obj);
//唤醒生产者生产
list.notifyAll();
}
}
}
}