多线程会共同使用一组计算机上的CPU,而线程数大于给程序分配的CPU数量时,为了各个线程都有执行的机会,就需要轮换使用CPU,不同的线程切换使用CPU发生的切换数据就是上下文切换,下面我们就来说一说关于合格程序员要敲多少行代码?我们一起去了解并探讨一下这个问题吧!
合格程序员要敲多少行代码
文章目录- 一、多线程中的上下文切换
- 二、死锁问题
- 三、如何避免死锁
- 四、活锁
- 五、饥饿
- 六、如何停止一个正在运行的线程
- 七、用户线程和守护线程的区别
- 八、线程优先级的理解
- 九、volatile
- 十、Synchronized
- 十一、volatile和synchronized
- 总结
一、多线程中的上下文切换
多线程会共同使用一组计算机上的CPU,而线程数大于给程序分配的CPU数量时,为了各个线程都有执行的机会,就需要轮换使用CPU,不同的线程切换使用CPU发生的切换数据就是上下文切换。
二、死锁问题死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都无法执行下去。产生死锁的必要条件:
- 互斥条件:线程在某一时间独占资源。
- 请求和保持条件:线程T1已经取得共享资源X,在等待共享资源Y,不释放共享资源X。
- 不可剥夺条件:其他线程不能强行抢占线程T1占有的资源。
- 循环等待条件:线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源。
- 破坏互斥条件:互斥是无法被破坏的,因为这是互斥锁的基本约束。
- 破坏请求和保持条件:一次性申请所有的资源,这样就不存在等待了。
- 破坏不可剥夺条件:当某进程获得了部分资源,但得不到其他资源,则释放已占有的资源,但是只适用于内存和处理器资源。
- 破坏循环等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次进行。
- 设置加锁的顺序:多个线程按照相同的顺序来请求锁,就不会出现循环的加锁依赖性。
任务或者执行者都没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,而处于死锁的实体表现为等待,活锁有可能自行解开,死锁则不能。
五、饥饿一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。导致饥饿的原因:
- 高优先级线程吞噬所有低优先级线程的CPU时间。
- 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能再它之前持续地对该同步块进行访问。
- 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法),因为其他线程总是被持续地唤醒。
- 使用退出标志,是线程正常退出,也就是当run方法完成后线程终止。
- 使用stop方法强行终止,但是不推荐这个方法,这个方法已经过期了。
- 使用interrupt方法中断线程,这种方法并不是强制中断,而是告诉正在运行的线程可以停止了,是否要中断,取决于正在运行的线程,所以它能够保证线程运行结果的安全性。
public class InterruptThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThreadDemo myThreadDemo = new MyThreadDemo();
System.out.println("===============");
Thread thread = new Thread(myThreadDemo);
thread.start();
Thread.sleep(3000);
System.out.println("Interrupt thread.." thread.getName());
thread.interrupt();
Thread.sleep(3000);
System.out.println("stopping.....");
}
}
守护线程都是为JVM中所有非守护线程的运行提供便利服务,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作,只有当最后一个非守护线程结束时,守护线程才会随着JVM一同结束工作。那么如何创建守护线程呢?任何线程都可以设置为守护线程和用户线程,通过下列方法,true则是把该线程设置为守护线程,false则是用户线程,该方法必须在start()方法之前调用,否则运行时会抛出异常。守护线程相当于后台管理者,比如进行内存回收,垃圾清理工作。
MyThreadDemo myThreadDemo1 = new MyThreadDemo();
Thread thread1 = new Thread(myThreadDemo1);
thread1.setDaemon(true);
thread1.start();
每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的,可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行,线程优先级是一个int变量从1-10,1代表最低优先级,10代表最高优先级,默认优先级是5;下面的方法是设置优先级的方法
Thread thread1 = new Thread(myThreadDemo1);
thread1.setPriority(7);
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
- 通过增加内存屏障防止多个指令之间的重排序(禁止指令重排序)。
Synchronized关键字解决的是多个线程之间访问资源的同步性,Synchronized关键字可以保证它修饰的方法或者代码块在任意时刻只能有一个线程执行。
- 修饰实例方法:作用相当于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
- 修饰静态方法:也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员。所以如果线程A调用一个实例对象的非静态Synchronized方法,而线程B需要调用这个实例对象所属类的静态Synchronized方法,是不会发生互斥现象的。静态的Synchronized方法占用的锁是当前类的锁,非静态Synchronized方法占用的锁是当前实例对象的锁。
- 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。
//类锁 修饰加锁的类
void demo4(){
//类锁
synchronized (SynchronizedDemo.class){
System.out.println(Thread.currentThread().getName() "demo4执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//类锁 静态方法
synchronized static void demo2(){
System.out.println(Thread.currentThread().getName() "demo2执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//锁的是同一个对象 修饰代码块,指定对象
void demo3(){
//this 代表实例 同一个实例会被锁住
synchronized (this){
System.out.println(Thread.currentThread().getName() "demo3执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//普通方法
synchronized void demo1(){
System.out.println(Thread.currentThread().getName() "demo1执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只是当前线程可以访问该变量,其他线程被阻塞。
- volatile仅能在变量级别;synchronized则可以使用在变量、方法、类级别。
- volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编辑器优化;synchronized标记的变量可以被编译器优化。
总结
等等,我还有句话想说,你累吗?我觉得今天有点累,但是累证明我还活着,说明我再上坡!
,