多线程
概念:
1. 进程 ---> 指编写好的程序,在设备中运行起来后,所属的那片内存空间。
2. 线程 ---> 指程序运行时,负责执行指令的执行器。
package com.evan.counter;
public class Test {
public static void main(String[] args) {
//以下代码是通过主线程来执行机器指令
System.out.println("......");
System.out.println("......");
System.out.println("......");
System.out.println("......");
System.out.println("......");
System.out.println("......");
}
}
多线程则是指有多个指令执行器可以同时执行多段指令(代码)。
线程的创建:
1. 继承Thread类,复写run方法。
package com.evan.counter;
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
//要执行的代码
}
}
2. 实现Runnable接口, 创建Thread,构造参数传入Runnable。
package com.evan.counter;
public class Test {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
//要执行的代码
}
}
线程和CPU之间的关系:
CPU作为处理器,在一个时间段内只能处理一条线程,假设一个系统内开启了3条线程,那么CPU是在这3条线程之间做着随机切换,来保证多线程的“同时”执行。只是这个时间片很短。而让用户感觉不到。
线程状态:
- NEW(创建状态): 线程对象创建后的状态。
- RUNNABLE(可运行状态):在调用了Thread.start()之后,线程进入可运行状态,也就获取到了CPU的执行资格。
- RUNNING(运行状态) : 在获取到CPU执行资格之后,这时CPU也切换到了该线程上,就是运行状态。
- BLOCK(阻塞状态) : 在调用了thread.sleep或wait方法后,线程丧失CPU执行资格,进入阻塞状态。
- DESTORY(销毁状态) : 在调用了thread.destory()方法后,线程进入销毁状态。
- TERMINATED(结束状态) : 在线程的run方法运行完毕后,线程进入此状态。
多个线程访问多行代码涉及的问题:
先看一段示例代码
package com.evan.counter;
public class Test {
public static void main(String[] args) {
Resource resource = new Resource();
MyRunnable runnable = new MyRunnable(resource);
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
Thread t4 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource{
private int count = 100;
public void mute() {
if(count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("Count: " + count);
}
}
}
class MyRunnable implements Runnable{
private Resource resource;
public MyRunnable(Resource resource) {
super();
this.resource = resource;
}
@Override
public void run() {
//要执行的代码
while(true) {
resource.mute();
}
}
}
运行结果有些意外,在我们进行count--操作前,我们已经进行了判断,if(count > 0),那为什么还会出现这样的情况。
这种情况就是因为CPU的随机切换造成的。
结论就是: 如果有多个线程同时操作,多行代码,并且这多行代码都操作同一个变量时,就会造成这个问题。
解决办法:
我们如果想解决这个问题,就需要保证,在多行代码操作同一个变量时(区间内),保证只有一条线程进行操作,一条线程在操作的时候,其它线程就算切换到了RUNNING状态,也不能运行这多行代码。
这就涉及到了一个关键字,synchronized
synchronized关键字可以保证多行代码涉及到多线程时的执行问题,可以保证多行代码之间只允许一条线程执行,执行完毕后其它线程才可以继续执行。
我们修改一下Resource的代码,假如synchronized关键字
class Resource{
private int count = 100;
public void mute() {
synchronized (Resource.class) {
if(count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("Count: " + count);
}
}
}
}
现在运行结果已经正常了。
synchronized关键字的3种用法
前提: 使用synchronized关键字进行加锁时,需要保证多个线程之间使用的锁是同一把锁。
- 代码块 (同步代码块中放入显性的锁对象)
class Resource{ private int count = 100; public void mute() { synchronized (Resource.class) { if(count > 0) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println("Count: " + count); } } } }
-
函数(同步函数使用的锁是this对象)
class Resource{ private int count = 100; public synchronized void mute() { if(count > 0) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println("Count: " + count); } } }
-
静态函数(静态同步函数使用的锁是当前对象的字节码对象)
class Resource{ private static int count = 100; public static synchronized void mute() { if(count > 0) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println("Count: " + count); } } }