一、概念
进程:进程是指正在运行的程序。确切的说,当一个程序进入内存中运行时即变成一个进程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行。一个进程至少有一个线程。
多线程:一个程序中多个线程同时进行。
所以,一个电脑的快慢决定于CPU和内存,因为软件都是在内存中运行的。
二,程序运行原理
l 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
l 抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
三、线程状态
四、如何创建线程
1,继承Thread类
1 定义一个类继承Thread。
2 重写run方法。(run方法里写的就是线程任务)
3 创建子类对象,就是创建线程对象。
4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。
2,实现Runnable接口并重写run方法,start()方法调用(run方法里写的就是线程任务)
1、定义类实现Runnable接口。
2、覆盖接口中的run方法。。
3、创建Thread类的对象
4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
5、调用Thread类的start方法开启线程。
3,实现Callable<v>接口并重写call()方法(和run方法功能一样,但该方法有返回值,且会抛异常)
获取线程名方法:Thread.currentThread().getName()
五、线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的 效率,因为频繁创建线程和销毁线程需要时间。
Executors:线程池创建工厂类
public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象
ExecutorService:线程池类
简单代码演示:
//创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
//创建Runnable实例对象
MyRunnable r = new MyRunnable();
//从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);//每submit一次获取个线程去执行任务
//再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
//关闭线程池
service.shutdown();
如果有兴趣的可以参考:https://blog.csdn.net/weixin_28760063/article/details/81266152
六、多线程的安全问题
1.1,多个线程运行同一段代码,共享一个资源,这是就会出现线程安全问题。
案例:电影院卖票,一共100张。假设有三种方式购买(美团、淘票票、现场排队),相当于三个线程共同操作这100张票,这时就会出现线程安全问题。
七,解决线程安全问题
2.1 线程同步(线程安全处理synchronized)
上图中使用了synchronized(obj)同步代码块,起锁对象可以使任意对象。也可以用同步方法
public synchronized void method(){
if (ticket > 0) {
//模拟选坐的操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
其所对象是this
还可以用静态方法,其锁对象是该类.class (属性)
System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
2.2使用Lock接口解决线程安全问题
public class Ticket implements Runnable {
//共100票
int ticket = 100;
//创建Lock锁对象
Lock ck = new ReentrantLock();
@Override
public void run() {
while(true){
//synchronized (lock){
//获取锁
ck.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
}
//释放锁
ck.unlock();
总结,该方式可以每次释放锁,而synchronized 方式只有出了同步方法才会释放,如果出异常catch后不会释放锁
七,死锁
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
死锁情况代码演示:
1 定义锁对象类
public class MyLock {
public static final Object lockA = new Object();
public static final Object lockB = new Object();
}
2 线程任务类
public class ThreadTask implements Runnable {
int x = new Random().nextInt(1);//0,1
//指定线程要执行的任务代码
@Override
public void run() {
while(true){
if (x%2 ==0) {
//情况一
synchronized (MyLock.lockA) {
System.out.println("if-LockA");
synchronized (MyLock.lockB) {
System.out.println("if-LockB");
System.out.println("if大口吃肉");
}
}
} else {
//情况二
synchronized (MyLock.lockB) {
System.out.println("else-LockB");
synchronized (MyLock.lockA) {
System.out.println("else-LockA");
System.out.println("else大口吃肉");
}
}
}
x++;
}
}
}
3 测试类
public class ThreadDemo {
public static void main(String[] args) {
//创建线程任务类对象
ThreadTask task = new ThreadTask();
//创建两个线程
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
//启动线程
t1.start();
t2.start();
}
}
八,线程常用方法
1 sleep():休眠一定时间后又开始抢夺CPU资源执行任务
2 wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。在没有notify()唤醒之前永久等待
3 notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
4 notifyAll():唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
注意:2,3,4这三个方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程(必须用锁对象去调用)。这三个方法并不定义在 Thread中,也没定义在Runnable接口中,却被定义在了Object类中,因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。