java的多线程劣势_1 多线程的优缺点

之前写的都乱糟糟的,现在也需要重新记忆一遍。所以重新整理一下JUC包。

多线程及其优缺点

什么是线程

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。(wiki百科)

创建线程的三种方式

public class ThreadTest {

public static void main(String[] args) throws InterruptedException {

//1、继承Thread方式

Thread thread1 = new Thread(){

@Override

public void run() {

System.out.println("thread1 start");

}

};

thread1.start();

//2、实现Runnable接口

Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("thread2 start");

}

});

thread2.start();

//3、实现Callable接口

ExecutorService executorService = Executors.newSingleThreadExecutor();

Future future = executorService.submit(new Callable() {

@Override

public String call() throws Exception {

return "future start";

}

});

try {

String result = future.get();

System.out.println(result);

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

}

在jdk8之后用lambda表达式转换一下

public class ThreadTest {

public static void main(String[] args) throws InterruptedException {

//1、继承Thread方式

Thread thread1 = new Thread(() -> System.out.println("thread1 start"));

thread1.start();

//2、实现Runnable接口

Thread thread2 = new Thread(() -> System.out.println("thread2 start"));

thread2.start();

//3、实现Callable接口

ExecutorService executorService = Executors.newSingleThreadExecutor();

Future future = executorService.submit(() -> "future start");

try {

String result = future.get();

System.out.println(result);

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

}

简化了一点,但是更多是有点懵,lambda为什么会简化方法,->是怎么找到对应的方法,下次在研究。

为什么要用多线程

早期的CPU是单核的,为了提升计算能力,将多个计算单元整合到一起。形成了多核CPU。多线程就是为了将多核CPU发挥到极致,一边提高性能。

多线程缺点呢

上面说了多线程的有点是:为了提高计算性能。那么一定会提高?

答案是不一定的。有时候多线程不一定比单线程计算快。引入《java并发编程的艺术》上第一个例子

public class ConcurrencyTest {

/** 执行次数 */

private static final long count = 10000l;

public static void main(String[] args) throws InterruptedException {

//并发计算

concurrency();

//单线程计算

serial();

}

private static void concurrency() throws InterruptedException {

long start = System.currentTimeMillis();

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

int a = 0;

for (long i = 0; i < count; i++) {

a += 5;

}

System.out.println(a);

}

});

thread.start();

int b = 0;

for (long i = 0; i < count; i++) {

b--;

}

thread.join();

long time = System.currentTimeMillis() - start;

System.out.println("concurrency :" + time + "ms,b=" + b);

}

private static void serial() {

long start = System.currentTimeMillis();

int a = 0;

for (long i = 0; i < count; i++) {

a += 5;

}

int b = 0;

for (long i = 0; i < count; i++) {

b--;

}

long time = System.currentTimeMillis() - start;

System.out.println("serial:" + time + "ms,b=" + b + ",a=" + a);

}

}

结果为

50000

concurrency :22ms,b=-10000

serial:0ms,b=-10000,a=50000

而且多线程会带来额外的开销

上下文切换

线程安全

上下文切换

时间片是CPU分配给各个线程的时间,因为时间非常短,所以CPU不断通过切换线程,让我们觉得多个线程是同时执行的,时间片一般是几十毫秒。而每次切换时,需要保存当前的状态起来,以便能够进行恢复先前状态,而这个切换时非常损耗性能,过于频繁反而无法发挥出多线程编程的优势。

减少上下文切换可以采用无锁并发编程,CAS算法,使用最少的线程和使用协程。

无锁并发编程:可以参照concurrentHashMap锁分段的思想,不同的线程处理不同段的数据,这样在多线程竞争的条件下,可以减少上下文切换的时间。

CAS算法,利用Atomic下使用CAS算法来更新数据,使用了乐观锁,可以有效的减少一部分不必要的锁竞争带来的上下文切换

使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多的线程,这样会造成大量的线程都处于等待状态

