线程知识点

线程是操作系统调度的最小单元,也叫轻量级进程。同一进程可以创建多个线程,而他们都拥有各自的计算器、堆栈和局部变量等属性,并且能够访问共享的内存变量。

Java中提供了关键字volatile和synchronized关键字来保证线程之间操作的有序性。volatile包含了禁止指令重排序的语义,并保证不同线程对同一变量操作时的可见性。
synchronized关键字对同一时刻同一变量只允许一个线程对其进行lock操作。
volatile保证可见性、有序性,不保证原子性。

wait() 和notify()
这两个方法不是Thread类特有的,而是所有类的父类Object中的。
当一个对象调用了wait方法后,如:objectA.wait(),当前线程就会在这个对象上等待,会释放该对象的锁,直到其他线程调用了objectA.notify()方法为止。
wait和notify方法都必须获得对象的监视器(锁),在同步代码得到执行后也会释放对象的锁,所以必须被包含在对象的synchronzied语句中。

线程状态图:
在这里插入图片描述
在这里插入图片描述

阻塞的情况分三种:
(一).等待阻塞:运行状态中的线程执行wait()方法,JVM会把该线程放入等待队列(waitting queue)中,使本线程进入到等待阻塞状态;
(二).同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
(三).其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

  1. Java中创建的每个对象都有一个关联的监视器Monitor(也就是互斥锁)。在任何给定时间,只有一个线程可以拥有该监视器。
  2. 在Java中使用此监视器来实现同步。当任何线程进入同步方法/块时,它要获取指定对象的锁。当任何线程获得锁时,也就是说它已获取了该监视器。所有其他需要执行相同同步代码(锁定监视器)的线程将被挂起,直到最初获取锁的线程释放它为止。
  3. wait方法告诉当前线程(在同步方法或块内执行代码的线程)放弃监视器并进入等待状态(waiting state)。
  4. notify方法唤醒正在此对象监视器上等待的单个线程。
  5. notifyAll方法唤醒在同一对象上调用wait()的所有线程。

在Java程序中怎么保证多线程的运行安全?
方法一:使用安全类,比如java.util.concurrent下的类,使用原子类Atomiclnteger
方法二:使用自动锁synchronized
方法三:使用手动锁Lock

线程类的构造方法、静态块是被哪个线程调用的
线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。
例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:
(1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的
(2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的

为什么代码会重排序?
在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:
1、在单线程环境下不能改变程序运行的结果;
2、存在数据依赖关系的不允许重排序
需要注意的是:重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语义。


public class Singleton { 
private volatile static Singleton uniquelnstance; 
private Singleton() { 
}
public static Singleton getUniqueInstance() { 
	if (uniquelnstance == null) { 
		synchronized (Singleton.class) { 
			if (uniquelnstance == null) { 
				uniquelnstance = new Singleton(); 
			}
		}
	}
	return uniquelnstance; 
	}
}

ReentrantLock与synchronized:
ReentrantLock使用起来比较灵活,但是必须有释放锁的配台动作;
ReentrantLock必须手动获取与释放锁,而synchronized不需要手动释放和开启锁;
ReentrantLock只适用于代码块锁,而synchronized可以修饰类、方法、变量等;
二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word;
异常时,synchronized会自动释放锁,ReentrantLock需要在finally{}中释放锁。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorDemo {

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;
    public static void main(String[] args) {

        //使用阿里巴巴推荐的创建线程池的方式
        //通过ThreadPoolExecutor构造函数自定义参数创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 10; i++) {
            //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
            Runnable worker = new MyRunnable("" + i);
            //执行Runnable
            executor.execute(worker);
        }
        //终止线程池
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }
}

在这里插入图片描述
在这里插入图片描述

实例数据:存放类的属性数据信息,包括父类的属性信息;
对齐填充:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐;
对象头:Java对象头一般占有2个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit,在64位虚拟机中,1个机器码是8个字节,也就是64bit),但是如果对象是数组类型,则需要3个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值