- 1.java有三大变量?
- 2.如何选择类?
- 3.synchronized的三种写法
- 第一种:同步代码块
- 第二种:在实例方法上使用synchronized
- 第三种:在静态方法上使用synchronized
- 4.synchronized面试题
- 5.死锁的写法
- 6.总结:在以后开发中怎么解决线程安全问题?
- 7.守护线程
- java语言中线程分为两大类:
- 守护线程的特点:
- 主线程main方法是一个用户线程。
- 守护线程用在什么地方:
- 8.定时器
- 定时器的作用:
- 定时器的应用场景:
- 如何实现定时器:
- 9.实现线程的第三种方式:
实例变量: 在堆中。
静态变量: 在方法区。
局部变量: 在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量永远不共享。(一个线程一个栈)
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。
ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable实线程安全的。
灵活
synchronized(共享线程对象){
同步代码块;
}
synchronized(this){ //线程安全
double before=this.getBlance();
double after=before-money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBlance(after);
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this 并且同步代码块是整个方法体。
public synchronized void withdraw(double money){
double before=this.getBlance();
double after=before-money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBlance(after);
}
第三种:在静态方法上使用synchronized
表示找类锁。
类锁永远只有一把。
就算创建了100个对象,那类锁也只有一把。
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。
4.synchronized面试题
面试题1:
package com.jmpower.javase.exam;
//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需等待,因为doOther()方法没有synchronized
public class Exam01 {
public static void main(String[] args) {
MyClass mc=new MyClass();
Thread t1=new MyThread(mc);
Thread t2=new MyThread(mc);
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{
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome end");
}
public void doOther(){
System.out.println("doOher begin");
System.out.println("doOther end");
}
}
面试题2(在面试题1的基础上,将doOther修改为被synchronized修饰)
//需要等待,因为实例方法都被synchronized修饰,共享线程对象是this,两个方法均需等待。
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 end");
}
public synchronized void doOther(){
System.out.println("doOher begin");
System.out.println("doOther end");
}
}
面试题3(在面试题2的基础上,修改为两个线程两个MyClass对象)
//不需等待,因为MyClass对象是两个,两把锁。
public static void main(String[] args) {
MyClass mc1=new MyClass();
MyClass mc2=new MyClass();
Thread t1=new MyThread(mc1);
Thread t2=new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//这个睡眠的作用是:为了保证t1线程先执行。
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
面试题4(在面试题3的基础上,将方法体改为被synchronized修饰的静态方法)
//需要等待,synchronized出现在静态方法上是找类锁。同一个类只有一把锁。
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 end");
}
public synchronized static void doOther(){
System.out.println("doOher begin");
System.out.println("doOther end");
}
}
5.死锁的写法
package com.jmpower.javase.threadsafe;
public class DeadLock {
public static void main(String[] args) {
Object o1=new Object();
Object o2=new Object();
//t1和t2两个线程共享o1,o2
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) {
throw new RuntimeException(e);
}
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) {
throw new RuntimeException(e);
}
synchronized (o1){
}
}
}
}
6.总结:在以后开发中怎么解决线程安全问题?
第一种*方案:尽量使用局部变量代替"实例变量和静态变量"
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
一类是:用户线程 一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收机制(守护线程)
守护线程的特点:一般守护线程是一个死循环,所有的用户线程只要结束。守护线程自动结束。主线程main方法是一个用户线程。 守护线程用在什么地方:
如,每天00:00的时候系统自动备份。
这个需要使用到定时器,并且我们可以将定时器设置会守护线程。
一直在那里看着,每到00:00的时候就备份一次。所有的用户线程
如果结束了,守护线程自动退出,没有必要进行数据备份了。
package com.jmpower.javase.thread;
public class ThreadTest12 {
public static void main(String[] args) {
Thread t=new BackupDateThread();
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 BackupDateThread 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();
}
}
}
}
8.定时器
定时器的作用:
间隔特定的时间,执行特定的程序。
定时器的应用场景:每周要进行银行账户的总账操作。
每天要进行数据的备份操作。
可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来执行任务。这种方式是最最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,这个直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
package com.jmpower.javase.timer;
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();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime=sdf.parse("2022-7-13 18:57:00");
//指定定时任务
//timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
timer.schedule(new LogTimerTask(),firstTime, 1000*10);
}
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{
@Override
public void run() {
//编写你需要执行的任务就行了。
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date=new Date();
String nowTime=sdf.format(date);
System.out.println(nowTime+"完成一次备份");
}
}
9.实现线程的第三种方式:
FutureTask方式,实现Callable接口。
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
package com.jmpower.javase.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest13 {
public static void main(String[] args) throws Exception{
//第一步: 创建一个“未来任务类”对象。
//参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call()方法就相当于run方法。只不过这个有返回值
//线程执行一个任务,执行之后可能会有一个执行结果
//模拟执行
System.out.println("call method begin");
Thread.sleep(1000*10);
System.out.println("call method end");
int a=100;
int b=200;
return a+b;//自动装箱(300结果变成Integer)
}
});
//创建线程对象
Thread t=new Thread(task);
//启动线程
t.start();
//这里是main方法,这里在主线程中。
//在主线程中,怎么获取线程的返回结果?
//get()方法的执行会导致"当前线程阻塞"
Object obj=task.get();
System.out.println("线程执行结果:"+obj);
//main方法这里的程序要想执行必须等待get()方法的结束
//而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果。
//另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}



