用户线程(User Thread)和守护线程(Daemon Thread)
Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
值得一提的是,守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。下面的方法就是用来设置守护线程的。 Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
Thread daemonTread = new Thread();
// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
// 验证当前线程是否为守护线程,返回 true 则为守护线程
daemonThread.isDaemon();
jps展示当前jvm内所有的进程信息(进程号,进程名字)
jconsole +进程号 (查看进程的状态)
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程
互斥和同步的区别
我的理解:互斥就是线程之间对一个资源的竞争,执行的结果是无序的,同步是要求程序员把这个资源进行有序化的操作(即这个对象哪个时候哪个线程来执行),就是按程序员的逻辑进行有序的该资源进行访问。
互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
同步其实已经实现了互斥,所以同步是一种更为复杂的互斥。
互斥是一种特殊的同步。
互斥是通过竞争对资源的独占使用,彼此之间不需要知道对方的存在,执行顺序是一个乱序。
同步是协调多个相互关联线程合作完成任务,彼此之间知道对方存在,执行顺序往往是有序的。
线程什么时候终止
当线程的run()方法结束线程也就自然死亡了,也可以调用.stop()强制让线程结束
调用start()方法时至少存在两个线程,一个是调用你自己的线程,例如:main线程,还有一个是自己创建的线程来执行run()方法
wait()和notify()函数的深入理解,涉及到的线程之间的传数据
public class Thread_Wait_Notify_ implements Runnable{
private String name;
private Object pre;
private Object self;
public Thread_Wait_Notify_(String name, Object pre, Object self) {
this.name = name;
this.pre = pre;
this.self = self;
}
@Override
public void run() {
int count=10;
while (count>0){
synchronized (pre){
synchronized (self){
self.notify();
System.out.print(name);
count--;
}
try {
pre.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
public static void main(String[] strings) throws InterruptedException {
Object a=new Object();
Object b=new Object();
Object c=new Object();
new Thread(){
@Override
public void run(){
}
};//不启动
Thread_Wait_Notify_ pa=new Thread_Wait_Notify_("A",c,a);
Thread_Wait_Notify_ pb=new Thread_Wait_Notify_("B",a,b);
Thread_Wait_Notify_ pc=new Thread_Wait_Notify_("C",b,c);
new Thread(pa,"A").start();
//Thread.sleep(100);//确保按ABC这个顺序执行
new Thread(pb,"B").start();
//Thread.sleep(100);
new Thread(pc,"C").start();
// Thread.sleep(100);
}
输出:
ABCABCABCABCABCABCABCABCABCABC
object.wait():这个对象在那个线程里面调用了,那个线程就进入停止状态,不再执行,这就是为什么不连续执行输出10个A的原因,代码中线程A的pre.wait()执行了,其实就是c对象.wait()(虽然c对象是在主线程传过来的,不属于A线程创建)那么线程A就进入停止状态,不会往下执行,然后B线程又被主线程调用了,进入B线程执行,打印了一个B,又执行了pre.wait(),B线程又进入停止状态,又调用了C线程进入执行,打印了一个C,执行self.notify(),其实也就是C对象.notify(),这句话很重要,这使得之前因为这个对象进入停止状态的线程重新向下执行。wait()方法会使得该对象的使用权释放出来给其他的线程使用,同时会使得执行这条语句的线程停止。。。
把run方法的代码改成如下
public void run() {
int count=10;
while (count>0){
synchronized (pre){
synchronized (self){
System.out.print(name);
count--;
System.out.print(self);
System.out.println("==");
self.notify();
}
try {
pre.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
看输出:
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
Ajava.lang.Object@7e437185==
Bjava.lang.Object@7042b8f1==
Cjava.lang.Object@3cd1a2f1==
上代码可以看出,一个对象在一个线程执行了.wait()方法后不用在另外一个线程先执行notify()才有这个对象的使用权,上一个线程执行了wait()就表明其他的线程可以使用这个对象。
最后补充一下:
这个程序不会停止:
还有B线程和C线程,因为最后是B线程和C线程内执行了pre.wait();
打印最后一轮ABC是这样子的:A线程拯救了B线程,自己锁住了,B拯救了C,自己锁住了,C拯救了A,自己锁住了,所以最后剩下B和C线程没有结束,两个都在等待别人把它notify();但是没有线程调用这个语句来唤醒它也就是永远的停在了那里
代码改进:怎么样子让程序正常的停止:
修改代码:
思路:在A线程被C线程唤醒之后,退出while循环后,执行self.notify(),(因为之前B线程执行了pre.wait(),因为a对象被停止了)唤醒之后,B线程就可以执行了,也是退出while循环后,执行b.notify(),(之前C线程因为b对象.wait()进入停止状态),C线程可以继续向下执行,最后B和C线程都正常结束,整个程序正常结束。
@Override
public void run() {
int count=10;
while (count>0){
synchronized (pre){
synchronized (self){
System.out.print(name);
count--;
System.out.print(self);
System.out.println("==");
self.notify();
}
try {
pre.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
synchronized (self){
self.notify();
}
}
明确一点哪个线程因为哪一个对象进入了停止状态
notify()和wait()为什么一定要要放在同步synchronized{}关键字里面
操作系统层面:
每一个对象都有一个与之对应的监听器:这个监听器包括整个对象的锁,同步队列和等待队列
因为wait()方法进入暂停的线程进入等待队列,唤醒之后和其他线程竞争失败就进入同步队列;
wait()方法的语义有两个,一是释放当前对象锁,另一个是进入阻塞队列,可以看到,这些操作都是与监视器相关的,当然要指定一个监视器才能完成这个操作了
notify()方法也是一样的,用来唤醒一个线程,你要去唤醒,首先你得知道他在哪儿,所以必须先找到该对象,也就是获取该对象的锁,当获取到该对象的锁之后,才能去该对象的对应的等待队列去唤醒一个线程。值得注意的是,只有当执行唤醒工作的线程离开同步块,即释放锁之后,被唤醒线程才能去竞争锁。
比如说A线程执行了self.notify(),那么就会去找这个a对象的的锁,获取了这个锁之后找到a对象的等待队列,唤醒之前执行了pre.wait()的B线程,()如果有多个线程对a对象执行了wait()那么notifyAll()可以唤醒所有的线程
值得注意的是:虽然执行了.notify()操作,但是被唤醒的B线程并不会立即去竞争a对象,因为只有等synchronized{}函数块执行完了才会去执行的
synchronized实现同步的关键:
synchroned关键字可以对普通方法,静态方法和在括号内加一个对象这3种用法
上面的wait()和notify()函数都是用在第三种用法上的代码块
1.作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
2.作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
3.作用于代码块,这需要指定加锁的对象,对所给的指定对象加锁,进入同步代码前要获得指定对象的锁。
synchronized关键字作用在run()函数上面
public class Mutl_Thread implements Runnable {
int count=0;
//int self=0;//+"self:"+self++
@Override
public synchronized void run() {
for (int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"count:"+count++);
System.out.println(Thread.currentThread().getName()+"=============");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] arg) throws InterruptedException {
Mutl_Thread mutl_thread=new Mutl_Thread();//这个是一个对象实例,
// synchronized在run()方法上加了,run方法操作了count,实质上就是给这个实例加上了锁,其他的Thread()再访问这个实例就没办法了
Mutl_Thread mutl_thread1=new Mutl_Thread();
//下面两个Thread()线程都是共用用上面这个实例来进行测试的,run()不加synchronized就保证不了同步
//如果分别用到了两个不同的对象实例,那就不是共享对象,不涉及同步
Thread thread01=new Thread(mutl_thread,"THread01");
Thread thread02=new Thread(mutl_thread,"Thread02");
thread01.start();
thread02.start();
// Thread.sleep(1000);
}
}
输出
THread01count:0
THread01=============
THread01count:1
THread01=============
THread01count:2
THread01=============
Thread02count:3
Thread02=============
Thread02count:4
Thread02=============
Thread02count:5
Thread02=============
可以看出:一个线程获取该对象锁之后不会被其他的线程调用,这个应该不是处以
但是如果没有synchronized方法就无法实现同步,就是进入一个多线程的竞争的状态:每一次输出的结果都不一样
Thread02count:1
Thread02=============
Thread02count:2
Thread02=============
Thread02count:3
Thread02=============
THread01count:0
THread01=============
THread01count:4
THread01=============
THread01count:5
THread01=============
注意啊:因为wait()进入停止状态的线程会释放该线程所有对象的资源的锁,也就是说不仅仅是调用wait()方法的对象会被释放;sleep()函数会让线程进入一个睡眠的状态,但是不会释放对象的锁,也就是说必须等我睡完觉了再继续执行。sleep()函数谁调用谁就睡觉
更多的synchronized的底层原理参考