一、多线程的概念
1.程序 :一个固定逻辑与数据的集合 就称为程序 例如淘宝 贪吃蛇小游戏
2.CPU: 中央处理器 主要用于协调程序与硬件进行配置的工作
3.并发与并行
1.并发(高并发)
在同一个时间段 执行两个或者多个任务 单核cpu 是进行相互切换 执行某一个任务 切换速度是非常快 很容易误解是同时执行的 其实是交替执行
2.并行
在同一时刻 执行两个或者是多个任务的时候 多核cpu是同时执行多个任务 同时执行 目前的电脑都是使用多核
例子: 可以听歌 敲代码 看视频
1.进程: 运行在内存中的程序 就是进程
2.线程:单线程 多线程
线程是进程中执行运算的最小单位,也称为轻量级进程
单线程 执行一条通向cpu 的执行的路径 就是单线程
多线程 有多条通向cpu的执行流程 就称为多线程
3.单线程
4.多线程 迅雷下载小电影(同时下载) 360 安全卫士 杀毒 清理 优化系统
5.Java中的main方法 主程序 就是单线程 主线程
二、第一种方式实现多线程(子类继承Thread)
1.步骤
(1)定义一个类 继承Thread
(2) 重写 run方法 ==> 线程执行的操作
(3)实例化这个类对象 创建线程
(4)调用start() 开启线程
2.代码
线程类
getClass()获取当前所属包名+类名,getName()获取属性名/变量名
package day21;
public class MyThread extends Thread{
@Override
public void run(){
//线程执行的操作
for (int i = 0; i < 100; i++) {
//getClass()获取当前所属包名+类名,getName()获取属性名/变量名
System.out.println(getName()+"\t"+i);
}
}
}
测试类
package day21;
public class Test01 {
public static void main(String[] args) {
//实例化线程对象
MyThread th = new MyThread();
//开启线程 控制台输出值是 Thread-0 0
th.start();
for ( int i=1;i<=100;i++){
//控制台输出值是 main 1
System.out.println(Thread.currentThread().getName()+"\t"+ i);
}
}
}
三、线程的调度方式
1.线程有两种调度分配方式: 分配时调度 抢占式调度
2.分配时调度:按照固定的时间来分配每个线程执行操作 多个线程执行操作的时间都是固定
3.抢占式调度 线程获取cpu的执行权越大 执行当前线程的优先级越大
任意一个线程抢到cpu的执行权 就执行当前线程的操作 Java中的多线程就是抢占式调度
4.代码
package day21;
public class Test01 {
public static void main(String[] args) {
//实例化线程对象
MyThread th = new MyThread();
//开启线程 控制台输出值是 Thread-0 0
th.start();
for ( int i=1;i<=100;i++){
//控制台输出值是 main 1
System.out.println(Thread.currentThread().getName()+"\t"+ i);
}
//实例化第二个对象 控制台输出值是 Thread-1 0
MyThread th1 = new MyThread();
th1.start();
}
}
5.原因的分析
线程谁抢到就执行谁
四、多线程的内存分析(start和run方法区别)
代码从上到下运行 ,当线程需要调用start()方法时,会新开辟一个栈内存,该方法进这个栈内存运行,每个线程都会开一个单独的栈内存
当线程调用run()方法时,不会新开辟一个栈,而是进main方法的所在栈,相当于普通对象调用方法
五、获取线程的名称
package day21;
public class Test01 {
public static void main(String[] args) {
//实例化线程对象
MyThread th = new MyThread();
//开启线程 控制台输出值是 Thread-0 0
th.start();
for ( int i=1;i<=100;i++){
//控制台输出值是 main 1
System.out.println(Thread.currentThread().getName()+"\t"+ i);
}
注意点:
默认获取到线程的名称:Thread-XXX ==>序号时从0开始
设置标题的名称
package day21;
public class MyThread extends Thread{
//无参构造
public MyThread() {
}
//有参构造
public MyThread(String name) {
super(name);
}
@Override
public void run(){
//线程执行的操作
for (int i = 0; i < 100; i++) {
//getClass()获取当前所属包名+类名,getName()获取属性名/变量名
System.out.println(getName()+"\t"+i);
//间隔1秒钟来打印即1000毫秒
/*try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
package day21;
public class Test02 {
public static void main(String[] args) {
//使用第一种方式 通过setName
MyThread th1 = new MyThread();
th1.setName("线程1");
th1.start();
//通过第二种方式来进行设置
MyThread th2 = new MyThread("线程2");
th2.start();
}
}
六、线程休眠
package day21;
public class MyThread extends Thread{
//无参构造
public MyThread() {
}
//有参构造
public MyThread(String name) {
super(name);
}
@Override
public void run(){
//线程执行的操作
for (int i = 0; i < 100; i++) {
//getClass()获取当前所属包名+类名,getName()获取属性名/变量名
System.out.println(getName()+"\t"+i);
//间隔1秒钟来打印即1000毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
七、守护线程
1.守护线程 也就是为其他线程做铺垫,守护其他线程完成操作 Java中gc 垃圾回收器 Java项目在运行的时候 会出现很多垃圾的对象 这个和守护线程 一致监听着 进行垃圾回收
2.守护线程的死亡
被守护的线程死亡 守护肯定会死亡
被守护的线程的操作的执行完成 守护线程也会随之死亡
一般守护的都是主线程
3.设置守护线程
package day21;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class MyThread1 extends Thread {
@Override
public void run(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
FileOutputStream fos = new FileOutputStream("1.txt");
fos.write(97);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package day21;
public class Test03 {
public static void main(String[] args) {
MyThread1 my = new MyThread1();
//需要将其设置为守护线程 守护的线程就是主的线程
//一定要再开启线程之前调用 否则没有效果
my.setDaemon(true);
System.out.println(my.isDaemon());
my.start();
}
}
八、设置线程的优先级
三个常量
注意点:
1.设置优先级的值越大 获取cpu的执行权越大 范围 1-10
2.优先级的值越大 并不一定会执行java 抢占式调度
理解 优先级 是更大概率抢到cpu 但概率是概率 不是一定
package day21;
public class Test04 {
public static void main(String[] args) {
MyThread th1 = new MyThread();
th1.setPriority(Thread.MAX_PRIORITY);
//开启线程
th1.start();
MyThread th2 = new MyThread();
th2.setPriority(8);
th2.start();
}
}
九、第二种方式实现多现线程(实现类连接Runnable接口)
步骤
1.定义一个类 这个类需要实现Runnable这个接口
2.实现其抽象方法 run() ==>执行线程操作
3.实例化线程Thread 对象 构造方法参数就是Runnable的实现类
4.开启线程 调用start()方法
注意点:
这一种方式底层也是使用第一种方式来实现了 这种方式就是为了解决线程 java单继承的问题
package day21;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
}
}
package day21;
public class Test05 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread th =new Thread(runnable);
th.start();
Thread th1 = new Thread(runnable);
th1.start();
}
}
十、伪第三种方式实现多线程(使用匿名内部类)
1.匿名没有名字 类部类就是定义在类中的类
2.匿名内部类的语法
new 抽象类| 或者是接口{
实现的方法
}
3.代码
new Thread(){public void run() }.start(); 这个是在方法参数后加大括号,在大括号里重写run(),实质是第一种方式的匿名写法
new Thread(new Runnable()).start(); 这个是在方法参数里new Runnable() 匿名类,在匿名类中重写run(),实质是第二种方式的匿名写法
package day21;
public class Test06 {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"\t"+"在执行操作");
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"\t"+"在执行操作");
}
}).start();
}
}
十一、线程之间的通信问题
1.案例
1.使用三个线程来购买电影票 必须实现接口 这种方式来实现
2.需要定义一个变量来记录其票数
3.使用循环来进行购买 需要判断是否存在票 每次购买完票之后 票数需要-1
public class MyMovieRunnable implements Runnable {
//定义一个变量来记录其总的票数
private int count =100;
@Override
public void run() {
//使用死循环 一直进行购买票
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断票是否还存在
if (count>0){
System.out.println(Thread.currentThread().getName()+"买了
第"+count+"张票");
// 需要进行递减
count--;
}
}
}
}
package day21;
public class Test07 {
public static void main(String[] args) {
MyMovieRunnable movieRunnable = new MyMovieRunnable();
Thread th1 = new Thread(movieRunnable);
th1.start();;
Thread th2 = new Thread(movieRunnable);
th2.start();
Thread th3 = new Thread(movieRunnable);
th3.start();
}
}
2.出现问题的原因
3.解决方法一
第一种方式:使用同步代码块
同步就是排队,一个一个来
1.同步代码块的语法:
synchronized (锁的对象){
出现问题的代码
}
2.注意点
(1)锁的对象可以是任意的对象,但要保证多线程数据安全,应该锁多线程共享的数据
(2)所有的线程都必须使用同一个锁的对象
3.作用:
保证多线程数据共享的安全性
4.改造代码
package day21;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyMovieRunnable implements Runnable {
//定义一个变量来记录其总的票数
private int count =100;
//定义一个锁的对象 多线程都共享obj对象
private Object obj = new Object();
@Override
public void run() {
while (true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
if(count>0){
System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
//需要递减
count--;
}
}
}
}
4.解决问题的原理
5.解决问题方法二
1.使用的是同步的方法 可以使用同步的普通方法 也可以使用同步的静态方法
2.同步方法的语法
访问修饰符 synchronized 访问值类型 方法的名称(参数列表) {
可以出现问题的代码
}
3.代码
package day21;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyMovieRunnable implements Runnable {
//定义一个静态变量来记录其总的票数
private static int count =100;
//定义一个锁的对象
private Object obj = new Object();
@Override
public void run() {
while (true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断票是否存在
showInfo();
}
}
private synchronized void showInfo() {
if(count>0){
System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
//需要递减
count--;
}
}
private static synchronized void showInfoStatic(){
if(count>0){
System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
//需要递减
count--;
}
}
4.注意点
普通方法的锁的对象就是当前对象 就是this
静态方法的 锁对象是当前对象的.class 对象
6.第三种解决方法
1.使用Lock锁对象 Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作
2.常用的实现类 ReentrantLock
3.常用的方法
package day21;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyMovieRunnable implements Runnable {
private int count =100;
Lock lock =new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
Thread.sleep(500);
if(count>0){
System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
//需要递减
count--;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
synchronized锁升级的原理
无锁,先偏向锁,在轻量级锁,在重量级锁,性能消耗越来越大,且锁升级顺序不可逆
原理:在锁对象的对象头里面有一个threadid字段,线程在第一次访问的时候threadid 为空,jvm 让其持有偏向锁,并将threadid 设置为其线程id ,再次访问的时候会先判断threadid 是否与其线程id一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,等待锁对象中,通过自选循环一定次数来获取锁,执行一定次数之后,还未正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了synchronized锁的升级。
作用:锁升级是为了减低锁带来的性能消耗。在Java6之后优化synchronized 的实现方式,使用了偏向级锁升级为轻量级锁再升级到重量级锁的方式,从而降低了锁带来的性能消耗。
十二、线程的死锁
1.死锁: A线程使用B的锁资源 B线程使用A锁资源 都不进行释放 相互等着 这就是死锁
2.例子:宿舍只有一双拖鞋 A.刘正武 B.康林 == >A B 都要出去买东西
A得到左角的拖鞋 B得到右脚的拖鞋 A B 都霸占锁资源都不释放 所以出现死锁 都不能出去
3.代码
鞋资源
package day21;
public class Shoes {
public static final String left="left";
public static final String right="right";
}
张三线程
package day21;
public class ZhangSanRunnable implements Runnable{
@Override
public void run() {
synchronized (Shoes.right){
System.out.println("张三获取右脚拖鞋");
/*try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
synchronized (Shoes.left){
System.out.println("张三获取左脚拖鞋");
}
}
}
}
李四线程
package day21;
public class LiSiRunnable implements Runnable{
@Override
public void run() {
synchronized (Shoes.left){
System.out.println("李四获取左脚拖鞋");
synchronized (Shoes.right){
System.out.println("李四获取右脚拖鞋");
}
}
}
}
测试类
package day21;
public class Test08 {
public static void main(String[] args) {
ZhangSanRunnable zhangSan = new ZhangSanRunnable();
Thread th1 = new Thread(zhangSan);
th1.start();
LiSiRunnable liSi = new LiSiRunnable();
Thread th2 = new Thread(liSi);
th2.start();
//如果线程先抢占cpu通道运行完两只鞋就能运行完
//a占了b锁资源,b占了a锁资源才会运行不完
}
}