协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换

线程安全的问题

多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的情况

同样引入《java并发编程的艺术》的一个例子

public class DeadLockDemo {

private static String resource_a = "A";

private static String resource_b = "B";

public static void main(String[] args) {

deadLock();

}

public static void deadLock() {

Thread threadA = new Thread(new Runnable() {

@Override

public void run() {

synchronized (resource_a) {

System.out.println("get resource a");

try {

Thread.sleep(3000);

synchronized (resource_b) {

System.out.println("get resource b");

}

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

});

Thread threadB = new Thread(new Runnable() {

@Override

public void run() {

synchronized (resource_b) {

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("get resource b");

synchronized (resource_a) {

System.out.println("get resource a");

}

}

}

});

threadA.start();

threadB.start();

}

}

然后通过jps查看,找个这个类的id

然后通过jstack id来查看

Found one Java-level deadlock:

=============================

"Thread-1":

waiting to lock monitor 0x0000000016074808 (object 0x00000000e0b89280, a java.lang.String),

which is held by "Thread-0"

"Thread-0":

waiting to lock monitor 0x0000000016075ca8 (object 0x00000000e0b892b0, a java.lang.String),

which is held by "Thread-1"

Java stack information for the threads listed above:

===================================================

"Thread-1" #11 prio=5 os_prio=0 tid=0x00000000175ba800 nid=0x232c waiting for monitor entry [0x000000001889f000]

java.lang.Thread.State: BLOCKED (on object monitor)

at DeadLockDemo$2.run(DeadLockDemo.java:37)

- waiting to lock <0x00000000e0b89280> (a java.lang.String)

- locked <0x00000000e0b892b0> (a java.lang.String)

at java.lang.Thread.run(Thread.java:748)

"Thread-0" #10 prio=5 os_prio=0 tid=0x00000000175b7800 nid=0x234c waiting for monitor entry [0x000000001861f000]

java.lang.Thread.State: BLOCKED (on object monitor)

at DeadLockDemo$1.run(DeadLockDemo.java:18)

- waiting to lock <0x00000000e0b892b0> (a java.lang.String)

- locked <0x00000000e0b89280> (a java.lang.String)

at java.lang.Thread.run(Thread.java:748)

两个线程相互等待,仔细看上面的waiting to lock 和locked两个对象。是相互的。造成死锁。

造成死锁的原因和解决方案

死锁:指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

造成死锁的原因是:

因为系统资源不足。

进程运行推进的顺序不合适。

资源分配不当等。

如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则

就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

那么死锁的必要条件是:

互斥条件:一个资源每次只能被一个进程使用。

请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是 死锁的必要条件 ,只要系统发生死锁,这些条件必然成立,而只要上述条件之

一不满足,就不会发生死锁。

线程的状态

线程有6种状态

NEW:新建,线程被构建,但是还没有start()

RUNNABLE:运行,java中将就绪和运行统称为运行中

BLOCKED:阻塞,线程阻塞于锁

WAITING:等待,表示线程进入等待状态,需要其他线程的特定动作(通知或中断)

TIMED_WAITING:带超时的等待,可以在指定的时间内自动返还

TERMINATED:终止,表示线程已经执行完毕

b282913bfc7b

状态转换

线程创建之后调用start()方法开始运行。

当调用wait(),join(),LockSupport.lock()方法线程会进入到WAITING状态,而同样的wait(long timeout),sleep(long), join(long), LockSupport.parkNanos(), LockSupport.parkUtil()增加了超时等待的功能,也就是调用这些方法后线程会进入TIMED_WAITING状态,当超时等待时间到达后,线程会切换到Runable的状态,另外当WAITING和TIMED _WAITING状态时可以通过Object.notify(),Object.notifyAll()方法使线程转换到Runable状态。当线程出现资源竞争时,即等待获取锁的时候,线程会进入到BLOCKED阻塞状态,当线程获取锁时,线程进入到Runable状态。线程运行结束后,线程进入到TERMINATED状态,状态转换可以说是线程的生命周期。

注意

当线程进入到synchronized方法或者synchronized代码块时,线程切换到的是BLOCKED状态.

而使用java.util.concurrent.locks下lock进行加锁的时候线程切换的是WAITING或者TIMED_WAITING状态,因为lock会调用LockSupport的方法。

线程状态的操作

interrupted()

中断可以理解为线程的一个标志位,它表示了一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了一个招呼。

其他线程可以调用该线程的interrupt()方法对其进行中断操作,同时该线程可以调用 isInterrupted()来感知其他线程对其自身的中断操作,从而做出响应。

另外,同样可以调用Thread的静态方法 interrupted()对当前线程进行中断操作,该方法会清除中断标志位。

public class InterruptDemo {

public static void main(String[] args) throws InterruptedException {

//sleepThread睡眠1000ms

final Thread sleepThread = new Thread() {

@Override

public void run() {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

super.run();

}

};

//busyThread一直执行死循环

Thread busyThread = new Thread() {

@Override

public void run() {

while (true) ;

}

};

sleepThread.start();

busyThread.start();

sleepThread.interrupt();

busyThread.interrupt();

while (sleepThread.isInterrupted()) ;

System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted());

System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted());

}

}

运行结果是:

b282913bfc7b

对着两个线程进行中断操作,可以看出sleepThread抛出InterruptedException后清除标志位,而busyThread就不会清除标志位。

join()

join方法可以看做是线程间协作的一种方式。

如果一个线程实例A执行了threadB.join(),其含义是:当前线程A会等待threadB线程终止后threadA才会继续执行。

public class JoinDemo {

public static void main(String[] args) {

Thread previousThread = Thread.currentThread();

for (int i = 1; i <= 5; i++) {

Thread curThread = new JoinThread(previousThread);

curThread.start();

previousThread = curThread;

}

}

static class JoinThread extends Thread {

private Thread thread;

public JoinThread(Thread thread) {

this.thread = thread;

}

@Override

public void run() {

try {

//join

thread.join();

System.out.println(thread.getName() + " terminated.");

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

b282913bfc7b

运行结果

如果注释了上面的thread.join();

b282913bfc7b

运行结果

每个线程都会等待前一个线程结束才会继续运行。

sleep() VS wait()

两者主要的区别:

sleep()方法是Thread的静态方法,而wait是Object实例方法

wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;

sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。

守护线程Daemon

守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地守护一些系统服务,比如垃圾回收线程,JIT线程就可以理解守护线程。与之对应的就是用户线程,用户线程就可以认为是系统的工作线程,它会完成整个系统的业务操作。用户线程完全结束后就意味着整个系统的业务任务全部结束了,因此系统就没有对象需要守护的了,守护线程自然而然就会退。当一个Java应用,只有守护线程的时候,虚拟机就会自然退出。下面以一个简单的例子来表述Daemon线程的使用。

public class DaemonDemo {

public static void main(String[] args) {

Thread daemonThread = new Thread(new Runnable() {

@Override

public void run() {

while (true) {

try {

System.out.println("i am alive");

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

System.out.println("finally block");

}

}

}

});

//设置为守护线程

daemonThread.setDaemon(true);

daemonThread.start();

//确保main线程结束前能给daemonThread能够分到时间片

try {

Thread.sleep(800);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

运行结果:

b282913bfc7b

结果

守护线程应该先于start()方法之前。如果在之后,但是该线程还是会执行,只不过会当做正常的用户线程执行。

其他的一些概念

同步和异步

同步和异步通常用来形容一次方法调用。

同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。

而异步调用,指的是,调用者不用管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。

并发与并行

并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。

实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。

阻塞和非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响。

比如一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞。

而非阻塞就恰好相反,它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试地往前运行。

临界区

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页