进程:
应用程序的执行实例
有独立的内存空间和系统资源
线程:
CPU调度和分派的基本单位
进程中执行运算的最小单位,可完成一个独立的顺序控制流程
什么是多线程:
- 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”
- 多个线程交替占用CPU资源,而非真正的并行执行
多线程的好处:
- 充分利用CPU的资源
- 简化编程模型
- 带来良好的用户体验
在JAVA中实现多线程编程:
Thread类:
Java提供了java.lang.Thread类支持多线程编程
主线程:
- main()方法即为主线程入口
- 产生其他子线程的线程
- 必须最后完成执行,因为它执行各种关闭动作
public static void main(String args[]) {
Thread t= Thread.currentThread();
t.setName("MyJavaThread");
System.out.println("当前线程名是: "+t.getName());
Thread.currentThread():是获得主线程对象
.setName(……):设置线程名
.getName():获取线程名
在Java中创建线程的两种方式:
一、继承java.lang.Thread类
- 定义MyThread类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
例如:
public class MyThread extends Thread{
//重写run()方法
public void run(){
for(int i=1;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}}}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); }
extends Thread:继承Thread类
run():run()方法中编写线程执行的代码
thread.start():启动线程调用了父类的start方法
多个线程交替执行,不是真正的“并行”
线程每次执行时长由分配的CPU时间片长度决定
例如:
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
启动线程是否可以直接调用run()方法?
不可以!
二、实现java.lang.Runnable接口
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
例如:
public class MyRunnable implements Runnable {
public void run() {
for(int i=1;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}}}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.start(); //启动线程}
implements Runnable:实现Runnable接口
run():run()方法中编写线程执行的代码
myRunnable:创建线程对象
两种创建线程方式的优缺点:
- 继承Thread类
编写简单,可直接操作线程
适用于单继承
- 实现Runnable接口
避免单继承局限性
便于共享资源
所以:推荐使用实现Runnable接口方式创建线程
总结:
一、创建线程有哪2种方式?
- 继承Thread类
- 实现Runnable接口
二、启动线程
start()方法
三、线程对象调用start()方法和调用run()方法的区别
- run():只有主线程一条执行路径
- start():多条执行路径,主线程和子线程并行交替执行
线程的状态:
线程调度:
线程调度指按照特定机制为多个线程分配CPU的使用权
方法 |
说明 |
setPriority(int newPriority) |
更改线程的优先级(1-10之间的整数) |
static void sleep(long millis) |
在指定的毫秒数内让当前正在执行的线程休眠 |
void join() |
等待该线程终止 |
static void yield() |
暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() |
中断线程 |
boolean isAlive() |
测试线程是否处于活动状态 |
线程的优先级:
- 线程优先级由1~10表示,1最低,默认优先级为5
- 优先级高的线程获得CPU资源的概率较大
例如:
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread(),"线程A");
Thread t2 = new Thread(new MyThread(),"线程B");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
}}
Thread t1 = new Thread(new MyThread(),"线程A");:创建线程对象并指定线程名
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);:
两个线程对象分别设置为最高优先级和最低优先级
线程的休眠:
- 让线程暂时睡眠:指定时长,线程进入阻塞状态
- 睡眠时间过后线程会再进入可运行状态
【millis为休眠时长,以毫秒为单位】
【调用sleep()方法需处理InterruptedException异常】
例如:
public class Wait {
public static void bySec(long s) {
for (int i = 0; i < s; i++) {
System.out.println(i + 1 + "秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}}}}
public static void main(String[] args) {
System.out.println("*****主线程开始休眠*****");
Wait.bysec(5); //让主线程休眠5秒
System.out.println("*****主线程休眠结束*****"); }
Thread.sleep(1000):线程休眠1秒
线程的强制运行:
使当前线程暂停执行,等待其他线程结束后再继续执行本线程
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
【millis:以毫秒为单位的等待时长】
【nanos:要等待的附加纳秒时长】
【需处理InterruptedException异常】
例如:
public static void main(String[] args) {
Thread temp = new Thread(new MyThread());
temp.start();
for(int i=0;i<20;i++){
if(i==5){
try {
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"运行:"+i); } }
temp.join():阻塞主线程,子线程强制执行
线程的礼让:
- 暂停当前线程,允许其他具有相同优先级的线程获得运行机会
- 该线程处于就绪状态,不转为阻塞状态
public static void yield()
【只是提供一种可能,但是不能保证一定会实现礼让】
例如:
public class MyThread implements Runnable{
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().
getName()+"正在运行:"+i);
if(i==3){
System.out.print("线程礼让:");
Thread.yield(); } } }}
public static void main(String[] args) {
MyThread my = new MyThread();
Thread t1 = new Thread(my,"线程A");
Thread t2 = new Thread(my,"线程B");
t1.start();
t2.start();}
Thread.yield():当i=3时,当前线程礼让
使用线程的步骤:
- 定义线程
- 创建线程对象
- 启动线程
- 终止线程
总结:
- 线程的五个状态
创建、就绪、阻塞、运行、死亡
- 线程调度的方法
setPriority(int grade)
sleep(long millis)
Join()
yield()
当多个线程操作同一共享资源时,一个线程未完成全部操作的时候,其他线程修改的数据,造成数据不安全问题
例如:
public void run(){
while(true){
//省略代码:判断是否余票
num++;
count--;
try {
Thread.sleep(500); //模拟网络延时
} catch (InterruptedException e) {//…}
System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余"+count+"张票!"); }}
num++;
count--; :第一步:修改网站数据(出票号和剩余票数)
System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余"+count+"张票!"):第二步:显示出票信息
线程同步:
方法一:
使用synchronized修饰的方法控制对类成员变量的访问
访问修饰符 synchronized 返回类型 方法名(参数列表){……}
或者
synchronized 访问修饰符 返回类型 方法名(参数列表){……}
【synchronized就是为当前的线程声明一个锁】
例如:(修改之前的抢票)
// 同步方法:售票
public synchronized void sale() {
if (count <= 0) {
flag = true;
return;
}
//1、修改数据(剩余票数,抢到第几张票)
count--;
num++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2、显示信息,反馈用户抢到第几张票
System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余"+count+"张票"); } }
public void run() {
//循环当剩余票数为0时结束
while(!flag){
sale();
}
方法二:
使用synchronized关键字修饰的代码块
synchronized(syncObject){ //需要同步的代码}
【syncObject为需同步的对象,通常为this】
【效果与同步方法相同】
例如:(修改之前的抢票)
public void run() {
//循环当剩余票数为0时结束
while(true){
synchronized(this){
//同步方法,实现售票
if(count<=0){
break;
}
//1、修改数据(剩余票数,抢到第几张票)
count--;
num++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2、显示信息,反馈用户抢到第几张票
System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余"+count+"张票"); } } } }
多个并发线程访问同一资源的同步代码块时:
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
- 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
查看ArrayList类的add()方法定义:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true; }
ensureCapacityInternal(size + 1):集合扩容,确保能新增数据
elementData[size++] = e:在新增位置存放数据
ArrayList类的add()方法为非同步方法
当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题
【ArrayList为非线程安全的类型】
方法是否同步 |
效率比较 |
适合场景 |
|
线程安全 |
是 |
低 |
多线程并发共享资源 |
非线程安全 |
否 |
高 |
单线程 |
Hashtable && HashMap
- Hashtable
- 继承关系【实现了Map接口,Hashtable继承Dictionary类】
- 线程安全,效率较低
- 键和值都不允许为null
- HashMap
- 继承关系【实现了Map接口,继承AbstractMap类】
- 非线程安全,效率较高
- 键和值都允许为null
StringBuffer && StringBuilder
前者线程安全,后者非线程安全
死锁:
死锁产生的原因:
两个线程都在等待对方先完成 ,造成程序的停滞
例如:
//模拟死锁
public class Test1 {
public static void main(String[] args) {
Object bobby = new Object();
Object duck = new Object();
//糖糖和豆豆拿到的是同一个bobby和同一个duck
Thread Tangtang = new Thread(new Tangtang(bobby,duck));
Thread Doudou = new Thread(new Doudou(bobby,duck));
Tangtang.start();
Doudou.start();
}
}
//糖糖的构造和run方法
class Tangtang implements Runnable{
Object bobby;//芭比
Object duck;//玩具鸭
//通过测试类传入bobby和duck的参数
public Tangtang(Object bobby, Object duck) {
this.bobby = bobby;
this.duck = duck;
}
public void run() {
synchronized(bobby){ //用synchronized锁定bobby
synchronized(duck){ //在用synchronized锁定bobby的情况下还想锁定duck
}
System.out.println("糖糖把芭比给豆豆玩!");
}
}
}
//糖糖的构造和run方法
class Doudou implements Runnable{
Object bobby;
Object duck;
//通过测试类传入bobby和duck的参数
public Doudou(Object bobby, Object duck) {
this.bobby = bobby;
this.duck = duck;
}
public void run() {
synchronized(duck){ //用synchronized锁定duck
synchronized(bobby){ ////在用synchronized锁定duck的情况下还想锁定bobby
}
System.out.println("豆豆把玩具鸭给糖糖玩玩!");
}
}
}
死锁的条件:
- 两个或两个以上的线程共享同一份资源
- 某个线程拿到一个锁以后,还想拿第二个锁(但是这个锁已经被其他人独占了),造成锁的嵌套
避免
- 当前线程先释放自己的锁,使得代码可以继续运行。
- 尽量减少同步方法或者同步代码块的嵌套,避免死锁
总结: