进程和线程的概念
进程:正在运行的程序,是系统内进行资源分配和调用的独立单位。每个进程都有它自己的内存空间和系统资源。
线程:是进程中单个顺序控制流,是一条执行路径。一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行路径,则称为多线程程序。
实现多线程程序的方法:
- 继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public static void main(String[] args){
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
相关方法:
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。 让多个线程的执行更和谐,但是不能靠它保证一人一次。
public final void stop():让线程停止,过时了,但是还可以使用。
public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
- 实现接口
public class MyRunnableDemo {
public static void main(String[] args){
MyRunnable runnable1 = new MyRunnable();
Thread thread1 = new Thread(runnable1, "刘雨昕");
Thread thread2 = new Thread(runnable1, "许佳琪");
thread1.start();
thread2.start();
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0; i<100; i++){
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}
线程的生命周期
- 新建
- 就绪
- 运行
- 阻塞
- 死亡
实际应用(模拟卖票)
public class SellTicket extends Thread{
private static int tickets = 100;
@Override
public void run() {
while (tickets >= 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":" + tickets--);
}
}
}
public class SellTicketDemo {
public static void main(String[] args){
SellTicket thread1 = new SellTicket();
SellTicket thread2 = new SellTicket();
SellTicket thread3 = new SellTicket();
thread1.setName("窗口一");
thread2.setName("窗口二");
thread3.setName("窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
出现的问题:
- 相同的票卖了多次
原因:CPU的一次操作必须是原子性的
System.out.println(getName() + “:” + tickets–);这一语句分为三个原子性操作,分别为先记录以前的值,接着把ticket–,然后输出以前的值。
过程分析:假设当前 tickets为94,thread1线程运行,执行System.out.println(getName() + “:” + tickets–)时,记录以前的值为94,此时thread2抢到时间片,执行System.out.println(getName() + “:” + tickets–)记录以前的值为94,此时thread3抢到时间片,执行System.out.println(getName() + “:” + tickets–)记录以前的值为94,接着把ticket–,然后输出94,此时thread2抢到时间片,这时thread2记录的值是94(但是实际值已经变成了93),ticket–,然后输出94,由此带来错误。 - 出现了负票数
原因:随机性和延迟
过程分析:thread1抢到时间片,进入run程序睡眠,此时thread2抢到时间片,进入run程序并睡眠,此时thread3抢到时间片,进入run程序睡眠,thread123依次醒来,执行System.out.println(getName() + “:” + tickets–),就出现了图片所示情况。
解决:
出现线程安全问题的情况为:
A:是多线程环境
B:有共享数据
C:有多条语句操作共享数据
AB无法控制,对于C可以加锁将多条操作语句锁起来。即线程同步。
注意:
-
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
-
多个线程必须是同一把锁。
public class SellTicket extends Thread{
private static int tickets = 100;
//创建锁对象
private static Object obj = new Object();
@Override
public void run() {
synchronized (obj){
while (tickets >= 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":" + tickets--);
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args){
SellTicket thread1 = new SellTicket();
SellTicket thread2 = new SellTicket();
SellTicket thread3 = new SellTicket();
thread1.setName("窗口一");
thread2.setName("窗口二");
thread3.setName("窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
这是用继承thread类的方法实现的,其中tickets和obj都必须为静态变量,保证一个类中共用一个tickets和obj。
但这样会出现死锁问题需要注意,如:
public class DieLock extends Thread{
private boolean flag;
private static final Object obj1 = new Object();
private static final Object obj2 = new Object();
public DieLock(boolean flag) {
this.flag = flag;
}
public DieLock() {
}
@Override
public void run() {
if(flag){
synchronized (obj1){
System.out.println("if obj 1");
synchronized (obj2){
System.out.println("if obj 2");
}
}
}else {
synchronized (obj2){
System.out.println("if obj 2jiojoiji");
synchronized (obj1){
System.out.println("if obj 1hiughjhjkhk");
}
}
}
}
}
```java
public class DieLockDemo {
public static void main(String[] args){
DieLock lock1 = new DieLock(true);
DieLock lock2 = new DieLock(false);
lock1.start();
lock2.start();
}
}
正确结果为:
但会出现死锁情况,如:
分析:这是由于lock2将obj2锁住打印出“if obj 2”后,lock1抢到时间片将obj2锁住打印出“if obj 1”,接下来就无法执行了,因为两个锁都被锁住了。
线程组和线程池
线程组:java使用ThreadGroup来表示线程组,它可以对一匹线程进行分类管理,并且允许程序直接对线程组进行控制。
线程池:程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
- 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
- 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
相关知识点
并发和并行:
并行指逻辑上同时发生,指在某一个时间内同时运行多个程序。
并发指物理上同时发生,指在某一个时间点同时运行多个程序。
java程序的运行原理:
由java命令启动JVM,JVM启动就相当于启动了一个进程,该进程会创建一个主线程调用main方法。
实现多线程的程序:
线程是依赖进程而存在的,应该先创建一个进程,而进程是由系统创建的,所以要调用系统功能创建一个进程。而Java无法直接调用系统功能,所以没有办法直接实现多线程程序。但是Java可以调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用由此实现多线程程序。
线程安全的类:
StringBuffer
Vector
Hashtable