此博客为看B老师Java视频的部分记录
- 进程:正在执行中的程序(应用程序在内存中运行的那部分空间)
- 线程:进程中的一个执行单元,负责进程中的程序的运行,一个进程中至少要有一个线程。一个进程中可有多个线程,此应用程序也可称为多线程程序
- 系统启动了多线程,可以实现多部分程序同时执行,即 并发
- 多线程的使用可以合理使用cpu资源,如果线程过低会导致性能降低
- cpu处理程序时是通过快速切换完成的
- jvm启动后,必然有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在Java中称之为主线程
- 创建线程的两种方式
方法① 继承Thread类
- 定义一个类继承Thread
- 重写run方法
- 创建子类对象,即创建线程对象
- 调用start方法,开启线程让线程执行,同时还会使jvm去调用run方法
class Demo extends Thread
{
private String name;
Demo(String name)
{
this.name = name;
}
public void run()
{
for(int x=1; x<20; x++)
{
System.out.println("name="+name+"..."+x);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d1 = new Demo("小强");
Demo d2 = new Demo("旺财");
d2.start(); //开启d2线程
d1.run(); // 主线程负责
}
}
运行结果
- 调用run和start方法的区别:调用run方法不开启线程,仅是对象调用方法。调用start开启线程,并让jvm调用run方法在开启的线程中执行
方法② 实现Runnable接口
- 定义类实现Runnable接口,避免了继承Thread类的单继承局限性
- 覆盖接口中的run方法。将线程任务代码定义到run方法中
- 创建Thread类的对象,只有创建Thread类的对象才可以创建线程
- 将Runnable接口的子类对象作为参数传递给Thread类的构造函数。因为线程已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable的子类对象,所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务
- 调用Thread类的start方法开启线程
- 实现Runnable接口的方式,更加符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务
- 继承Thread类:线程对象与线程任务耦合在一起,一旦创建Thread类的子类对象,即是线程对象,又有线程任务
- 实现Runnable接口:将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦
class Demo implements Runnable
{
private String name;
Demo(String name)
{
this.name=name;
}
// 覆盖接口Runnable中的run方法
public void run()
{
for(int x=1;x<=20;x++)
{
System.out.println("name="+Thread.currentThread().getName()+"..."+x);
}
}
}
class ThreadDemo01
{
public static void main(String[] args)
{
// 创建Thread类的对象(它不是线程对象)
Demo d = new Demo("Demo");
// 创建Thread类的对象,将Runnable接口的子类对象作为参数传递给Thread类
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
//启动线程
t1.start();
t2.start();
}
}
运行结果
将Runnable接口的子类对象作为参数传递给Thread构造函数的原因(Java源码)
class Thread
{
private Runnable target;
Thread(Runnable target)
{
this.target=target;
}
public void run()
{
if (target != null)
{
target.run();
}
}
public void start()
{
run();
}
}
Runnable d = new Demo();
Thread t = new Thread(d);
t.start();
- 继承Thread类,因为Thread类描述线程事物,具备线程应该有的功能
- 创建线程的目的:为了建立单独的执行路径,让多部分代码实现同时执行。
- 线程任务:线程创建并执行需要执行的代码
- 主线程的任务定义在main函数中,自定义线程需要执行的任务都定义在run方法中。Thread类中的run方法内部的任务并不是我们所需要的,只要重写此run方法,因Thread类中已经定义了线程任务的位置,只要在位置中定义任务代码即可,所以进行了重写run方法
-
多线程执行时,在栈内存中,每一个执行线程都有一片自己所属的栈内存空间,进行方法的压栈和弹栈
-
当执行线程任务结束时,线程自动在栈内存中释放,当所有执行线程都结束时,进程结束
-
内存图
-
获取线程名称
Thread.currentThread() //获取当前线程对象
Thread.currentThread().getName() //获取线程名称
- 主线程名称为main,自定义线程名称为:Thread-n(n=0,1,2,3…)
- 多线程时,一个线程异常,其他线程不受影响
-
多线程状态图:(其中stop()方法已过时)
-
多线程简单小示例:模拟售票
class Ticket implements Runnable
{
// 票的数量
private int tickets=100;
// 售票,被多线程执行。多线程代码,定义在run方法中
// 线程结构中通常都有循环结构
public void run()
{
while(true)
{
if(tickets>0)
{
//打印线程名称
System.out.println(Thread.currentThread().getName()+"..."+tickets--);
}
}
}
}
class ThreadDemo3
{
public static void main(String[] args)
{
// 创建Runnable接口对子类对象
Ticket t = new Ticket();
// 创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
// 开启四个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果
在上述代码中,修改原来的代码为
try{Thread.sleep(10);}catch(InterruptedException e){/*暂时忽略处理方式*/ }
售票过程中发生了线程安全问题,出现了错误数据:0,-1,-2,
产生原因:
1. 线程任务中在操作共享的数据
2. 线程任务操作共享数据的代码有多条(运算有多个)
解决思路:执行过程中,不让其他线程参与运算,通过同步代码块synchronized完成,格式:
synchronized(对象)
{
//需要被同步的代码
}
修改后部分截图:
- 同步代码块好处:解决了多线程安全性问题
- 同步代码块弊端:降低了程序的性能
- 同步前提:必须保证多个线程在同步中使用的是同一个锁
- 当多线程安全问题发生时,加入了同步后,问题依旧,就要通过这个同步的前提来判断同步是否写正确
- 同步函数
- 同步函数使用的锁是this
- 同步函数和同步代码块的区别:
- 同步函数使用的锁是固定的this。当线程任务只需要一个同步时完全可以使用同步函数
- 同步代码块使用的锁可以是任意对象。当线程任务中需要多个同步时,必须通过锁来区分,这是必须使用同步代码块
- 同步代码块较为常用
public synchronized void demo() //同步函数,使用的锁对象为this
{
...
}
-
注:static同步函数,使用的锁不是this,而是字节码文件对象(表示方法:类名.class)
要实现同步,则将字节码文件对象传入同步代码块
-
单例设计模式并发访问
- 饿汉式,多线程并发没问题
- 懒汉式
- 饿汉式,多线程并发没问题
- 当多线程任务中出现多个同步(多个锁)时,如果同步中嵌套了其他的同步,这时容易引发 死锁 的现象
示例代码
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized(MyLock.LOCKA)
{
System.out.println(Thread.currentThread().getName()+"...if...locka");
synchronized(MyLock.LOCKB)
{
System.out.println(Thread.currentThread().getName()+"...if...lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.LOCKB)
{
System.out.println(Thread.currentThread().getName()+"...else......lockb");
synchronized(MyLock.LOCKA)
{
System.out.println(Thread.currentThread().getName()+"...else......locka");
}
}
}
}
}
}
// 定义一个用于存储锁对象类
class MyLock
{
public static final Object LOCKA = new Object();
public static final Object LOCKB = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
//创建两个线程任务
Test t1 = new Test(true);
Test t2 = new Test(false);
Thread t11 = new Thread(t1);
Thread t22 = new Thread(t2);
t11.start();
t22.start();
}
}
运行结果
上一篇:Java基础记录(三)
下一篇:Java基础记录(五)