一、线程的概念
一个完整的进程中,一个代码的实现路径就是一个线程。
二、线程的创建方式
线程创建方式有两种:一种是继承Thread类,重写run()。或者是实现Runnable接口,重写run方法。
public class Demo02 extends Thread{
@Override
public void run() {
System.out.println("需要在多线程下执行的代码【Thread】");
}
public static void main(String[] args) {
new Demo02().start();
}
}
class Demo03 implements Runnable{
@Override
public void run() {
System.out.println("需要在多线程下执行的代码【Runnable】");
}
public static void main(String[] args) {
new Thread(new Demo03(),"线程").start();
}
}
当然在这两种的基础上,发展出其他的线程创建方式
例如:实现Callable接口,一般在使用callable时会使用FutureTask来包装callable
/**
*Callable接口中重写call方法,可以获取返回值
*/
class Demo04 {
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 100;
}
});
new Thread(task).start();
try {
System.out.println("接口到的Callable的返回值:"+task.get());
}catch (Exception e){
e.getStackTrace();
}
}
}
例如:使用线程池的方式创建线程
class Demo05{
public static void main(String[] args) {
try {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
for (int i=0;i<10;i++){
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
}
});
}
}catch (Exception e){
e.getStackTrace();
}
}
}
线程的创建方式有很多种,但是都是基于Thread类和Runnable接口在做进一步的实现。
如果在进一步的探讨线程写法上不实现,还有lamda表达式的一种线程创建方式
class Demo06 {
public static void main(String[] args) {
new Thread(()-> System.out.println("线程开始执行")).start();
}
}
这种方式只是写法上的不同,其基本的实现还是通过Runnable进行实现的
三、线程的状态
线程的状态共分为三种状态,只是在运行状态中,根据线程是否拥有CPU执行权,分为就绪状态和运行中状态
new 新建状态 Thread t =new Thread() 线程就会进入新建状态
runnable 运行状态 t.start() 进入运行状态
ready 就绪状态 在等待cpu执行权时是就绪状态
running 运行中状态 获取到cpu执行权为运行中状态
blocked 阻塞状态 线程在等待获取锁时为阻塞状态
teminated 结束状态 线程结束销毁时就是结束状态
class Demo07 {
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println("线程状态:"+Thread.currentThread().getState()));
System.out.println("线程状态:"+thread.getState());
thread.start();
System.out.println("线程状态:"+thread.getState());
}
}
可以使用这种方式是测试线程的状态,并且在实际的多线程场景中,由于实际需求,线程调用sleep()或者yield()等方法线程进入就绪状态,或者是代码加同步锁synchronized线程进入阻塞状态。可以根据实际的需求进行编写。
四、线程常用的方法
Tread.currentThread() 获取当前线程对象
getPriority() 获取当前线程的优先级
setPriority() 设置当前线程的优先级,线程的优先级高,被cpu调度的概率会变大,但不是绝对会被优先调度,也有一定几率会运行优先级较小的线程。
isAlive() 判断线程是否存活
join() 在A线程中调用线程B.join(),就在先执行B线程,执行完之后才会继续执行A线程
sleep() 让线程休眠指定的毫秒数,进入阻塞状态,线程超时会自动苏醒进入就绪状态。sleep()方法是Thread的方法,可以在任意位置进行调用,注意:sleep()是不会释放锁的
wait() 线程等待,进入阻塞状态,wait()方法是Object的方法,必须在同步锁代码块内调用,会释放锁
notify() 唤醒wait()阻塞的当前线程,必须在同步锁代码块内调用
notifyAll() 唤醒所有被wait()阻塞的线程,必须在同步锁代码块内调用
yield() 线程让出cpu的执行权,返回就绪状态从新竞争cpu
五、线程锁
synchronized 同步锁,在sync修饰代码块后,这个代码块只能由一个线程进行访问,sync锁定的是对象
sync(Object) 锁定对象Object
sync method() 修饰方法时,锁定的是this
sync static method() 修饰静态方法时,锁定的是T.class
在多线程场景下,对于部分操作必须一次只能由一个线程进行操作,所以就必须在这一部分代码上加sync进行修饰,例如:
class Demo08 {
static final Object o = new Object();
private static int count = 0;
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
service.submit(new Runnable() {
@Override
public void run() {
m();
}
});
}
service.shutdown();
}
static void m() {
synchronized (o) {
count++;
System.out.println(count);
}
}
}
在以上的示例中count++在多线程中并不是一个线程安全的操作,所以必须要用sync进行修饰。这个需要注意一个点,sync锁定的对象必须是唯一的一个,所以需要在加锁的对象上加锁final修饰。
synchroized的所升级的过程:
偏向锁:只有一个线程的时候,会在锁定对象的markword中标记这个线程的ID,主要是用来确认当前线程的数量
自旋锁:当第二个线程来请求锁定对象时,sync会自动升级为自旋锁,也就是CAS模式。
重量级锁:当在自旋锁中,某一个线程自旋次数超过十次,锁就会自动升级为重量级锁,也就是会进入到内核中加锁。
注意:这里需要注意一点锁升级之后是不能降级的
锁的选择:
锁定代码执行时间长,线程数多,则选用sync
锁定代码执行时间短,线程数少,则选用automic,auto选用的也就是CAS锁的实现
volatlie 修饰的变量在多线程中主要是为了保证多线程之间变量的可见性,同时防止jvm对于变量的指令进行重新排序。
CAS(Compare And Set)的大致实现:
class Demo09 {
private int count = 0;
/**
* 以下示例只是为方便理解写的伪代码,不要复制后去执行
* e:count的实际值
* v:count的期望值
* n:count的新值
*/
static void cas(int e, int v, int n) {
//判断count实际的值是否与期望的值相等,如果相等表示count没有被别的线程修改,则本线程进行修改操作
if (e == v) {
e = n;
} else {
//如果count实际的值是否与期望的值不相等,则重新获取count最新值,再进行cas操作
v = e;
Demo09.cas(e, v, n);
}
}
}
CAS支持CPU源语层面的操作,也就是比如上面的cas()方法,一旦一个线程开始执行这个方法内部代码,
那就不会被打断。所以在执行方法内部代码时不用考虑线程安全的问题,CAS在java.util.current中的实现就是atomic包下