Java给多线程提供了内置的支持,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
新建状态:当一个线程对象被创建后,这个线程就进入了新建状态。
就绪状态:当线程调用了start()方法后,就进入了就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:当就绪状态的线程获取CPU资源,就可以执行run()函数,进入运行状态。进入运行状态的线程下一步可以进入就绪态、阻塞态或者死亡态。
阻塞状态:线程执行了sleep(睡眠),suspend(挂起)等方法后会进入阻塞状态。
线程创建
创建一个线程有三种方法:
通过实现Runnable接口(最简单);
通过继承Thread类本身;
通过Callable和Future创建线程。
线程优先级
实际上,Java程序中的每个线程并不是平均分配CPU时间的,为了使得线程资源分配更加合理,Java采用的是抢占式调度方式,优先级越高的线程,优先使用CPU资源!
- MIN_PRIORITY = 1 最低优先级
- MAX_PRIORITY = 5 最高优先级
- NOM_PRIORITY = 10 常规优先级
线程的礼让和加入
// 创建新线程
Thread t1 = new Thread(new Runnable(){
@Override
public void run(){
//...
}
});
// t1开始运行
t1.start();
// 线程睡眠10s. 此方法显示声明了会抛出一个InterruptedException异常
Thread.sleep(10000);
//终止线程,不建议使用的方法,因为会强制停止线程而不管线程所占用的资源
t1.stop();
//挂起,不建议使用,理由同上
t1.suspend();
/*给指定线程添加一个中断标记,以告知线程需要立即停止运行或是进行其他操作,由线程来响应此中断并进行相应的处理
不同于stop()函数那样简单粗暴可能会导致资源不完全释放,这个方法线程会自行处理后续!
*/
t1.interrupt();
// 复位中断标记,返回值是当前是否有中断标记
Thread.interrupted();
/*线程让位!让给同优先级的线程。但是该方法只是本轮CPU调度让给其它线程,下一轮时继续执行该线程而不让位。
*/
Thread.yeild();
/*线程的加入!该方法将CPU让给t1后,一直等到t1执行结束,才恢复让位,回来继续执行原来的线程。注意dan当前线程只是等待t1执行结束,并不是合并两个线程。
*/
t1.join();
synchronized同步 锁的底层实现
线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的工作内存(本地内存),工作内存中存储了该线程以读/写共享变量的副本。它类似于我们在计算机组成原理
中学习的多处理器高速缓存机制:
当两个线程去同时访问一个共享变量时,它们各自在操作完毕之后,可能会得到不同的结果,然后各自再将共享变量写回内存,这样就会得到我们预期之外的结果,发生一致性错误。
Synchronized的作用就是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保同一个时刻最多只有一个线程执行同步代码,从而保证多线程环境下并发安全的效果。
被syhchronized
修饰的代码就会以原子的方式执行,即作为一个整体,不会执行到一半然后停下来去执行其他的线程。当多个线程执行这块代码时,它们是互斥的,不会相互干扰,不会同时执行。
Synchronized工作机制是在多线程环境中使用一把锁,在第一个线程去执行的时候去获取这把锁才能执行,一旦获取就独占这把锁直到执行完毕或者在一定条件下才会去释放这把锁,在这把锁释放之前其他的线程只能阻塞等待。
synchronized修饰的对象有以下几种
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
Java的锁分为类锁和对象锁两种。
类锁:指synchronized修饰的静态的方法或指定锁为class对象。
对象锁:包括方法锁和同步代码锁。方法锁是synchronized修饰的非静态的方法。同步代码锁是指定锁为实例对象。
对于类锁,只要是这个类产生的对象,它们调用这个静态方法或者代码块就会产生互斥,即是同步的;对于对象锁,当其它线程想要调用该对象的某个上锁的非静态方法时,就会产生互斥,是同步的,不同的对象之间的锁互不影响,是异步的。
synchronized是一种悲观锁,随时都认为有其他线程在对数据进行修改
wait()和notify()方法只能在同步代码块里面使用,调用wait方法后,线程释放当前所占用的锁,进入该锁的阻塞队列,而当其余占用该锁的线程调用notify方法后,才会从队列里随机挑选一个唤醒,然后等待调用notify方法的线程执行结束之后,会紧接着执行唤醒的线程!
notifyAll()方法会把阻塞队列里所有线程都唤醒执行,但执行顺序仍然随机。
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
Thread t1 = new Thread(() -> {
synchronized (o1){
try {
System.out.println("开始等待");
o1.wait(); //进入等待状态并释放锁
System.out.println("等待结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (o1){
System.out.println("开始唤醒!");
o1.notify(); //唤醒处于等待状态的线程
for (int i = 0; i < 50; i++) {
System.out.println(i);
}
//唤醒后依然需要等待这里的锁释放之前等待的线程才能继续
}
});
t1.start();
Thread.sleep(1000);
t2.start();
}
上锁的形式有两种,一个是对方法上锁,一个是对代码块上锁构造为同步代码块。当一个方法被定义为同步方法后,该方法的flags标志里就多了一个ACC_SYNCHRONIZED
标志,告诉JVM这是一个同步方法,进入该方法之前需要获得相应的锁;同步代码块在class文件里的实现是通过monitorenter
和monitorexit
来实现的,用来获得锁和释放锁。
守护线程
守护线程不同于守护进程,守护线程在当其他所有非守护线程结束之后,会自动结束。
在线程开始执行之前必须先将其设置为守护线程才可以执行,否则无法在线程执行过程中将其转换为守护进程。守护线程中创建的新线程也是守护线程。
public static void main(String[] args) throws InterruptedException{
Thread t = new Thread(() -> {
while (true){
try {
System.out.println("程序正常运行中...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setDaemon(true); //设置为守护线程(必须在开始之前,中途是不允许转换的)
t.start();
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
}
}
0 条评论