目录
进程
进程是指内存中运行的一个应用程序,是操作系统资源分配的基本单位,有独立的内存空间,至少有一个线程,可以同时运行多个线程。
比如java -jar启动一个java的进程,可以通过参数配置指定分配多少系统资源给这个进程。但是这个进程本身是不执行任何任务的,至少启动了两个线程:主线程和垃圾回收线程。我们的代码逻辑都是cpu调度线程执行的。
linux查看当前系统运行的进程
ps aux
a:显示当前终端下的所有进程信息,包括其他用户的进程。
u:使用以用户为主的格式输出进程信息。
x:显示当前用户在所有终端下的进程。
java启动进程的两种方式
线程
线程是操作系统进行运算和调度的最小单元,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
linux查看当前系统运行的线程
ps -xH | grep pid
java启动线程的三种方式
1,继承Thread类,重写run()方法,调用线程类的start()方法。
2,实现Runnable接口,实现run()方法。
匿名内部类是实现Runnable接口
3,实现Callable接口,实现call方法
进程和线程的关系
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
并发(Concurrent)
指两个或多个事件在同一时间段内发生
在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
并行(Parallel)
指两个或多个事件在同一时刻点发生
在操作系统中是指,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。
//查看cpu型号
[root@VM-16-2-centos /]# cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c
1 Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz
//查看物理cpu个数
[root@VM-16-2-centos /]# cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
1
//查看每个cpu的核数
[root@VM-16-2-centos /]# cat /proc/cpuinfo| grep "cpu cores"| uniq
cpu cores : 1
//查看逻辑处理单元个数
[root@VM-16-2-centos /]# cat /proc/cpuinfo| grep "processor"| wc -l
1
当系统只有一个逻辑处理单元时,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权。
当系统有一个以上逻辑处理单元时,则同一个进程里的两个线程是有可能并行执行的。两个线程分别使用一个逻辑处理单元互不抢占CPU资源,可以做到同一时刻并行运行。
总结来说,单核处理器多个线程只能并发运行,多核处理器多个线程并发并行都存在。
线程的状态
源码中的截图,线程状态共有6个,并且一个线程同一时刻只存在一种状态。
合理利用多线程执行任务,可以有效利用CPU,提升程序的处理效率。但是在多线程运行时也会发生安全问题。
最经典的案例就是卖火车票。
public class ThreadTest {
public static void main(String[] args) {
Huochepiao huochepiao = new Huochepiao();
Thread conductor1 = new Thread(huochepiao, "售票员1");
Thread conductor2 = new Thread(huochepiao, "售票员2");
conductor1.start();
conductor2.start();
}
}
class Huochepiao extends Thread{
private static int ticket = 100;
@Override
public void run(){
while (true){
if (ticket <= 0) {
return;
}
System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
ticket--;
}
}
}
运行结果如下,发现售票员1和售票员2都卖出了第一张票。
为什么出现这样的问题呐,就要看java的内存模型了。
Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问其他工作内存中的变量,线程间变量值的传递均需要在主内存来完成。
比如上述出现的售票员1和售票员2都卖出了第一张票,就是售票员1从主内存取到总票数为100,然后卖出第一张票,此时票数变为99了,但是这个信息没有同步到主内存,售票员2开始运行的时候又从主内存取值,拿到的还是100张票,就导致了卖出了两个第一张票。
要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
解决思路:
1,多线程之间不同享变量。
1,线程封闭
保证变量只被一个线程使用,就不会出现线程安全问题。上述代码我们改造一下:
public class ThreadTest { public static void main(String[] args) { Huochepiao huochepiao1 = new Huochepiao(100); Huochepiao huochepiao2 = new Huochepiao(100); Thread conductor1 = new Thread(huochepiao1, "售票员1"); Thread conductor2 = new Thread(huochepiao2, "售票员2"); conductor1.start(); conductor2.start(); } } class Huochepiao extends Thread{ private static Integer ticket = 0; Huochepiao(Integer ticket){ this.ticket = ticket; } @Override public void run(){ while (true){ if (ticket <= 0) { return; } System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票"); ticket--; } } }
可以看到即使我们实例化了两个Huochepiao类,仍然出现了多卖的情况。因为ticket是static修饰的,属于类所有只有一份,所有多个线程共享还是有线程安全问题。
接下来我们去掉static修饰符,再次测试
public class ThreadTest { public static void main(String[] args) { Huochepiao huochepiao1 = new Huochepiao(100); Huochepiao huochepiao2 = new Huochepiao(100); Thread conductor1 = new Thread(huochepiao1, "售票员1"); Thread conductor2 = new Thread(huochepiao2, "售票员2"); conductor1.start(); conductor2.start(); } } class Huochepiao extends Thread{ private Integer ticket = 0; Huochepiao(Integer ticket){ this.ticket = ticket; } @Override public void run(){ while (true){ if (ticket <= 0) { return; } System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票"); ticket--; } } }
可以看到两个线程即两个售票员自己卖自己的票,根本不会影响其他线程中的变量。
2,栈封闭
栈封闭即使用局部变量。局部变量只会存在于本地方法栈中,不能被其他线程访问,因此也就不会出现并发问题。所以如果可以使用局部变量就优先使用局部变量。public class ThreadTest { public static void main(String[] args) { Huochepiao huochepiao = new Huochepiao(); Thread conductor1 = new Thread(huochepiao, "售票员1"); Thread conductor2 = new Thread(huochepiao, "售票员2"); conductor1.start(); conductor2.start(); } } class Huochepiao extends Thread{ @Override public void run(){ Integer ticket = 100; while (true){ if (ticket <= 0) { return; } System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票"); ticket--; } } }
可以看到我们将ticket定义在run方法内部,此时ticket变量就处于栈封闭,run方法压栈再出栈局部变量ticket是不受其他线程影响的。
3,ThreadLocal封闭
ThreadLocal是Java提供的实现线程封闭的一种方式,ThreadLocal内部维护了一个Map,Map的key是各个线程,而Map的值就是要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。public class ThreadTest { public static void main(String[] args) { Huochepiao huochepiao = new Huochepiao(); Thread conductor1 = new Thread(huochepiao, "售票员1"); Thread conductor2 = new Thread(huochepiao, "售票员2"); conductor1.start(); conductor2.start(); } } class Huochepiao extends Thread{ private static Integer tickets = 100; private static ThreadLocal<Integer> ticketThreadLocal = new ThreadLocal<>(); @Override public void run(){ ticketThreadLocal.set(tickets); Integer ticket = ticketThreadLocal.get(); while (true){ if (ticket <= 0) { return; } System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票"); ticket--; } } }
结果上也是各有100张票各卖各的。
2,多线程同步机制
1,同步代码块
public class ThreadTest { public static void main(String[] args) { Huochepiao huochepiao = new Huochepiao(); Thread conductor1 = new Thread(huochepiao, "售票员1"); Thread conductor2 = new Thread(huochepiao, "售票员2"); conductor1.start(); conductor2.start(); } } class Huochepiao extends Thread{ private static Integer ticket = 100; @Override public void run(){ while (true) { synchronized (this) { if (ticket <= 0) { return; } System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票"); ticket--; } } } }
2,同步方法
ublic class ThreadTest { public static void main(String[] args) { Huochepiao huochepiao = new Huochepiao(); Thread conductor1 = new Thread(huochepiao, "售票员1"); Thread conductor2 = new Thread(huochepiao, "售票员2"); conductor1.start(); conductor2.start(); } } class Huochepiao extends Thread{ private static Integer ticket = 100; @Override public void run(){ ticketSell(); } synchronized private void ticketSell() { while (true) { if (ticket <= 0) { return; } System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票"); ticket--; } } }
3,lock锁
public class ThreadTest { public static void main(String[] args) { Huochepiao huochepiao = new Huochepiao(); Thread conductor1 = new Thread(huochepiao, "售票员1"); Thread conductor2 = new Thread(huochepiao, "售票员2"); conductor1.start(); conductor2.start(); } } class Huochepiao extends Thread{ private static Integer ticket = 100; private final Lock lock = new ReentrantLock(); @Override public void run(){ lock.lock(); try { while (true) { if (ticket <= 0) { return; } System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票"); ticket--; } }finally { lock.unlock(); } } }
3,使用线程安全的类或集合
线程安全对象有Vector,HashTable,经过Collections.synchronizedCollection()方法包装的集合对象、ConcurentHashMap,ConcurentLinkedQueue,CopyOnWriteArrayList、BlockingQueue的实现类型、AtomicInteger,AtomicLong等。
public class ThreadTest { public static void main(String[] args) { Huochepiao huochepiao = new Huochepiao(); Thread conductor1 = new Thread(huochepiao, "售票员1"); Thread conductor2 = new Thread(huochepiao, "售票员2"); conductor1.start(); conductor2.start(); } } class Huochepiao extends Thread{ private static AtomicInteger ticket = new AtomicInteger(100); @Override public void run(){ while (true) { if (ticket.get() <= 0) { return; } int decrementAndGet = ticket.getAndDecrement(); System.out.println(currentThread().getName() + " 卖出了第 " + (100 - decrementAndGet + 1) + " 张票"); } } }
死锁问题
加锁不对还容易导致死锁问题。
public class ThreadTest {
public static void main(String[] args) {
Huochepiao huochepiao = new Huochepiao();
Huochepiao2 huochepiao2 = new Huochepiao2();
Thread conductor1 = new Thread(huochepiao, "售票员1");
Thread conductor2 = new Thread(huochepiao2, "售票员2");
conductor1.start();
conductor2.start();
}
}
class Huochepiao extends Thread{
private static Integer ticket = 100;
@Override
public void run(){
while (true) {
synchronized (Huochepiao2.class) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Huochepiao.class) {
if (ticket <= 0) {
return;
}
System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
ticket--;
}
}
}
}
}
class Huochepiao2 extends Thread{
private static Integer ticket = 100;
@Override
public void run(){
while (true) {
synchronized (Huochepiao.class) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Huochepiao2.class) {
if (ticket <= 0) {
return;
}
System.out.println(currentThread().getName() + " 卖出了第 " + (100 - ticket + 1) + " 张票");
ticket--;
}
}
}
}
}
jps看一下进程号
jstack看一下这两线程为啥不动了,发现死锁了。