进程和线程的区别(面试题)
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
进程:一个内存中运行的应用程序,每个进程都有独立的内存空间,进程是程序的一次执行过程,是系统运行程序的基本单位。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。
创建3种多行程的方法
1:创建Thread的子类,重写run方法
/**
* 创建线程的第一种方式是创建Thread的子类,重写run方法
* 在主方法中创建子类线程对象,调用子类的start方法
*
* @author zhuang
*
* 2020年11月13日 下午2:58:32
*/
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("mythread--->"+i);
}
}
2:实现Runnable接口
/**
* 创建线程的的第二种方式,实现Runnable接口,并实现它的run方法
*
* @author zhuang
*
* 2020年11月13日 下午3:03:11
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("myRunnable--->"+i);
}
}
以上两个方法的实现在同一个main方法中
同时这里还有多线程里面常用的API方法
主方法的线程一直存在,垃圾回收机制的线程也存在,所以一个程序最少会启动2个线程
我们创建的线程另外计算
多个线程的堆空间是共享的,栈空间是独立的
单核cpu的情况下,进程是并行的
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread(); //创建线程对象
thread.start(); //调用线程对象的start方法,启动线程
//创建一个线程任务
MyRunnable my = new MyRunnable();
//创建一个线程对象
Thread t = new Thread(my,"runnable");
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("main--->"+i);
}
//线程优先级
System.out.println(Thread.MAX_PRIORITY);
System.out.println(Thread.MIN_PRIORITY);
System.out.println(Thread.NORM_PRIORITY);
//设置线程名称
t.setName("t1");
//开启线程
t.start();
//线程休眠2秒
Thread.sleep(2000);
//获取线程编号
System.out.println(t.getId());
//获取线程名称
System.out.println(t.getName());
//获取线程优先级
System.out.println(t.getPriority());
//获取线程状态
System.out.println(t.getState());
}
通过内部类创建多线程
/**
* 通过创建匿名内部类的方式创建线程
* @author zhuang
*
* 2020年11月13日 下午3:40:51
*/
public class TestInnerClassThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("innerclass---> " + i);
}
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("main--->"+i);
}
}
注意:
第一个main方法中,线程运行的结果每次都不一样,这是因为CPU是不可控的,运行结果就是随机的,但是内部类创建的多线程的运行结果一直是先运行主线程,这个问题我也没弄懂
多线程安全问题
CPU在执行多线程时,CPU不受程序控制。而出现线程不安全的根本原因是因为多个线程在同时操作共享数据时发生的。不能利用程序控制CPU,但是可以通过程序控制共享数据。
同步代码块
通过同步代码块解决多线程的不安全性,不确定性
同步锁只是一个概念,可以想象为在对象上标记了一个锁,锁可以是任意类型,多个线程对象,要使用同一把锁,在任何时候,最多允许一个线程拥有同步锁,谁拿到锁谁就进入代码块,其他的线程只能在外等着,同步锁必须在run方法的外部创建,这样可以保证是所有的线程用的是同一个锁
语法:
synchronized(同步锁){
需要同步操作的代码
}
案例:
/**
*
* @author zhuang
*
* 2020年11月13日 下午4:10:45
*/
public class TickImpl implements Runnable {
int count = 100;
//创建同步代码块
//必须在run方法的外部创建,这样可以保证是所有的线程用的是同一个锁
private Object lock = new Object();
public void run() {
while(true) {
//同步代码块
synchronized (lock) {
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "出售了编号为"+count+"的车票");
count--;
}
}
}
}
}
但是同步锁在频繁的判断锁 、获取锁、释放锁,程序的效率会降低
同步方法
语法:
访问修饰符 synchronized 返回值类型 方法名(参数列表){
//方法体代码
}
案例:
public class TickImpl2 implements Runnable{
int count = 100;
@Override
public void run() {
while(true) {
if(count%2 == 0) {
synchronized (this) {
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "出售了编号为"+count+"的车票");
count--;
}
}
}else {
method();
}
}
}
//同步方法,同步锁是this
public synchronized void method() {
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "出售了编号为"+count+"的车票");
count--;
}
}
}
使用同步方法解决线程不安全的时候,这个方法会分为静态方法和普通方法:
普通方法就是在方法中加入synchronized 修饰符,这个修饰符会让方法变成同步方法,同步方法的锁就不是像同步代码块那样自定义的一个锁了,这个同步方法的锁是这个类的对象,也就是this。
但是静态方法中没有对象,只有类,所以静态方法的锁就是:类名.class,而且静态方法只能调用静态属性,所以count也变成了静态的属性,如下:
public class TickImpl2 implements Runnable{
static int count = 100;
@Override
public void run() {
while(true) {
if(count%2 == 0) {
synchronized (TickImpl2.class) {
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "出售了编号为"+count+"的车票");
count--;
}
}
}else {
method();
}
}
}
//静态同步方法,同步锁是本类
public static synchronized void method() {
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "出售了编号为"+count+"的车票");
count--;
}
}
}
同步锁
/**
* Lock是一个接口,所以需要定义它的实行类对象,这个对象定义在成员变量的位置
* Lock中的方法:void lock()获取锁
* void unlock()释放锁
* @author zhuang
*
* 2020年11月13日 下午4:10:45
*/
public class TickImpl3 implements Runnable {
int count = 100;
//在成员位置创建同步锁ReentrantLock对象
Lock l = new ReentrantLock();
public void run() {
while(true) {
//在可能出现安全问题的代码前调用lock方法获取锁
l.lock();
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "出售了编号为"+count+"的车票");
count--;
}
//在可能出现安全问题的代码后调用unlock方法释放锁
l.unlock();
}
}
}
三种锁的实现类:
public static void main(String[] args) {
TickImpl tick = new TickImpl();
new Thread(tick,"第一个窗口").start();
new Thread(tick,"第二个窗口").start();
new Thread(tick,"第三个窗口").start();
TickImpl2 tick2 = new TickImpl2();
new Thread(tick2,"第一个窗口").start();
new Thread(tick2,"第二个窗口").start();
new Thread(tick2,"第三个窗口").start();
TickImpl3 tick3 = new TickImpl3();
new Thread(tick3,"第一个窗口").start();
new Thread(tick3,"第二个窗口").start();
new Thread(tick3,"第三个窗口").start();
}
死锁
死锁:有多个(两个)线程(A线程、B线程)、有两个不同的对象锁(Lock_A、Lock_B),其中A线程中拥有Lock_A,等待Lock_B, B线程中拥有Lock_B,等待Lock_A,这样就容易产生死锁现象。
注:发生死锁现象无法通过程序的运行来解决问题,只能修改程序中的源代码
–尽可能不要使用嵌套同步代码块
–如果一定要使用嵌套的同步代码块,需要保证同步代码块中使用同一个同步锁
public class ThreadChild implements Runnable {
Object lock_A = new Object();
Object lock_B = new Object();
boolean flag = true;
@Override
public void run() {
while(true) {
if(flag) {
flag = false;
synchronized (lock_A) {
synchronized (lock_B) {
System.out.println(Thread.currentThread().getName()+"先获取A,再获取B");
}
}
}else {
flag = true;
synchronized (lock_B) {
synchronized (lock_A) {
System.out.println(Thread.currentThread().getName()+"先获取B,再获取A");
}
}
}
}
}
}