并发:指两个或多个事件在同一时间段内发生。
并行:指两个或多个事件在同一时刻发生。
进程:指一个内存中运行的应用程序,每个进程都有独立的内存空间,一个应用程序可以运行多个进程。
线程:线程是进程中的一个执行单元。
小结:
线程的作用是提供一个轻量级执行单元——虽比进程小,但仍能执行任何java代码。一般情况下,对操作系统来说,一个线程是一个完成的执行单元,但仍属于一个进程,进程的地址空间在组成该进程的所有线程之间共享。也就是说,每个线程都可以独立调度,而且有自己的栈和程序计数器,但会和同个进程中的其他线程共享内存和对象。
线程调度
- 分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
- 抢占式调度:优先级高的线程使用CPU,如果线程的优先级相同,则随机选择一个(线程随机性),java的使用为抢占式调度
创建线程
创建多线程程序的第一种方式:
创建Thread类的子类
java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
实现步骤:
1.创建一个Thread类的子类
2.在Thread类中的子类重写run方法
3.创建Thread类的子类对象
4.调用Thread类中的方法start,开启线程,执行run方法
(多次启动一个线程是非法的,特别是当线程已经结束后,不能再重新启动)
(java的线程使用为抢占式调度)
package demo1;
public class MyThread01 extends Thread{
public void run() {
for(int i=0;i<5;i++) {
System.out.println("thread:"+i);
}
}
}
package demo1;
public class myDemo12 {
public static void main(String[] args) throws Exception {
MyThread01 myThread = new MyThread01();
myThread.start(); //开辟新的栈空间,执行run方法
for(int i=5;i<10;i++) {
System.out.println("main:"+i);
}
}
}
运行结果:
main:5
thread:0
main:6
thread:1
main:7
thread:2
main:8
thread:3
main:9
thread:4
或者是:
使用匿名内部类
Thread thread = new Thread() {
public void run() {
for(int i=5;i<10;i++) {
System.out.println("main:"+i);
}
}
};
thread.start();
Thread常用方法
setName(Sting name); //设置线程的名字
getName(); //获取线程的名字
sleep(long ms); //暂定执行线程
join(); //让此线程强制执行,其他线程无法运行,必须等待此线程完成之后才可以继续执行
interrupt(); //当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态
setPriority(Thread.MIN_PRIORITY); //设置线程的优先级为最低(NORM_PRIORITY中等,MAX_PRIORITY最高)
【注意】并非优先级越高就一定会先执行,哪个线程先执行将由 CPU 的调度决定
yield(); //线程礼让,将一个线程的操作暂时让给其他线程执行
wait(); //线程等待,属于Object类,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法
创建多线程程序的第二种方式:
实现Runnable接口
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类对象的start方法
package demo1;
public class ImpRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++) {
System.out.println("thread:"+i);
}
}
}
package demo1;
public class myDemo12 {
public static void main(String[] args) throws Exception {
ImpRunnable imp = new ImpRunnable();
Thread run = new Thread(imp);
run.start();
}
}
或者使用匿名内部类
new Thread(new Runnable() {
public void run() {
for(int i=5;i<10;i++) {
System.out.println("main:"+i);
}
}
}).start();
或者用Lambda表达式替代匿名内部类
new Thread(()->{
for(int i=5;i<10;i++){
System.out.println("main:"+i);
}
}).start();
使用Runnable接口的好处:
1.避免了单继承的局限性
2.增强程序的扩展性,降低了程序的耦合性(解耦)
(设置线程任务与开启线程进行隔离)
提示:
可以通过重载线程类的构造函数来传入参数,以便run方法使用
-
同步与死锁
参考:https://www.cnblogs.com/java1024/archive/2019/11/28/11950129.html
一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。
解决方法:
同步代码块
synchronized(同步对象){
需要同步的代码
}
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
synchronized(this){ // 要对当前对象进行同步
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
}
}
};
public class SyncDemo02{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
运行结果:
卖票:ticket = 5;
卖票:ticket = 4;
卖票:ticket = 3;
卖票:ticket = 2;
卖票:ticket = 1;
同步方法
除了可以将需要的代码设置成同步代码块外,也可以使用 synchronized 关键字将一个方法声明为同步方法。
synchronized 方法返回值 方法名称(参数列表){
}
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
this.sale() ; // 调用同步方法
}
}
public synchronized void sale(){ // 声明同步方法
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
};
public class SyncDemo03{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
运行结果:
卖票:ticket = 5;
卖票:ticket = 4;
卖票:ticket = 3;
卖票:ticket = 2;
卖票:ticket = 1;
死锁
所谓死锁,就是两个线程都在等待对方先完成,造成程序的停滞,一般程序的死锁都是在程序运行时出现的。
参考:java技术手册