一.synchronized线程同步锁
synchronized是用于多线程中保证同一时间内只能有一个线程访问一个代码块的关键字,江湖称同步锁。
synchronized可以用于很多地方,什么对方法上锁,对对象上锁,静态方法上锁,对class类上锁。个人觉得总结后其实就是一种
对对象上锁。
每一个对象有拥有一把锁,如果访问带同步锁的代码块时必须拥有这个锁才能访问。
例:public void c(){ synchronized(this){ System.out.println("tt"); } }
1.给对象上锁
我们对this这个对象上锁,只有拥有this这个对象的锁,我们才能访问这个代码块。每个对象只有一把锁,如果我们有一个线程拥有这个锁那么别的线程就无法访问带synchronized关键字的代码块了,而不带synchronized的代码块随意访问。
例:synchronized public void a(){ } synchronized public void b(){ }
2.给非静态方法上锁
如果有一个线程拥有这个方法的锁,那么别的线程无法访问这个对象所有带锁代码块。对非静态方法上锁我们就可以看成对“this”对象上锁,锁住这个代码块。其实就是和上面一样只不过是换了个写法。
例:public void d(){ synchronized (Test.class) { System.out.println("ttt"); } }
3.给class类上锁
其实Test.class也是一个对象,每个类拥有这样的对象,他对应的方法是静态方法,变量也是静态变量。所以呢这种对class上锁也可以看成对对象上锁,当有一个线程获得Test.Class对象锁后,别的线程都不能访问这个类带有synchronized 静态 代码块代码块。所以说呢,对静态方法加synchronized关键字就是和这种情况一样了,就不总结了。
二.object.wait()和object.notify()
wait和notify是Object类中有的方法,当一个线程中一个对象调用这个方法的时候这个线程就会进入等待状态,等待别的线程用相同对象调用notify唤醒。
例:
static boolean condition = true;
public static void main(String args[]){
Object object = new Object();
Thread threada = new Thread(new Runnable() {//用于调用wait
public void run() {
synchronized(object){//获取调用wait对象上锁
while(condition){//判断是否需要wait
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("wait end");//输出wait结束
}
});
Thread threadb = new Thread(new Runnable(){
@Override
public void run() {
synchronized (object) {
condition = false;//threada满足唤醒条件
object.notify();//唤醒
}
System.out.println("notify end");//唤醒结束
}
});
示例中是Threada调用wait,Threadb调用notify唤醒Threada。
当调用wait的时候,代码中必须获得调用wait的对象锁,如果我们没有去获取这个锁,在编译的时候是不会报错的,但是当运行的时候就会报IllegalMonitorStateException错误。当我们获取这个锁,调用wait之后,这个object对象锁会被睡眠的线程释放,以便别的线程获取这个object的锁调用notify。注意!!别的锁是不会释放,只会释放调用objct的那个对象的锁。
在判断是否调用wait时,判断用的是while而不是if,这个是官方推荐我们这么去做的,不推荐用if。因为我们有一个方法object.notifyAll() 唤醒所有等待的线程,这个方法在我们一个对象同时在多个线程调用wait导致多个线程等待,当我们调用notify()时它会唤醒哪个线程是不可控的,所以我们大多情况下都会调用notifyAll把所有的线程都唤醒,这就导致一个问题,那就是有些线程被唤醒的条件可能不一样,如果我们用if只判断一次的话那么全部都会被唤醒,用while判断就会在唤醒后再去判断条件是否满足,如果不满足,则再次进入等待,等下一次notify。
三.static Thread.sleep(Int time)
Thread.sleep是一个将当前线程休眠的方法,会在设置的时间后自动唤醒。这个线程如果拥有某个对象的锁,当睡眠后他是不会释放锁的。
示例:
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
我们需要catch一个有可能抛出的InterruptedException,这个异常有可能在wait的时候也被抛出。这个异常会在线程调用Thread.sleep后还在休眠状态,被别的线程调用interrupt()(等下会介绍)方法。
四.Thread.join()
我们在ThreadA线程运行ThreadB.join(),这个时候ThreadA就休眠直到ThreadB运行完毕。join()就相当于是将ThreadA未执行的代码放到ThreadB后面执行。
例:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("xxxx");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
try {
thread.join();//将主线程中剩下内容放到thread线程执行完后
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("mainThread");//主线程中输出一句话
示例中的代码运行完thread.join()后,就是将主线程最后一个输出语句放到thread执行完后才执行,因为thread正常情况下永远执行不完,所以主线程永远不会执行。有一点需要注意的就是threa.join()必须放在thrad.start()后面才会起作用,放在前面是不起作用的。
五.Thread.yeid()
操作系统在运行的时候cpu按照算法执行各个线程。当线程调用yeid的那一刻cpu就不执行当前线程了,也就是将线程从执行状态切换到就绪状态,但是下一刻执行哪一个线程这个是要根据线程调度算法来确定的,我们并不能确定具体执行哪个线程,也就是说有可能下一秒当前线程又被切换到执行状态。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int index = 0;index != 20 ; index++){
System.out.println("Thread: " +index);
if(index == 10){
System.out.println("让出cpu");
Thread.yield();
}
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
for(int index = 0; index != 20 ;index++){
System.out.println("ThreadB: " +index);
}
}
});
thread.start();
threadB.start();
thread在index 为10的时候让出了cpu的控制权。
六.Thread.interrupt()
Thread.interrupt()是一个非静态的方法,他的作用是“停止”当前线程的运行,为什么加上一个引号,它只在特殊情况下有用,像线程调用了wait,sleep,join等可中断的阻塞方法 后,调用interrupt就会抛出InterruptedException异常。其实它称为协作停止。调用了这个方法后线程除了特殊情况外别的时候并不会停止,它发送一个停止请求,并且由线程记录下来(实际上就是有一个为bool的变量,当为true时就表示有停止线程运行请求)。所以说线程真正的停止需要我们自己去检查是否有停止线程的请求,当有线程停止请求时停用当前线程。和它配套使用的有 :
例:1.public static boolean interrupted()
这个方法是检查当前线程是否有停止请求,需要注意的是这个方法调用过后会重置请求标志位。如果我们第一次调用这个方法得到结果是true,那么这个时候它会重置标志位,第二次调用这个方法得到的结果是false。2.public boolean isInterrupted()
这个方法是判断调用该方法线程是否需要停止,它和静态的interrupted()有一点很不同的是它不会去重置标志位。也就是说我们用这个方法查询第一次是true的话,以后那次查询都还是true,并不会改变。
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int index = 0;; index++){
if((index % 100000000) == 0){
System.out.println("让出cpu");
}
}
}
});
thread.start();
thread.interrupt();
像上面这个例子,thread调用interrupt停用当前线程,但是线程并不会停止。而是继续输出
如果我们这样写
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int index = 0;; index++){
if((index % 100000000) == 0){
System.out.println("让出cpu");
}
if(Thread.interrupted()){
return;
}
}
}
});
thread.start();
thread.interrupt();
就可以实现了,但是这样非常耗时,每次都会去检查是否需要停止。检查的具体算法就要看我们具体的需求了。