什么是线程
线程是操作系统能够进行运算调度的最小单位,一个程序运行就是一个进程,一个进程包括至少一个线程
线程和进程有什么区别
线程是进程的子集;进程占据较多的系统资源,线程仅占用一些必不可少的系统资源;进程之间的内存空间是独立的,线程之间是共享的;进程的上下文切换代价较大,线程之间的切换代价很小。
实现Runnable接口和继承Thread接口的区别
1、首先应当明白无论是何种方式,最后都是通过Thread.start方法开启的线程(注意,Runnable接口的run方法并不是开启了新的线程)
2、实现Runnable接口构建线程时,需要以此接口的实现类做为真正线程类的构造方法中的构造参数
Thread thread = new Thread(runnable,"Runnable接口实现线程");
继承Thead类时,由于没有Runnable接口的实现类,自然使用无参构造函数
创建线程类
Thread thread = new Thread();//使用无参构造方法创建线程
因此,两种方式构造线程时的区别在于线程之间的资源是否能够共享。资源共享如果不太清楚可以参考这个博主的文章
https://www.cnblogs.com/fxust/p/8998696.html
3、最明显的区别自然就是实现接口可以有更好的扩展性了。
synchronized关键字
大家一直习惯称它为同步,我觉得将之称为按照顺序执行更合适,线程是具有并发性的,因此两个线程一起运行的时候可能会出现数据错误的情况,比如下面,我每次执行线程时同时执行一个sleep方法,模拟线程执行较慢的情况(注意:在加锁的情况下,执行sleep方法不会释放锁哦);
public class Test implements Runnable{
private int i=0;
@Override
public void run() {
while (i<5){
System.out.println(Thread.currentThread().getName()+"卖出的票号为:"+i);
try {
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
i=i+1;
}
}
public static void main(String[] args) {
Runnable runnable = new Test();
Thread thread1 = new Thread(runnable,"线程一");//使用无参构造方法创建线程
Thread thread2 = new Thread(runnable,"******线程二");
thread1.start();
thread2.start();
}
}
运行结果如下:
可以看到,我卖出了两张票号为0的票,自然就出了错误,为了避免这种错误,我们可以引入synchronized关键字
如下
public synchronized void run() {
System.out.println(Thread.currentThread().getName()+"想要售卖票------");
while (i<5){
System.out.println(Thread.currentThread().getName()+"卖出的票号为:"+i);
try {
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
i=i+1;
}
}
如下我们可以看到,不会卖出错乱重复的票了
synchronizedTest可以在普通方法,静态方法,代码块中使用。作用在普通方法中时,锁的是当前实例对象;作用在静态方法时,锁的是这个类(静态方法属于类,仅此一个嘛);
作用在代码块时,代码块中是什么,自然锁的就是什么,如下:
锁的是当前实例对象
public void run() {
System.out.println(Thread.currentThread().getName()+"想要售卖票------");
synchronized (this){
while (i<5){
System.out.println(Thread.currentThread().getName()+"卖出的票号为:"+i);
try {
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
i=i+1;
}
}
}
也可以锁当前这个类
public void run() {
System.out.println(Thread.currentThread().getName()+"想要售卖票------");
synchronized (Test.class){
while (i<5){
System.out.println(Thread.currentThread().getName()+"卖出的票号为:"+i);
try {
Thread.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
i=i+1;
}
}
}
锁的概念
这里的锁的概念我刚开始一直不太明白,说下我自己的理解。首先个人认为锁有两个关键点,锁了谁,锁了哪里。
举个例子,交警叔叔在立水桥路口查未佩戴头盔骑电动车的人,不符合要求的不允许通行,这其实就是一个锁。锁了谁呢,骑电动车未佩戴头盔的人;锁了哪里呢,立水桥路口。
那么如果我没骑电动车且没佩戴头盔,那交警叔叔也不会查处我的,因为我不是锁的对象(锁普通方法时,仅锁当前实例对象,再生成一个实例对象时,不会上锁);
如果我骑了电动车还未佩戴头盔,那么我可以不走立水桥路口啊,因为别的地区没上锁(锁代码块时,代码块之外的代码仍然可以被线程并发访问)
如果我骑了电动车还带着头盔自然可以从立水桥通过,因为我是获得了资源的。
如果我骑了电动车未佩戴头盔还从立水桥过,那么你未获得权限,成功被拦截,不允许通行(不允许访问上锁的资源)
start、run、join、yield方法的区别
run方法只是Runnable接口的一个普通方法,运行它并未开启真正的线程
start方法是Thread类的方法,运行它才是开启了线程,开启后会调用run方法
join方法是在当前线程调用另一个线程的join方法,那么会阻塞当前线程,直到另一个线程执行完毕,才会继续执行当前线程
如下,首先main方法也是一个线程,在不加thread1.join()的情况下,由于线程的并发性,那么打印最后打印出来的i的值很可能为0,加上thread1.join()后,则先执行thread1这个线程,再执行main方法的线程,最后打印的数据为5
public class Test implements Runnable{
private int i=0;
@Override
public void run() {
while (i<5){
i=i+1;
}
}
public static void main(String[] args) throws Exception{
Runnable runnable = new Test();
Thread thread1 = new Thread(runnable,"线程一");//使用无参构造方法创建线程
thread1.start();
thread1.join();
System.out.println(((Test) runnable).i);
}
}
yield是Thread类的一个静态方法,会让当前线程交出cpu资源,让线程重新竞选执行机会,如下,如果去掉了Thread.yield(),如果计算机运行速度较快的话,则可能一直是线程一运行直至运行完毕,加上此方法后,则使线程二在每次i++之后都能够有获取cpu资源的机会
public class Test implements Runnable{
private int i=0;
@Override
public void run() {
while (i<10){
System.out.println(Thread.currentThread().getName()+"卖出的票号为:"+i);
i++;
Thread.yield();
}
}
public static void main(String[] args) {
Runnable runnable = new Test();
Thread thread1 = new Thread(runnable,"线程一");//使用无参构造方法创建线程
Thread thread2 = new Thread(runnable,"******线程二");
thread1.start();
thread2.start();
}
}
守护线程
守护线程和普通线程的创建没有太大区别,在生成一个线程实例的时候,调用setDaemon(true)即可设置为守护线程,注意需要在start方法之前。
当一个进程内的非守护线程执行完成后,无论有没有正在执行的守护线程,JVM都会停止。