多线程
进程:1. 进程是系统运行程序的基本单位。2. 每一个进程都有自己独立的一块内存空间、一组系统资源。3. 每一个进程的内部数据和状态都是完全独立的。
线程:线程是进程中执行运算的最小单位,可完成一个独立的顺序控制流程。
多线程:一个进程中同时运行多个线程来完成不同的工作。优点:充分利用CPU资源,简化编程膜性,带来良好用户体验。
线程生命周期
创建——就绪——运行——阻塞——死亡
刚new出来时创建状态
调用start()就绪状态
当就绪状态的线程获得CPU资源时,即可转入运行状态,执行run()方法。一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态
一个线程的run()方法运行完毕,则进入死亡状态。处于死亡状态的线程不具有继续运行的能力。
主线程
public class TestMainThread {
public static void main(String[] args) {
//获取当前线程
Thread mainThread = Thread.currentThread();
//获取线程名字
String mainName = mainThread.getName();
System.out.println("线程名字是: " + mainName);
//设置线程名字
mainThread.setName("MyMain");
String newMainName = mainThread.getName();
System.out.println("修改后线程名字是: " + newMainName);
}
}
线程创建方式
1.继承Thread类重写run()方法
2.实现Runnable接口,重写run()方法
3.实现 Callable 接口,重写 call 方法,这种方式可以通过 FutureTask 获取任务执行的返回值。
//继承Thread类重写run()方法
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
//测试
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
//实现Runnable接口,重写run()方法
public class MyRannable implements Runnable{
@Override
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
//测试
public static void main(String[] args) {
MyRannable myRannable = new MyRannable();
//把创建好的对象传到Thread构造方法中
Thread thread = new Thread(myRannable);
thread.start();
//使用有参构造创建线程
Thread thread2 = new Thread(myRannable,"newName");
thread2.start();
}
}
//实现 Callable 接口,重写 call 方法
public class CallableTest implements Callable<Integer> {
@Override
public Integer call(){
return 10;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTest callableTest = new CallableTest();
FutureTask<Integer> futureTask = new FutureTask<Integer>(callableTest);
Thread thread = new Thread(futureTask);
thread.start();
//判断线程是否执行完毕,返回一个boolean值
futureTask.isDone();
//获取线程执行完毕后的返回值
System.out.println(futureTask.get());
}
}
start()和run()区别
线程实例调用start()方法和调用run()方法有着天壤之别,前者是启动线程,后者是调用实例方法。
线程调度
简介:线程调度是指按照特定机制为多个线程分配CU的使用权。
//join()
public static void main(String[] args) {
Join join = new Join();
Thread thread1 = new Thread(join,"线程一");
thread1.start();
try {
//阻塞其他线程,等待自己执行完后,其他线程再执行
thread1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//setPriority() ,getPriority()
Thread thread1 = new Thread(join,"first");
//设置线程优先,参数1-10之间
thread1.setPriority(10);
thread1.getPriority();
}
//sleep()
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i+"你好"+Thread.currentThread().getName());
if(i == 10) {
try {
//线程休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//yield()
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i+"你好"+Thread.currentThread().getName());
if(i == 10) {
try {
//线程礼让,释放资源立刻参与争抢
Thread.yield();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
/*线程通讯 wait()notify()
这两个方法是Object类中的方法,用于线程的等待与唤醒,必须搭配synchronized锁使用
wait()
wait()让当前执行代码的线程进行等待并主动释放锁,当满足一定条件才会被唤醒重新尝试获取锁
唤醒wait()?
1.持有相同对象锁的线程通过对象调用notify()/notifyAll()方法
2.wait()等待超时,需要提前设置超时时间wait(1000)
wait()和sleep区别?wait()会释放锁,sleep()不会,wait()来自Object,sleep来自Thread
notify()
1.notify()唤醒处于等待中的线程,唤醒与被唤醒线程所使用的锁对象必须是同一个。
2.执行notify()方法后,线程不会马上释放锁对象而是等到该线程的方法执行完,退出同步代码块才会释放锁
3.notify()只能随机唤醒一个处于wait()状态的线程,可以使用notifyAll()唤醒所有处于wait()状态的线程*/
//下面两个类示范使用wait()notify()
//一个类M
public class W extends Thread {
static Object lock = new Object();
@Override
public void run() {
synchronized (lock) {
while (true) {
System.out.println("a");
try {
lock.notify();
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
//一个类N
public class N extends Thread {
@Override
public void run() {
synchronized (W.lock){
while (true){
System.out.println("b");
try {
W.lock.notify();
W.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
//测试
public class WandN {
public static void main(String[] args) {
W w = new W();
N n = new N();
w.start();
n.start();
}
}
锁
synchronized
可以用在代码块也能用在方法上
//用在代码块上
public class Gift implements Runnable{
private static int gif = 0;//送出数量
private static int count = 100;//剩余数量
@Override
public void run() {
synchronized (this){
sent();
}
}
public void sent(){
while(true) {
if(count > 10){
gif++;
count--;
System.out.println(Thread.currentThread().getName() + "送出了第"+gif+"份礼物剩余"+count+"份");
}else {
return;
}
}
}
//用在方法上
public class Gift implements Runnable{
private static int gif = 0;//送出数量
private static int count = 100;//剩余数量
@Override
public void run() {
sent();
}
public synchronized void sent(){
while(true) {
if(count > 10){
gif++;
count--;
System.out.println(Thread.currentThread().getName() + "送出了第"+gif+"份礼物剩余"+count+"份");
}else {
return;
}
}
}
Lock
相比于synchronized更加灵活
public class LockThread extends Thread{
@Override
public void run() {
//创建锁
ReentrantLock lock = new ReentrantLock();
lock.lock();//上锁
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
lock.unlock();
}
}
lock.unlock();//解锁
}
}
AtomicInteger线程安全的类
AtomicInteger 类是 Java 并发包 (java.util.concurrent.atomic) 中的一个类,用于提供一个整型值的封装,并支持原子操作。它是一个线程安全的类,可以让我们在不需要显式锁的情况下更新整数值。AtomicInteger 内部使用了底层的原子操作来保证其方法的线程安全性。
主要特点
1.线程安全:AtomicInteger 提供的方法都是线程安全的,可以在多个线程之间共享实例而不必担心数据竞争问题。
2.原子性:它的操作如 get()、set()、incrementAndGet()、decrementAndGet() 等都是原子操作,意味着这些操作要么全部完成,要么全部不完成,不会被其他线程中断。
常用方法
get(): 返回当前的整数值。
set(int newValue): 设置当前整数值。
getAndIncrement(): 原子性地将当前值加1,并返回原来的值。
incrementAndGet(): 原子性地将当前值加1,并返回更新后的值。
getAndDecrement(): 原子性地将当前值减1,并返回原来的值。
decrementAndGet(): 原子性地将当前值减1,并返回更新后的值。
compareAndSet(int expect, int update): 如果当前值等于预期值,则以原子方式将该值设置为给定的更新值,并返回 true;否则返回 false。
使用场景
当需要在多线程环境下对某个计数器进行原子性的增减操作时。
需要在不使用锁的情况下实现线程安全的计数器。
当需要实现简单的同步控制逻辑,例如信号量或栅栏等。