文章目录
前言
线程的创建方式4种
线程的安全问题解决的方法3种:同步代码块,同步方法,Lock
基本概念
- 程序(program)
是为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象 - 进程(process)
正在运行的一个程序 - 线程(thread)
线程是进程的一部分,是指进程中的一个执行流程
下面的概念也了解一下
- 并行:多个cpu执行多个任务。例:多个人做不同的事情
- 并发:一个cpu执行多个任务。例:秒杀。多个人做同一件事
一个java程序至少有三个线程:
- 1、main()主程序线程
- 2、gc()垃圾回收线程
- 3、异常处理线程
创建线程方法一:继承Thread
下面是工程例子:
包含说明
package com.edu.create;
import static java.lang.Thread.*;
/***
* 创建线程的方式一:
*1、 继承Thread类
*2、 重写run()方法
*3、 start()启动线程并调用run()方法
*/
/**
* 常用方法:
* 1、run()运行过程
* 2、start()启动
* 3、currentThread()静态方法,返回当前线程
* 4、getName() 获得线程名称
* 5、setName() 设置线程名称
* 6、yield() 释放当前cpu的执行权,注:当前线程依然有抢占cpu的权利
* 7、join() 在线程a中使用b.join() 则a阻塞,直到b线程执行结束
* 8、stop() 已过时,强制结束线程
* 9、sleep() 睡眠,使线程睡眠
*/
/***
* 优先级:
* MAX_PRIORITY :10 最高级
* MIN_PRIORITY :1 最低级
* NORM_PRIORITY :5 默认优先级
* setPriority() 设置线程优先级
* getPriority() 获取线程优先级
*
* 注:设置线程优先级,例如:设置最高级。
* 并不意味着一定是这个线程最优先执行
* 这里还是有一定的概率问题,只能说提高线程抢占cpu的优先概率
*
*
*/
class MyThread extends Thread{
@Override
public void run() {
//Thread.currentThread().setName("线程1");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class CreateThread {
public static void main(String[] args) {
MyThread thread0 = new MyThread();//线程0
//线程启动
thread0.start();
//设置名称
thread0.setName("线程0");
//设置优先级
thread0.setPriority(MAX_PRIORITY);
//如果直接使用run,会不会报错?不会
//new MyThread().run();
//但是直接使用run(),并不是创建一个新线程,而只是运行它的方法而已
MyThread thread1 = new MyThread();//线程0
thread1.start();
thread1.setName("线程1");
//设置thread1线程优先级
thread1.setPriority(MIN_PRIORITY);
for (int i = 0; i < 10; i++) {
// if(i == 5){
// yield();
// //释放先前cpu
// //但是当前线程依然可以抢到cpu的执行权
// try {
// sleep(100);
// //使线程陷入睡眠100
// //配合上面党的yield使用,基本上可以切换当前cpu的执行权
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
创建线程的方式二:实现Runnable
下面例子工程:
package com.edu.create;
/***
* 创建线程方法2:Runnable
* 注:这个创建线程的步骤与方法一有所不同
*
* 不同1:实现Runnable接口--Runnable
* 不同2:
* MyThread2 myThread = new MyThread2();
* Thread t = new Thread(myThread);
* 需要将创建的对象,传入新建Thread对象的构造方法中
*/
class MyThread2 implements Runnable{
private int num = 10;
@Override
public void run() {
while (num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}
}
}
public class CreateThread2 {
public static void main(String[] args) {
MyThread2 myThread = new MyThread2();
Thread t = new Thread(myThread);
t.start();
//这里t1与t2线程共享同一个Runnable
Thread t2 = new Thread(myThread);
t2.start();
}
}
实现Callable接口
Class TestCallable implement Callable<Integer>{
@Override
public Integer call() {
return 0;
}
}
Callable 实现的线程需要通过线程池来实现
public class CreateThread3 {
public static void main(String[] args) {
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
TestCallable thread = new TestCallable ();
//执行线程
Future future = pool.submit(thread );
pool.showdown()
//获取返回值
try{
future .get();
}catch(Expection e){}
}
}
上面三种种创建线程的方式比较
-
在实际开发之中优先使用Runnable类
原因一:实现Runnable接口没有单一继承的局限性
原因二:实现的方式更适合用来处理共享数据 -
联系: Thread implements Runnable 都会实现 Runnable类
(Run() 方法是Runnable类的子方法,Thread 也是实现Runnable接口覆盖重写) -
Callable最大的区别在于他能接收返回值,也能抛出线程内异常
主要用来做线程的通信
线程生命周期
这个Thread源码中有自己的状态定义
查看源码状态定义步骤:
- crtl+鼠标点击 进入Thread类
- crtl+o 找到state方法
就会看到有六种状态:
- NEW :新建
- RUNNABL:运行
- BLOCKED:阻塞
- WAITING:等待
- TIMED_WAITING:有时间性的等待
- TERMINATED:终结
而实际上我们一般也只会说五种状态
下面借用一下尚硅谷老师的图,比较好理解
(就绪就是等待)
线程的同步
解决线程的安全问题(一):synchronized
当多个线程在操作一个共享数据是,容易发生线程的同步
而线程同步时,多个线程一起操作共享数据,就容易发生数据的错误
下面工程例子;
package com.edu.safe;
/***
* 线程安全问题
*
* 我们加入sleep看一下线程的输入
* 发现:num并没有按理想状态递减,出现一定的重复,错位
* 原因:在某个线程在操作共享数据(num)时,另一个线程也参与进来
* 解决途径:加锁
*
* 在java中我们通过同步机制来解决线程的安全问题
* 解决方式一:
* 同步代码块
* synchronized(同步监视器){
* //共享数据操作区
* }
* 说明 同步监视器:俗称,锁。任何一个类的对象都可以当作锁。
* 隐藏要求:需要同步的线程要公用一把锁。
*
* 同步方法
* private synchronized void 方法名(){} 注:private、void不是固定的
* 同步方法的锁是this
*
*
*
*/
class MyThread implements Runnable{
private int num = 10;
Object lock = new Object();
@Override
public void run() {
//同步代码块
// synchronized (lock) {
// while (num > 0) {
// //加入休眠
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + ":" + num);
// num--;
// }
// }
num();
}
//同步方法
private synchronized void num(){
while (num > 0) {
//加入休眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num);
num--;
}
}
}
public class ThreadSafe {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t = new Thread(myThread);
t.start();
//这里t1与t2线程共享同一个Runnable
Thread t2 = new Thread(myThread);
t2.start();
}
}
在没有解解决问题时:
出现num重复,错位,甚至=0
死锁
死锁定义:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁在操系统课程中有更加详细的定义,同时死锁也是一节大课,要想真正的了解死锁,我这里是写不完了,如果想要跟家了解死锁可以直接度娘
死锁需要学习的知识:
- 死锁产生的必要条件
- 处理死锁
- 预防死锁的方法
- 避免死锁
- 死锁的解除
- 还涉及到计算问题:多少个线程需要至少多少个资源才不会产生死锁
线程的安全问题二:Lock
lock() 加锁
unlock() 解锁
package com.edu.safe;
import java.util.concurrent.locks.ReentrantLock;
/***
* 解决线程的安全问题方法二:Lock
*/
public class ThreadSafe2 {
public static void main(String[] args) {
myThread thread = new myThread();
Thread t = new Thread(thread);
Thread t2 = new Thread(thread);
t.start();
t2.start();
}
}
class myThread implements Runnable{
private int num=100;
//声明锁
private ReentrantLock lock =new ReentrantLock();
@Override
public void run() {
//加锁
lock.lock();
while (num >=0){
System.out.println(Thread.currentThread().getName() + ":" + num);
num--;
}
//释放锁
lock.unlock();
}
}
synchronized 和lock 的区别
synchronized
- 执行完相应的同步代码后,会自动的释放监视器
- 可以有同步方法和同步代码块两种
- 隐式锁
lock
- 需要手动的加锁和解锁
- 只有同步代码块,没有同步方法
- 性能更好,更容易扩展
- 显式锁
线程阻塞
- wait(); 阻塞线程
- notify(); 唤醒一个线程
- notifyAll(); 唤醒多个线程
wait和sleep的区别
- sleep 不会释放锁
- wait只能用在同步块或者同步方法(synchronized)里面,他可以释放锁