今天来总结一下多线程中常用操作方法:
1,线程的命名与取得:
方法名 | 类型 | 描述 |
---|---|---|
Public Thread(Runnable target,String name) | 构造 | 通过构造方法在创建线程时设置线程名称 |
Public final String getName() | 普通 | 取得线程名称 |
Public final synchronized void setName(String name) | 普通 | 创建线程后设置名字 |
在说其他线程方法前先来看一张图:
2,线程休眠方法: sleep()–单位为毫秒
让当前线程暂缓执行,等到了预计时间后再恢复执行
线程休眠会立刻交出CPU,但是不会释放锁
状态切换:从运行态到阻塞态再到就绪态
3,线程让步:yield()方法
暂停执行当前线程对象,并执行其他线程
Yield()方法会让当前线程交出CPU,同样不会释放锁,但yield()方法无法控制具体交出CPU的时间,并且yield()方法只能让拥有相同优先级的线程有获取CPU的机会
状态切换:从运行态返回就绪态
4,等待其他线程终止:join() 方法
等待该线程终止,如果在主线程中调用该方法,会让主线程休眠,让调用该方法的线程执行完毕后再恢复执行主线程.
会释放对象锁
其实:Join方法只是对Object提供的wait() 做了一层包装而已
5,线程停止的三种方法
(1)设置标记位停止线程,可以是线程正常退出
class MyThread implements Runnable {
boolean flag=true;
@Override
public void run(){
int i=0;
while(flag){
try {
Thread.sleep(1000);
System.out.println("第"+i+"次执行,线程名为:"+Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setflag(boolean flag){
this.flag=flag;
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"子线程A");
thread1.start();
Thread.sleep(2000);
myThread.setflag(false);
System.out.println("代码结束");
}
}
(2)调用stop方法强制停止线程
该方法不安全已被Deprecated。
为什么不安全?因为stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对 象所运行的线程就会立即停止,
(3)调用Thread类的interrupt() 方法
interrupt() 方法只是将线程状态置为中断状态而已,它不会中断一个正在运行的线程,此方法只是给线程传递一个中断信号,程序可根据此信号来判断是否需要终止。
当线程中使用了wait(),sleep()以及join()方法导致线程阻塞,则interrupt()会在线程中抛出InterruptException,在catch块中捕获该异常,然后退出,并且将线程的中断状态由true置为false。
当线程中没有wait, sleep, join,调用interrupt只是将线程状态置为interrupt=true
6,线程优先级
线程优先级是指优先级越高越有可能先执行,但仅仅是有可能而已。
设置优先级的方法:public final void setPriority(int newPriority)
取得优先级: Public final int getPriority()
于优先级设置的内容可以通过Thread类的几个常量来决定 :
MAX_PRIORITY=10
NORM_PRIORITY=5
MIN_PRIORITY=1
主线程只是一个普通优先级而已,优先级为5
线程具有继承性:只是继承优先级而已
比如:从A线程启动B线程,则B线程和A线程的优先级一样
7,守护线程
守护线程是一种特殊的线程,又称为陪伴线程,
Java中一共有两种线程:用户线程和守护线程。
Thread类提供方法isDaemon()区别两种线程:返回false表示该线程是用户线程,否则为守护线程,垃圾回收线程为典型的守护线程
只要当前JVM进程中存在任何一个用户线程没有结束,守护线程就在工作,只有当最后一个用户线程结束时,守护线程才会随着JVM一同停止工作。
Thread提供setDaemo()将用户线程设置为守护线程
class A implements Runnable {
private int i;
@Override
public void run() {
try {
while (true) {
i++;
System.out.println("线程名称:" + Thread.currentThread().getName() + ",i=" + i + ",是否为守护线程:"+ Thread.currentThread().isDaemon());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("线程名称:" + Thread.currentThread().getName() + "中断线程 了");
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new A(),"子线程A"); // 设置线程A为守护线程,此语句必须在start方法之前执行
thread1.setDaemon(true);
thread1.start();
Thread thread2 = new Thread(new A(),"子线程B");
thread2.start();
Thread.sleep(3000); // 中断非守护线程
thread2.interrupt();
Thread.sleep(10000);
System.out.println("代码结束");
}
}
8, 线程的同步与死锁
Synchronized(内建锁)实现同步处理(加锁操作)
Synchronized处理同步问题的两种方式:
(1)同步代码块:在方法中使用synchronizeds(对象),一般可以锁定当前对象this,
表示同一时刻只有一个线程能够进入同步代码块,但多个线程可同时进入方法。
A. synchronized(this){} —>锁住的是该类的实例对象
B. Synchronized(类名.class){}–全局锁
Object obj=new Object();
C. Synchronized(任意对象,obj){}
(2)同步方法:在方法声明上加synchronized,表示此时只有一个线程更够进入同步方法
synchronized对象锁概念:
Synchronized(this)以及普通的synchronized方法,只能防止多个线程同时执行同一个对象的同步段,synchronized锁的是括号中的对象而非代码
全局锁:锁代码段
(1)使用类的静态同步方法:synchronized与static 一起使用,此时锁的是当前使用的类而非对象
(2)在代码块中锁当前class对象:synchronized(类名称.Class){}
Synchronized 底层实现
同步代码块底层实现:
执行同步代码块后首先要执行moniterenter指令,退出时执行moniterexit指令,使用Synchronized实现同步,关键点就是获取对象的监视器moniter对象,当线程获取到moniter对象后,才可以执行同步代码块,否则就只能等待,同一时刻就只能有一个线程获取到该对象的monitor监视器。
通常一个monitorenter指令会同时包含多个monitorexit指令,因为JVM要确保所获取得锁无论在正常执行路径或异常执行路径都能正确解锁。
同步方法底层实现:
当使用synchronized标记方法时,字节码会出现访问ACC_SYNCHRONIZED.该标记表示在进入该方法时,JVM需要进行monitorenter操作,在退出该方法时,无论是否正常返回,JVM需要进行monitorexit操作。
当JVM执行moniterenter时,如果目标对象monitor 的计数器为0,表示该对象没有被其他线程所持有,此时JVM会将该锁对象的持有线程设置为当前线程,并且将monitor计数器+1。
在目标锁对象的计数器不为0的情况下,如果锁对象的持有线程是当前线程,JVM可将计数器再次+1(可重入锁),否则需要等待,直到持有线程释放该锁。
当执行monitorexit时,JVM需将锁对象计数器-1,当计数器减为0时,代表该锁已经被释放掉,唤醒所有等待的线程去竞争该锁。
最后再提一个概念:对象锁(monitor)
对象锁机制是JDK1.6之前synchronized底层原理,又称JDK1.6重量级锁,线程的阻塞及唤醒均需要操作系统由用户态切换到内核态,开销非常大,因此效率很低。
今天就先写到这里,这块的知识还是比较复杂的,我写的都很浅,后续会继续深入学习这块的知识。