此文转载乐字节
- 多线程编程基础 1.1 进程、线程 1.1.1 进程
狭义:进程是正在运行的程序的实例。
广义:进程是一个具有一定独立功能的程序,关于某个数据集合的一次运行活动。
进程是操作系统动态执行的基本单元,在传统的操作系统中, 进程即是基本的分配单元,也是基本的执行单元。
1.1.2 线程
线程是操作系统能够进行运算调试的最小单位。它被包含在进程中,是进程中的实际动作单位。一个线程指的是进程中的一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程执行不同的任务。
1.1.3 多线程的优点
可以把占据时间较长的任务放到后台去处理 程序的运行速度加快 1.3 线程常用API 1.3.1 currentThread方法
返回代码正在那个线程调用的详细信息
1.3.2 isAlive
是判断当前的线程是否处于活动状态。活动状态就是线程已经启动且运行没有结束。线程处于正在运行或准备开始运行的状态,就认为线程是『存活』的状态。
1.3.3 sleep方法
作用是在指定的毫秒数内让当前『正在执行的线程』暂停执行。
1.3.4 getId方法
作用获取当前线程的唯一标识
1.4 停止线程 停止一个线程意味着在线程处理完成任务之前结束正在执行的操作。
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。用一个boolean类型flag即可
stop方法强制结束线程(stop方法已经被作废,如果强制让线程停止有可能使一些清理性的工作得不到完成。另外一个原因是对锁定的对象进行『解锁』,导致数据得不同同步的处理,出现数据不一致性的问题。)
使用interrupt方法中断线程
调用interrupt方法不会真正的结束线程,在当前线程中打上一个停止的标记。 Thread类提供了interrupted方法测试当前线程是否中断,isInterrupted方法测试线程是否已经中断。 复制代码 图片.png
1.5 暂停线程 暂停线程使用suspend,重启暂停线程使用resume方法
public class Test { public static void main(String[] args) throws InterruptedException { Demo19Thread t = new Demo19Thread(); t.start(); Thread.sleep(1000);
t.suspend(); // 暂停线程
System.out.println("A=" + System.currentTimeMillis() + ", i=" + t.getI());
Thread.sleep(1000);
System.out.println("A=" + System.currentTimeMillis() + ", i=" + t.getI());
t.resume(); // 恢复暂停线程运行
Thread.sleep(1000);
t.suspend();
System.out.println("B=" + System.currentTimeMillis() + ", i=" + t.getI());
Thread.sleep(1000);
System.out.println("B=" + System.currentTimeMillis() + ", i=" + t.getI());
}
}
class Demo19Thread extends Thread{ private long i = 0;
public long getI(){
return i;
}
public void setI(long i){
this.i = i;
}
@Override
public void run() {
while (true){
i++;
}
}
} 复制代码 图片.png
suspend方法和resume方法都已经过时,它的缺点:
1.suspend方法暂停线程时,如果有锁的情况不会释放锁,容易造成其他线程无法持有锁,而无法进入run方法。
2.suspend方法和resume方法不是同步方法,可能造成脏读环境。
public class Test { public static void main(String[] args) throws InterruptedException { Demo22User user = new Demo22User(); Thread t1 = new Thread(){ @Override public void run() { user.updateUsernameAndPassword("b", "bb"); } }; t1.setName("A"); t1.start();
Thread.sleep(10);
Thread t2 = new Thread(){
@Override
public void run() {
user.printUseruserAndPassword();
}
};
t2.start();
}
}
class Demo22User { private String username = "a"; private String password = "aa";
public void updateUsernameAndPassword(String username, String password){
this.username = username;
if ("A".equals(Thread.currentThread().getName())){
System.out.println("停止A线程");
Thread.currentThread().suspend();
}
this.password = password;
}
public void printUseruserAndPassword(){
System.out.println("username=" + username + ", password=" +password);
}
}
1.6 yield方法 yield方法的作用是放弃当前的CPU资源,将资源让给其它的任务去占用CPU执行。但放弃时间不确定,有可能刚刚放弃,马上又获取CPU时间片。
1.7 线程的优先级 在操作系统中,线程可以划分优先级,优先级较高的线程得到更多的CPU资源,也就CPU会优先执行优先级较高的线程对象中的任务。设置线程优先有助于帮助『线程调度器』确定在下一次选择哪个线程优先执行。
设置线程的优先级使用setPriority方法,优级分为1~10个级别,如果设置优先级小于1或大于10,JDK抛出IllegalArgumentException。JDK默认设置3个优先级常量,MIN_PRIORITY=1(最小值),NORM_PRIORITY=5(中间值,默认),MAX_PRIORITY=10(最大值)。
获取线程的优先级使用getPriority方法。
线程的优先级具有继承性,乐字节比如线程A启动线程B,线程B的优先级与线程A是一样的。 高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部执行完。当线程优先级的等级差距很大时,谁先执行完和代码的调用顺序无关。
线程的优先还有『随机性』,也就是说优先级高的线不一定每一次都先执行完成
1.8 守护线程 在Java线程中有两种线程,一种是用户线程,另一种是守护线程。
守护线程是一种特殊的线程,特殊指的是当进程中不存在用户线程时,守护线程会自动销毁。典型的守护线程的例子就是垃圾回收线程,当进程中没有用户线程,垃圾回收线程就没有存在的必要了,会自动销毁。(设置守护线程必须要在调用start方法之前设置,否则JDK会产生IllegalThreadStateException)
- 线程的同步机制 2.1 如果多个线程同时读写共享变量,会出现数据不一致的问题。Java程序依靠synchronized对线程进行同步,使用synchronized的时候,锁住的是哪个对象非常重要。synchronized取得的锁都是对象锁,而不是把一段代码或方法作为锁,所以那个线程先执行带synchronized关键字修饰的方法,哪个方法就持有该方法所属对象的锁,其它线程只能呈等待状态,前提是多个线程访问的是同一个对象。如果多个线程访问多个对象,JVM会创建出多个对象锁。synchronized修饰普通方法获得的锁对象是this,synchronized修饰静态方法,获得的锁对象是当前类的字节码
2.2 锁重入
关键字synchronized拥有锁重入的功能,就是说在使用synchronized时,当一个线程得到一个对象锁后,再次请求些对象锁时是可以再次得到该对象锁。
同步synchronized(class)代码块的作用其实和synchronized static方法的作用是一样的
2.3 锁的自动释放
当一个线程执行的代码出现了异常,其所持有的锁会自动释放。
2.4 同步不具有继承性
2.5 使用任意对象作为对象锁 除了可以使用syncrhonized(this)来同步代码块,Java还支持『任意对象』作为『对象锁』来实现同步的功能。这个『任意对象』大数是成员变量或方法的参数,使用格式synchronized(非this对象)。
在多个线程持有『对象锁』为同一个对象的情况下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码。如果使用不是同一个对象锁,运行的结果就是异步调用,会交叉运行。
2.6 用字符串做对象,因为常量池,一样的字符串是同一个对象。用自定义类的实例做对象,类的成员变量变了,锁对象没变,因为该对象实例对应的内存地址没有变化
2.7 死锁
是指多个线程在运行过程中因争夺资源而造成的一种僵局,当线程处于这种僵持的状态时,如果没有外力作用,这些线程都无法再继续运行。
package chap2;
public class Demo23 { public static void main(String[] args) throws InterruptedException { Demo23Thread t = new Demo23Thread(); t.setFlag("a");
Thread t1 = new Thread(t);
t1.start();
Thread.sleep(10);
t.setFlag("b");
Thread t2 = new Thread(t);
t2.start();
}
}
class Demo23Thread extends Thread{ private String flag; // 标志,控制代码以什么样的方式运行 private Object lock1 = new Object(); private Object lock2 = new Object();
public void setFlag(String flag){
this.flag = flag;
}
@Override
public void run() {
try {
if ("a".equals(flag)) {
synchronized (lock1) {
System.out.println("flag=" + flag);
Thread.sleep(3000);
synchronized (lock2) {
System.out.println("按lock1=>lock2的顺序执行");
}
}
} else {
synchronized (lock2){
System.out.println("flag=" + flag);
Thread.sleep(3000);
synchronized (lock1){
System.out.println("按lock2->lock1的顺序执行");
}
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
2.8 synchronize和volatile区别:
volatile是线程同步的轻易级实现,它的性能比synchronized要好,并且volatile只能修饰变量。而synchronized可以修饰方法及代码块。随着JDK的版本更新,synchronized在执行效率也得到很大的提升,在开发中synchronized的使用率还是较高
多线程访问volatile不会发生阻塞,而synhcronized会出现阻塞
volatile能保证数据的可见性,不能保证原子性,而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
volatile解决的是变量在多个线程之见的可见性,而synchronized是解决多个线程之间访问资源的同步性
总之,volatile解决的是变量在多个线程之见的可见性和有序性(防止指令重排),而synchronized是解决多个线程之间访问资源的同步性
- 线程间的通信 线程是程序中独立的个体,但这些乐字节个体如果不经过处理就能成为一个整体。线程间的通信就是成为整体的必胜方案之一,可以说使线程间通讯后,线程之间的交互性会更强大,大大提高CPU复用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督。