1.什么是进程和线程?
-
进程:是正在运行的程序
是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源
-
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
实现多线程的方式
方式一:继承Thread类
方法介绍
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
代码演示:
class MyThread extends Thread{
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
class TestMyThread {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
注意
1.继承Thread类需要重写ta的run方法,里面封装的是线程要执行的代码
2.如果直接调用run方法,相当于普通方法的调用
3.调用start方法,就是线程启动;然后由JVM调用此线程的run方法;
方式二:实现Runnable接口
Thread构造方法
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
Thread(Runnable target, String name) | 分配一个新的Thread对象 |
代码演示:
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
class RunThreadTest{
public static void main(String[] args) {
//先创建一个线程任务类,作为参数传入Thread的构造方法
MyRunnable myRunnable = new MyRunnable();
//创建线程类
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
//启动线程
thread1.start();
thread2.start();
}
}
方式三:通过线程池
线程池:
每次线程执行完任务以后,线程不会销毁,会放回线程池中,每次在执行任务的时候又会到线程池中去取线程。这样会提高效率。
解决了当程序中要创建大量生存期很短的线程,而造成的资源浪费这一问题,大大提高了操作性能。
方法介绍
方法名 | 说明 |
---|---|
newFixedThreadPool(int nThreads)` | 返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量) |
submit(Runnable task)` | 获取线程池中的某一个线程对象,并执行 |
拓展:
<T> Future<T> submit(Callable<T> task)
: 获取线程池中的某一个线程对象,并执行.
Callable和Runnable接口的区别:Runnable接口不会返回结果,并且无法抛出经过检查的异常
代码演示:
class MyRunnable2 implements Runnable{
@Override
public void run() {
int sum = 0;
for(int i=0; i<100; i++) {
sum+=i;
}
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for(int i=0; i<100; i++) {
sum+=i;
}
return sum;
}
}
class ThreadTest{
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建任务类对象
MyRunnable2 myRunnable = new MyRunnable2();
MyCallable myCallable = new MyCallable();
//获取线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
//执行任务
Future<?> runnable = executorService.submit(myRunnable);
Future callable = executorService.submit(myCallable);
//获取执行结果
// Object o1 = runnable.get();
Object o2 = callable.get();
// int i = (int)o1;抛出java.lang.NullPointerException异常,没有返回值
int y = (int)o2;
System.out.println("callable的返回结果"+y);
}
}
说明:
1.创建线程的方式有三种,但是只有一种启动方式。那就是Thread。线程池和Runnable,都是一种封装好的快捷方式。
2.因为java单继承的性质,所以推荐使用Runnable接口来实现线程。
3.Runnable接口,把线程代码和任务的代码分离,解耦合(解除线程代码和任务的代码模块之间的依赖关系)。代码的扩展性非常好;
线程优先级
线程调度
-
两种调度方式
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
-
Java使用的是抢占式调度模型
-
随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
优先级相关方法
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级 线程默认优先级是5;线程优先级的范围是:1-10 |
注意:线程优先级高不一定就先执行,只是执行调度分配到的资源更多,可能执行的几率更大。
代码演示:
class MyRunnable3 implements Runnable{
@Override
public void run() {
for(int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
class PriorityTest{
public static void main(String[] args) {
MyRunnable3 myRunnable3 = new MyRunnable3();
Thread t = new Thread(myRunnable3,"优先级为7");
Thread t2 = new Thread(myRunnable3,"优先级为3");
t.setPriority(7);
t2.setPriority(3);
t.start();
t2.start();
}
}
线程控制
相关方法
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
void join() | 等待这个线程执行完 |
void setDaemon(boolean on) | 将此线程标记为守护线程,为其他线程提供服务。当运行的线程都是守护线程时,Java虚拟机将退出 |
void yield() | 让线程回到就绪状态,直接等到cpu重新分配资源。但只有优先级和该线程相等或大于该线程的其他线程才有机会被执行 |
sleep()方法比yield()方法有更好的移植性,不建议使用yield();
代码演示:
sleep方法和join方法
class JoinTest implements Runnable{//该线程用来测试join方法
@Override
public void run() {
System.out.println("join方法,我执行完你再执行");
}
}
class JoinTest2 implements Runnable{
@Override
public void run() {
//创建任务类和线程
JoinTest join = new JoinTest();
Thread thread = new Thread(join);
for (int i = 0; i <10 ; i++) {
try {
//让线程休眠
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i == 5){
//当该执行到i==20,必须等thread线程执行完,才能重新执行
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(i);
}
}
}
}
class Test3{
public static void main(String[] args) {
JoinTest2 joinTest = new JoinTest2();
Thread t1 = new Thread(joinTest, "线程1号");
Thread t2 = new Thread(joinTest, "线程2号");
t1.start();
t2.start();
}
}
线程的生命周期
线程同步
当是多线程环境,有共享数据,有多条语句操作共享数据时,就可能会产生数据安全问题。
解决办法:可以人为的控制CPU在执行某个线程操作共享数据的时候,不让其他线程进入到操作共享数据的代码中去,这样就可以保证安全。这种方案称为线程的同步
1.同步代码块
格式:
synchronized( 需要一个任意的对象(锁))
{
代码块中放操作共享数据的代码。
}
注意:同步代码块上的锁,可以是随便任意的一个对象。但是必须是唯一的。
优点:解决了多线程的数据安全问题
缺点:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
2.同步方法
同步方法:就是把synchronized关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){
方法体;
}
同步方法的锁对象是 this
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步静态方法的锁对象是类名.class
3.Lock锁
上面两种方法,我们不能直接看到在哪里加上了锁,在哪里释放了锁。为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
代码演示
class LockTest implements Runnable{
int sum = 100;
//创建ReentrantLock对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//使用try-finally的方式,来保证锁一定会被释放
try {
//获取锁
lock.lock();
if (sum > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+sum);
sum--;
}
}finally {
lock.unlock();
}
}
}
}
public class demo3 {
public static void main(String[] args) {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(lockTest,"线程1号");
Thread thread2 = new Thread(lockTest,"线程2号");
Thread thread3 = new Thread(lockTest,"线程3号");
thread1.start();
thread2.start();
thread3.start();
}
}
死锁
死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象。
死锁在开发中,也会遇到,当线程进入到死锁状态时,程序中线程就会一直处于等待状态。
产生死锁必要条件:
- 互斥条件:一段时间内某资源只能被一个进程占有。此时其他资源如果想请求该资源,只能等待。
- 不可剥夺条件:进程所获得的资源在还没有使用完毕之前,不能被其他进程强行夺走,只能由自己来释放资源。
- 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求
案例:
class DeadLockRunnable implements Runnable {
private int flag;//决定线程走向的标记
//需要注意这两个对象一定要是两个实例共享的
//不然每次new的就会是两个独立对象
//因此需要把每一个Object对象设置为静态对象static
private static Object obj1 = new Object();//锁对象1
private static Object obj2 = new Object();//锁对象2
//调用传输过来的走向标记
public DeadLockRunnable(int flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag == 1){
//线程1执行代码:
synchronized (obj1){
System.out.println(Thread.currentThread().getName()
+"以获取到资源obj1,请求obj2");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//请求资源2
synchronized (obj2){
System.out.println(Thread.currentThread().getName()
+"以获取到资源obj1和obj2");
}
}
}else{
//线程2执行代码:
synchronized (obj2){
System.out.println(Thread.currentThread().getName()
+"以获取到资源obj2,请求obj1");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//请求资源1
synchronized (obj1){
System.out.println(Thread.currentThread().getName()
+"以获取到资源obj1和obj2");
}
}
}
}
}
class DeadLock {
public static void main(String[] args) {
//创建两个DeadLockRunnable实例:flag=1,flag=2
DeadLockRunnable DeadLock1 = new DeadLockRunnable(1);
DeadLockRunnable DeadLock2 = new DeadLockRunnable(2);
//创建两个线程执行两个DeadLockRunnable实例
Thread thread1 = new Thread(DeadLock1,"DeadLock1");
Thread thread2 = new Thread(DeadLock2,"DeadLock2");
//启动线程
thread1.start();
thread2.start();
}
}
解决办法:
只要破坏产生死锁的四个条件中的其中一个就可以了。
1、破坏互斥条件
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
2、破坏请求与保持条件
一次性申请所有的资源。
3、破坏不剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
4、破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
上面的案例:线程一等待线程二中的锁,而线程二又等待线程一中的锁。通过破坏这种循环条件,使它有序的访问资源就不会产生死锁了
修改线程二中的代码:
//线程2执行代码:
synchronized (obj1){
System.out.println(Thread.currentThread().getName()
+"以获取到资源obj2,请求obj1");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//请求资源1
synchronized (obj2){
System.out.println(Thread.currentThread().getName()
+"以获取到资源obj1和obj2");
}
}