一:进程概述及多进程的意义
进程概述:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的
内存空间和系统资源。
多进程的意义:多进程的意义单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏
(游戏进程一边听 乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。
比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务.
对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换.
且切换速度很快, 所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。 多进程的作用不是 提高执行速度,而是提高CPU的使用率。
二:线程概述及多线程的意义
线程:在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。
多线程有什么意义:多线程的作用不是提高执行速度,而是为了提高应用程序的使用率. 那么怎么理解这个问题呢?
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.
Java程序运行原理和JVM的启动是多线程的吗?
Java程序运行原理:
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。
JVM的启动是多线程的吗:
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
1. java中的多线程:Thread类
创建线程:第一种方法
Thread t = new Thread() {
public void run() { // 运行
// 线程需要执行的代码
// 不能抛出检查异常,只能自己处理检查异常
}
};
直接调用run和使用start间接调用run的区别:
直接调用run是在主线程中执行了run,没有启动新的线程
使用start是启动新的线程,通过新的线程间接执行run
package ogr.westos.Demo;
public class Demo1 {
public static void main(String[] args) {
//创建一个线程
Thread thread = new Thread(){
public void run(){
System.out.println("线程thread开始运行");
}
};
thread.start();//启动线程
System.out.println("主线程开始运行");
}
}
创建线程的第二中方法:
把线程和要执行的代码分开
Thread 代表线程
Runnable 可运行的(线程要执行的代码)
package ogr.westos.Demo;
public class Demo2 {
public static void main(String[] args) {
Runnable runnable = new Runnable(){
public void run(){
System.out.println("线程所需要执行的代码");
}
};
Thread thread = new Thread(runnable);//创建一个线程,把包装需要执行代码的runnable,传入此线程中
thread.start();//线程开始
System.out.println("主线程开始运行");
}
}
2. 线程中常见的方法
Thread.sleep(long n); // 让当前线程休眠n毫秒
Thread.currentThread(); // 找到当前线程 join(long n) 表示等待线程结束,但是最多等待n毫秒 start() 让线程启动, 只能调用一次,如果调用了多次会出现IllegalThreadStateException
其它方法:
getName() 得到线程的名称
yield() 谦让
不推荐使用的方法
.stop() 让线程停止
.suspend() 让线程暂停
.resume() 让线程继续
线程中常见方法的举例:
package ogr.westos.Demo;
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread()+"开始");
try {
System.out.println("线程一开始休眠");
Thread.sleep(1000);//线程休眠一秒
System.out.println("线程一结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("主线程开始");
t1.start();//线程1开始
t1.join(2000);//等待线程1结束,最多等两秒
System.out.println("主线程结束");
}
}
3.守护线程:
java进程需要等待所有线程都运行结束,才会结束.
有一种特殊的线程叫做守护(守护的是主线程)线程,只要主线程运行结束,即使守护线程的代码没有执行完, 也会跟着主线程一起结束
守护线程举例:
package ogr.westos.Demo;
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("守护线程开始");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程结束");
});
t1.setDaemon(true);
t1.start();
Thread.sleep(5000);
}
}
4.线程的并发(Concurrent)
判断一个多线程应用程序是否有问题的标准:
是否是多线程环境
是否存在共享数据
是否存在多条语句同时操作共享数据
synchronized (同步关键字)
解决这一安全问题: 需要使用同步代码块:
格式:
synchronized(对象){//不能在括号了直接new 对象 new 了 就没效果
要被同步的代码
}
这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注:这个对象 要定义为静态成员变量 才能被所有线程共享
需要这个对象被所有的线程对象所共享,这个对象其实就是一把锁.这个对象习惯叫做监视器
每个对象都有一个自己的monitor(监视器),当一个线程调用synchronized(对象),就相当于进入了这
个对象的监视器。要检查有没有owner,如果没有,此线程成为owner; 但如果已经有owner了,这个线程在entryset
的区域等待 owner的位置空出来.在竞争的时候,是非公平的synchronized必须是进入同一个对象的monitor
同步的好处: 同步的出现解决了多线程的安全问题。
同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行 效率。
举例:
package ogr.westos.Demo;
public class Demo6{
static int i = 0;
static Object obj = new Object(); //创建一个对象,这个对象被共享,作为线程在执行过程中的监视器(锁)
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {//线程一
for (int j = 0; j < 5000; j++) {
synchronized (obj) { // 线程一进锁
i++;
}//线程执行完后,线程1出锁
}
});
Thread t2 = new Thread(() -> { // 乙
for (int j = 0; j < 5000; j++) {
synchronized (obj) {//线程一如果进锁,则线程二在外面等待,等线程一出锁猜可以进入
i--;
} //
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
5.volatile 易变的
可以用来修饰成员变量和静态成员变量,他可以防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的 值。它保证的是变量在多个线程之间的可见性, 不能保证原子性
package ogr.westos.Demo;
//一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止
public class Demo7 {
volatile static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
Thread.sleep(1000);
run = false;
}
}
一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止
synchronized 语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性。
但缺点是synchronized是属于重 量级操作,性能会受到影响。
6.synchronized的另外两种写法
1).synchronized关键字加在方法上:
public synchronized void test() {
}
等价于
public void test() {
synchronized(this) {
}
}
```
2).synchronized关键字加在静态方法上:
class Test{
public synchronized static void test() {
}
}
等价于
public static void test() {
synchronized(Test.class) {
}
}
同步代码块的锁对象: 任意一个对象
同步方法的锁对象: 是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象
7:线程死锁
如果出现了同步嵌套,就容易产生死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
package ogr.westos.Demo;
public class Demo8 {
static Object obj1 = new Object();
static Object obj2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1开始");
synchronized (obj1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程一进入obj1锁");
synchronized (obj2){
System.out.println("线程一进入obj2锁");
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程2开始");
synchronized (obj2){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2进入obj2锁");
synchronized (obj1){
System.out.println("线程2进入obj1锁");
}
}
}
});
t1.start();
t2.start();
}
}
7. wait() notify() notifyAll()都属于Object对象的方法
wait() 等待
notify() 唤醒
它们都是线程之间进行协作的手段
obj.wait(); 让object监视器的线程等待
obj.notify(); 让object上正在等待的线程中挑一个唤醒
obj.notifyAll(); 让object上正在等待的线程全部唤醒
必须获得此对象的锁,才能调用这几个方法
package ogr.westos.Demo;
public class Demo9 {
public static void main(String[] args) {
Object obj = new Object();
new Thread(() -> {
synchronized (obj) {
System.out.println("thread-0线程执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-0其它代码....");
}
}).start();
new Thread(() -> {
synchronized (obj) {
System.out.println("thread-1线程执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-1其它代码....");
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唤醒obj上其它线程");
synchronized (obj) {
// obj.notify();
obj.notifyAll();
}
}
}
wait() 方法实际是会释放对象的锁,进入WaitSet等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify为止wait(long n) 有时限的等待, 到n毫秒后结束等待,或是被notify
sleep(long n), wait(long n)两者之间的区别:
1) sleep是Thread方法,而wait是Object的方法
2) sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用
3 ) sleep 在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放
8.线程的状态:
NEW(新建) 线程刚被创建,但是还没有调用 start方法
RUNNABLE(可运行) 当调用了start() 方法之后
BLOCKED(阻塞) 当线程进入了monitor监视器区,处于entrySet里准备竞争锁的时候,处于阻塞状态
WAITING(等待) 当调用了对象的wait方法,或调用了线程对象的join方法,进入了WaitSet,处于等待状态
TIMED_WAITING 当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态
当调用sleep(long n) 是让当前线程放弃cpu的时间片,睡眠一会
TERMINATED (终止)当线程代码运行结束.
9. java.util.concurrent.* java并发工具包
10.第三种创建线程的方式
public interface Callable<V>
与Runnable的区别,1)有返回结果,2)可以抛出检查异常