1、上下文切换
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。有性能损耗,线程不是越多越好,毫秒级
2、多线程一定会快吗?
不一定,如果是单核CPU,需要CPU切换,切换会有性能损耗,需要时间
CPU产生计算浪费时(请求时间远远大于CPU处理时间),使用多线程更合适
让CPU刚好满,多线程的个数取决于CPU浪费比例(请求时间/处理时间),只能判断多线程个数的上限,无法判断下限
3、测线程的工具
使用Lmbench3 可以测量上下文切换的时长。
·使用vmstat可以测量上下文切换的次数。
- JMeter:一个开源的、基于Java的性能测试工具,可以用于测试Web应用程序、数据库、网络等的性能。
- LoadRunner:一个商业性能测试工具,可以用于测试各种应用程序的性能,包括Web、数据库、网络等。
- Gatling:一个开源的、基于Scala的高性能测试工具,主要用于测试Web应用程序的性能。
- Locust:一个开源的、基于Python的性能测试工具,可以用于编写可扩展的性能测试用例。
- Tsung:一个开源的、基于Erlang的性能测试工具,可以用于测试Web、数据库、实时通讯等应用程序的性能。
4、减少上下文切换的方法:减少线程数量
使用线程池:线程池可以重用已经存在的线程,避免创建和销毁线程的开销,从而减少上下文切换的次数。
使用锁机制:使用锁机制可以避免多个线程同时访问共享资源,从而减少上下文切换的次数。
使用并发集合:Java提供了一些并发集合,如ConcurrentHashMap、CopyOnWriteArrayList等,这些集合在多线程环境下具有较好的性能,可以减少上下文切换的次数。
避免频繁的I/O操作:频繁的I/O操作会导致线程频繁的阻塞和唤醒,从而增加上下文切换的次数。因此,应该尽量避免频繁的I/O操作。
合理使用synchronized和volatile:synchronized和volatile是Java中常用的并发控制工具,但是使用不当会导致上下文切换的次数增加。因此,应该根据实际情况合理使用synchronized和volatile。
避免使用Thread.sleep()和Thread.yield():Thread.sleep()和Thread.yield()会导致线程阻塞,从而增加上下文切换的次数。因此,应该尽量避免使用这些方法。
使用Java 8的流式API:Java 8引入了流式API,可以减少对原始数据结构的访问次数,从而减少上下文切换的次数。
5、dump: 将当前信息执行命令信息导出一个文件
6、在Java中,synchronized是一个关键字,用于控制多个线程对共享资源的访问,以避免出现线程安全问题。
synchronized可以用于方法或代码块。当它用于方法时,整个方法都是同步的,这意味着在任何时刻只有一个线程可以执行该方法。当它用于代码块时,它只同步该代码块,而不是整个方法。等代码块/方法执行完后释放锁。
以下是使用synchronized的示例:
public class SynchronizedExample {
private int count = 0;
public synchronized void incrementCount() {
count++;
}
public void worker1() {
for (int i = 0; i < 1000; i++) {
incrementCount();
}
}
public void worker2() {
for (int i = 0; i < 1000; i++) {
incrementCount();
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedExample example = new SynchronizedExample();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
example.worker1();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
example.worker2();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + example.count); // Should be 2000
}
}
在这个示例中,incrementCount()方法是同步的,这意味着在任何时刻只有一个线程可以调用它。尽管有两个线程同时执行worker1()和worker2()方法,但它们都尝试调用incrementCount()方法时,每次只有一个线程能够执行该方法。因此,最终的计数应该是2000。
synchronized 锁代码块时只能锁引用类型,不能是基本类型;不能修饰变量,只能修饰方法
不允许有两个线程对同一资源同时加锁;一个线程对一个资源加锁,其它线程可以访问该资源
使用synchronized关键字时,需要注意以下几点:
对象锁:synchronized关键字用于修饰方法或代码块,称为对象锁。同一时间只有一个线程可以访问被synchronized修饰的方法或代码块,其他线程需要等待当前线程执行完毕后才能继续执行。
锁的粒度:当存在多个线程共同操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据。使用synchronized关键字可以控制多个线程对共享资源的访问,但需要注意锁的粒度。如果锁的粒度太大,可能会导致多个线程同时竞争同一个锁,从而降低程序的性能。
锁的释放:当一个线程持有锁时,其他线程必须等待该线程释放锁后才能继续执行。因此,在使用synchronized关键字时,需要注意锁的释放。如果一个线程在持有锁的过程中出现了异常,那么该锁可能不会被释放,从而导致其他线程一直等待下去,造成死锁。
锁的公平性:Java中的synchronized关键字默认情况下是公平的,即按照线程请求锁的顺序来分配锁。但是,如果多个线程同时竞争同一个锁,那么可能会出现不公平的情况。如果需要保证锁的公平性,可以使用java.util.concurrent.locks.ReentrantLock类来实现。
避免嵌套锁:在使用synchronized关键字时,需要注意避免嵌套锁。如果一个线程已经持有了一个锁,然后又试图获取另一个锁,那么可能会导致死锁。因此,在使用synchronized关键字时,应该尽量避免嵌套锁的情况。
避免在持有锁的情况下进行I/O操作:当一个线程持有锁时,其他线程必须等待该线程释放锁后才能继续执行。因此,如果在持有锁的情况下进行I/O操作(如打印输出、网络通信等),那么可能会导致其他线程一直等待下去,造成死锁。因此,在使用synchronized关键字时,应该尽量避免在持有锁的情况下进行I/O操作。
7、死锁
阻塞状态:不会被操作系统选中执行,能节省CPU开销
public class DeadLockDemo {
privat static String A = "A";
private static String B = "B";
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
private void deadLock() {
Thread t1 = new Thread(new Runnable() {
@Override
publicvoid run() {
synchronized (A) {
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("1");
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
publicvoid run() {
synchronized (B) {
synchronized (A) {
System.out.println("2");
}
}
}
});
t1.start();
t2.start();
}
}
上述是一段死锁的代码。首先t1线程对A代码块进行加锁,加锁之后t1线程随即进入休眠2000毫秒,立即让出CPU;然后执行t2线程,t2线程对B进行了加锁,之后t2线程尝试对A加锁,由于t1线程已经对A加过锁,两个线程不能同时对同一资源进行加锁,所以此时t2进入阻塞状态;等到t1线程休眠2000毫秒后,继续执行t1线程,然后t1线程尝试对B进行加锁,但是B已被t2线程加过锁,所以t1进入阻塞状态,此时t1和t2线程形成死锁。
①什么是死锁
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力作用,它们都将无法推进下去。
②避免死锁的方法
- 避免多次锁定:尽量避免同一个线程对多个 Lock 进行锁定。例如,主线程要对 A、B 两个对象的 Lock 进行锁定,副线程也要对 A、B 两个对象的 Lock 进行锁定,这就埋下了导致死锁的隐患。
- 保持相同的加锁顺序:如果多个线程需要对多个 Lock 进行锁定,则应该保证它们以相同的顺序请求加锁。比如,主线程先对 A 对象的 Lock 加锁,再对 B 对象的 Lock 加锁;而副线程则先对 B 对象的 Lock 加锁,再对 A 对象的 Lock 加锁。这种加锁顺序很容易形成嵌套锁定,进而导致死锁。如果让主线程、副线程按照相同的顺序加锁,就可以避免这个问题。
- 使用定时锁:程序在调用 acquire() 方法加锁时可指定 timeout 参数,该参数指定超过 timeout 秒后会自动释放对 Lock 的锁定,这样就可以解开死锁了。
- 死锁检测:死锁检测是一种依靠算法机制来实现的死锁预防机制,它主要是针对那些不可能实现按序加锁,也不能使用定时锁的场景的。
此外,还有一些其他策略可以避免死锁:
- 尽量避免并发地只执行更新数据的语句。
- 要求每个事务一次就将所有要使用的数据全部加锁,否则就不予执行。
- 预先规定一个封锁顺序,所有的事务都必须按这个顺序对数据执行封锁。例如,不同的过程在事务内部对对象的更新执行顺序尽量保持一致。
- 每个事务的执行时间不可太长,在业务允许的情况下可以考虑将事务分割成为几个小事务来执行。
- 将逻辑上在一个表中的数据尽量按行或列分解为若干小表,以便改善对表的访问性能。一般来讲,如果数据不是经常被访问,那么死锁就不会经常发生。
- 将经常更新的数据库和查询数据库分开。