线程
目标
● 程序,进程,线程
● 创建线程
● Thread类中方法
● 线程状态
● 多线程的概念
● 线程同步
● Lock
● 线程通信
● 新增创建线程方式
程序,进程,线程
● 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码.
● 进程((process):正在内存中运行的应用程序,如运行中的QQ,运行中的音乐播放器。进程是操作系统进行资源分配的最小单位.
● 线程(thread):进程可进一步细化为线程,是一个进程内部的最小执行单元,是操作系统进行任务调度的最小单元,隶属于进程.
● 一个进程可以包含多个线程,
● 一个线程只能属于一个进程,线程不能脱离进程而独立运行;
● 每一个进程至少包含一个线程(称为主线程);
● 在主线程中可以创建并启动其它的线程;
● 一个进程内的所有线程共享该进程的内存资源。
创建线程
● 继承Thread类的方式
● 继承Thread类的方式
● 在Java中要实现线程,最简单的方式就是扩展Thread类,重写其中的run方法,方法原型如下:
● Thread类中的run方法本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行run方法。
线程中要执行的任务都要写在run()中,或在run()中调用;
public class MyThread extends Thread{ @Override public void run() { for (int i=0;i<1000;i++){ System.out.println("MyThread:"+i); } } } ************************** public static void main(String[] args) { MyThread myThread = new MyThread(); //myThread.run();仅仅调用方法 myThread.start();//线程 for(int i=0;i<1000;i++){ System.out.println("main:"+i); } } }
● 实现Runnable接口的方式
● java.lang.Runnable接口中仅仅只有一个抽象方法:
public void run()
● 也可以通过实现Runnable接口的方式来实现线程,只需要实现其中的run方法即
可;
● Runnable接口的存在主要是为了解决Java中不允许多继承的问题。
//实现Runnable接口的方式 //定义: //只先创建线程要执行的任务,创建一个类,实现Runnable接口,重写执行任务的run(); public class MyTask implements Runnable{ @Override public void run() { } } public class Mythreadtest { public static void main(String[] args) { //创建任务 MyTask myTask = new MyTask (); //创建一个线程作为外壳,将myTask包起来,然后执行任务 Thread thread = new Thread(myTask); thread.start(); } }
实现Runnable接口方式的好处
(1)避免了单继承的局限性
(2)多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
Thread类中方法
sleep();让程序阻塞休眠指定时间
join();等待调用了join()线程执行完毕吗,其他线程在执行
yield();主动礼让,退出CPU
run();//用来定义线程要执行的任务代码
start();//启动线程
currentThread();//获取当前线程
get();//获取线程Id
setname();//为线程设置名字
getState();//获取线程状态
getPriority();获取线程优先级
setPriority();//设置线程优先级 优先级为1~10,默认为5
getname();//获取线程名字
-
start()
: 启动线程并执行线程的run方法。 -
run()
: 线程的运行方法,包含线程的执行逻辑。 -
sleep(long millis)
: 使当前线程休眠指定的毫秒数。 -
yield()
: 暂停当前正在执行的线程,让出CPU执行权给同等优先级的线程。 -
join()
: 等待该线程执行完毕,在当前线程中调用其他线程的join方法,会让当前线程等待该线程执行完毕。 -
interrupt()
: 中断线程,给线程发送中断信号。 -
isInterrupted()
: 判断线程是否被中断。 -
wait()
: 在对象上等待,线程进入等待状态,直到其他线程调用notify()或notifyAll()方法唤醒它。 -
notify()
: 唤醒在该对象上等待的一个线程。 -
notifyAll()
: 唤醒在该对象上等待的所有线程。 -
setPriority(int priority)
: 设置线程的优先级,优先级范围为1到10,数字越大,优先级越高。 -
isAlive()
: 判断线程是否还存活。 -
yield()
: 暂停当前线程执行,让出CPU执行权给其他线程。 -
wait(long timeout)
: 在对象上等待指定的时间。 -
join(long millis)
: 等待线程执行指定的毫秒数。 -
stop()
: 已经废弃的方法,强制停止线程的执行,不建议使用。
线程状态
以下是Java线程的几种状态:
-
新建(New):使用new关键字创建线程对象,但还没有执行start()方法。
-
运行(Runnable):线程被start()方法启动后,线程处于可执行状态。
-
阻塞(Blocked):线程被阻塞,等待某个条件满足或者获得内部锁。方法包括sleep()、wait()、join()。
-
等待(Waiting):线程处于等待状态,等待其他线程的通知或者调用notify()、notifyAll()方法。方法包括wait()、join()。
-
超时等待(Timed Waiting):线程等待一定时间后自动返回。方法包括sleep(long millis)、wait(long timeout)、join(long millis)。
-
终止(Terminated):线程执行完毕或者发生未捕获的异常。
线程状态之间的转换如下:
-
新建 -> 运行:调用start()方法
-
运行 -> 阻塞:调用sleep()、wait()、join()方法
-
运行 -> 等待:调用wait()、join()方法
-
运行 -> 超时等待:调用sleep(long millis)、wait(long timeout)、join(long millis)方法
-
阻塞、等待 -> 运行:被唤醒或者获得锁
-
运行 -> 终止:run()方法执行完毕,线程终止
-
阻塞 -> 终止:调用stop()方法或者interrupt()方法
-
等待 -> 终止:调用stop()方法或者interrupt()方法
-
超时等待 -> 终止:等待时间到或者调用interrupt()方法
多线程的概念
多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务
何时需要多线程
● 程序需要同时执行两个或多个任务。
● 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
多线程的优点
● 提高程序的响应.
● 提高CPU的利用率.
● 改善程序结构,将复杂任务分为多个线程,独立运行
多线程的缺点
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
多线程需要协调和管理,所以需要跟踪管理线程,使得cpu开销变大;
线程之间同时对共享资源的访问会相互影响,如果不加以控制会导致数据
出错
线程同步
● 多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,
即各线程间要有先来后到;
同步就是排队+锁:
● 几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作;
● 为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
● 确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个
线程获取了这把锁,才有权利访问该共享资源。
● 在Java代码中实现同步:
使用synchronized(同步锁)关键字同步方法或代码块。
synchronized (同步锁对象){ // 需要被同步的代码; }
同步锁对象作用:同步锁对象的作用是协调多个线程对共享资源的访问,确保同一时刻只有一个线程可以访问共享资源,避免出现数据竞争和不一致性。
/用来记录有没有线程进入到同步代码块,如果有,那么其他线程就不能进入同步代码块,直到上一个线程执行完同步代码块的内容,释放锁之后,其他线程才能进入
同步锁对象的要求:
-
锁对象必须是一个对象,可以是任意对象,例如可以使用内置的对象(如Object)或自定义的对象作为锁。
-
多个线程必须使用同一把锁对象来对共享资源进行加锁和解锁操作。
-
加锁和解锁操作必须保证原子性,即一个线程在访问共享资源时必须先锁定该对象,完成操作后再释放锁定。
-
使用关键字synchronized或Lock类来对共享资源进行同步操作,确保线程安全。
synchronized方法声明
synchronized还可以放在方法声明中,表示整个方法,为同步方法。
例如:
public synchronized void show (String name){ // 需要被同步的代码; }
在Java中,使用synchronized
关键字修饰一个方法可以保证在同一时间只有一个线程可以进入该方法,从而确保方法内部的代码是线程安全的。当一个线程进入了被synchronized
修饰的方法时,其他线程需要等待该线程执行完毕才能进入该方法。
示例:
public synchronized void synchronizedMethod() { // 线程安全的代码块 }
在上面的示例中,当一个线程调用syncronizedMethod
方法时,其他线程将会被阻塞直到第一个线程执行完毕。这样可以避免多个线程同时访问该方法导致的线 程安全问题。
在使用synchronized
修饰方法时,需要注意以下几点:
-
死锁:当多个线程互相等待对方释放锁而导致所有线程都无法继续执行的情况称为死锁。因此,在设计并发程序时要避免出现死锁情况,合理规划使用
synchronized
关键字。 -
性能问题:
synchronized
关键字会带来性能开销,因为每次执行同步方法时都需要获取锁,而且只能有一个线程执行。对于性能要求比较高的场景,可以考虑使用更轻量级的锁机制如ReentrantLock
。 -
可重入性:Java中的
synchronized
关键字是可重入的,即一个线程可以再次进入它已经拥有的锁,而不会被阻塞。因此,在编写方法时要特别注意递归调用时可能出现的死锁情况。 -
对象锁:使用
synchronized
修饰方法时,锁的粒度是整个对象实例。如果在同一个对象实例上有多个同步方法,那么它们之间会相互竞争对象锁,可能影响程序效率。因此,尽量避免在一个对象上有过多的同步方法。
总之,在使用synchronized
修饰方法时要谨慎处理,避免出现死锁、性能问题和其他潜在的并发安全问题。最好是结合其他并发控制技朮来实现更复杂、更高效的并发处理。
sleep()和wait()区别
-
sleep()是Thread类的静态方法,
而wait()是Object类的方法,只能在同步代码块中使用。
-
sleep()方法是让当前线程休眠一段时间,不会释放锁,而wait()方法是让当前线程进入等待状态并释放锁,直到被唤醒。
-
sleep()方法无法被中断,必须等到指定的时间过去才能继续执行,
而wait()方法可以被唤醒或者等待一定的时间。
-
sleep()方法的执行不会影响监视器(锁),
而wait()方法执行会释放监视器(锁)。
使用ReentrantLock类需要先创建一个ReentrantLock对象,然后在需要对共享资源进行加锁和解锁的地方使用lock()和unlock()方法。以下是一个简单的示例代码:
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { Thread thread1 = new Thread(new MyRunnable()); Thread thread2 = new Thread(new MyRunnable()); thread1.start(); thread2.start(); } static class MyRunnable implements Runnable { @Override public void run() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + " acquired the lock"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " released the lock"); } } } }
在上面的代码中,我们创建了一个ReentrantLock对象,并在MyRunnable的run方法中使用lock()和unlock()对共享资源进行加锁和解锁。两个线程分别执行MyRunnable的run方法,在获得锁后输出线程名并休眠1秒钟,然后释放锁。
除了lock()和unlock()方法外,还可以使用tryLock()方法尝试非阻塞地获取锁,使用lockInterruptibly()方法支持锁的中断,使用newCondition()方法创建Condition对象进行线程协作等。
通过使用ReentrantLock类,我们可以更好地控制线程的并发访问共享资源,避免出现竞态条件和死锁等问题。
ReentrantLock类实现了Lock,
它拥有与synchronized相同的并发性和内存
语义,在实现线程安全的控制中,可以显式加锁释放锁
ReentrantLock类和synchronized关键字都用于实现多线程同步,但有一些重要的区别:
-
ReentrantLock类是Java.util.concurrent包中的一部分,而synchronized是Java语言提供的内置关键字。ReentrantLock类提供了更灵活的同步控制功能。
-
ReentrantLock类可以实现公平锁和非公平锁,而synchronized关键字只能实现非公平锁。
-
ReentrantLock类提供了更多的方法来处理获取锁失败的情况,如tryLock()方法可以尝试获取锁而不会阻塞,lockInterruptibly()方法可以响应中断。
-
ReentrantLock类允许程序员进行条件等待和通知,而synchronized关键字要和Object类的wait()和notify()方法一起使用。
-
ReentrantLock类可以被另一个线程释放,而synchronized关键字只能被拥有锁的线程释放。
总的来说,ReentrantLock类在功能上比synchronized关键字更加灵活和强大,但使用也更加复杂。在大多数情况下,使用synchronized关键字已经能够满足同步的需求了。