线程相关知识
程序、进程、线程
创建多线程的方式
-
方式一:
package com.growingwang.Tread; /** * 测试Thread中的常用方法: * 1. start():启动当前线程,调用当前线程的run() * 2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 * 3. currentThread():静态方法,返回执行当前代码的线程 * 4. getName():获取当前线程的名字 * 5. setName():设置当前线程的名字 * 6. yield(): 释放当前CPU的执行权 * 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完成以后,线程a才结束阻塞状态 * 8. stop(): 已过时。强制结束当前线程 * 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态 * 10. isAlive(): 判断当前线程是否存活 */ class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+i); } if(i % 20 == 0){ yield(); } } } public MyThread(){ } public MyThread(String name){ super(name); } } public class ThreadMethodTest { public static void main(String[] args) { MyThread th1= new MyThread("Thread: 1"); //th1.setName("th1"); th1.start(); Thread.currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName()+":"+i); } if(i == 20){ try { th1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(th1.isAlive()); } }
-
方式二:
package com.growingwang.Tread;
/*
* 创建多线程方式2:实现Runnable接口
* 1. 创建一个实现Runnable接口的类
* 2. 实现类去实现Runnable的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
*/
class MThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class TestThread2 {
public static void main(String[] args) {
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
//通过Thread类的对象调用start():1.启动线程。2.调用当前线程的run()-->调用了Runnable类型的target的run()
t1.setName("线程1");
t1.start();
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
比较创建线程的两种方式
开发中:优先选择实现Runnable接口的类
原因:1. 实现的方式没有类的单继承的局限性
2.实现的额方式更适合来处理多个线程有共享数据的情况
方式三:实现Callable接口 --->JDK5.0新增
线程的生命周期
线程的同步
同步代码块
package com.growingwang.Tread;
/**
* 创建3个窗口卖票,总票数100张,使用Runnable接口的方式
*
* 1.问题:卖票过程中出现了重票、错票--->出现了线程安全问题
* 2.原因:当某个线程操作车票的过程中,尚未完成操作是,其它线程参与进来也操作车票
* 3.如何解决:当一个线程a在操作ticket的时候,其它线程不能参与进来,知道线程a操作完成
* ticket时,其它线程才可以开始操作ticket。这种情况即使a出现了阻塞,也不能被改变
* 4.在Java中,通过同步机制来解决线程的安全问题
*
* 方式一:同步代码块
*
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:1.操作共享数据的代码,即为需要被同步的代码
* 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
* 3.同步监视器:俗称:锁,任何一个类的对象,都可以充当锁
*
* 方式二:同步方法
* 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
*/
class Window implements Runnable{
private int ticket = 100;
//Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (this){//此时的this:唯一的Window的对象 //方式二:synchronized (obj){
if(ticket > 0){
//方式3:synchronized(Window.class){//Window只会加载一次
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":售票订单为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法
package com.growingwang.Tread;
class Window1 implements Runnable {
private int ticket = 100;
//Object obj = new Object();
@Override
public void run() {
while (true) {
show();
}
}
//方式二
private synchronized void show() {//同步监视器this
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票订单为:" + ticket);
ticket--;
}
}
}
public class Window1Test {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
总结:
- 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
- 非静态的同步方法(实现Runnable),同步监视器是:this
- 静态的同步方法(继承Thread),同步监视器是:当前类本身
Lock锁
package com.growingwang.Tread;
import java.util.concurrent.locks.ReentrantLock;
class Window2 implements Runnable{
private ReentrantLock lock = new ReentrantLock();
private int ticket = 100;
public void run(){
while(true){
try{
//调用lock()锁定方法
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":售票订单为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//调用解锁方法:unlock()
lock.unlock();
}
}
}
}
public class ThreadLock {
public static void main(String[] args) {
Window2 w = new Window2();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
Lock与Synchronized的异同
相同:二者都可以解决线程的安全问题
不同:Synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的气筒同步(lock()),同时结束同步也需要手动的实现(unlock())
优先使用顺序
Lock --->同步代码块(已经进入了方法体,分配了相应资源)---->同步方法(在方法体外)
停止线程
可以通过设置一个标志位flag来操作
class TestStop implements Runnable{
private boolean flag = true;
public void run(){
int i= 0;
while(flag){
System.out.println("run....Thread"+i++);
}
}
public void stop(){
this.flag = false;
}
}
public class StopThread{
public static void main(String[] args){
TestStop testStop = new TestStop();
new Thread(testStop).start();
for(int i=0;i<1000;i++){
if(i==900){
testStop.stop();
System.out.println("线程停止了");
}
}
}
}
线程的死锁
-
死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,形成了线程死锁
-
说明:
1) 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于死锁状态,无法继续
2) 使用同步时避免死锁
package com.growingwang.Tread;
public class ThreadLockTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}.start();
new Thread(){
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}.start();
}
}
- 解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
线程通信
使用两个线程交替打印1-100
package com.growingwang.Tread;
class Communicate implements Runnable{
private int number = 1;
public void run(){
while(true){
synchronized (this){
notify();//唤醒
if (number <= 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使用调用如下wait()方法的线程进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Communicate c = new Communicate();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
涉及到的方法:
- wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的
- notifyAll():旦执行此方法,就会唤醒被所有被wait的线程
注意:
- wait(), notify(), notifyAll()三个方法必须使用在同步代码块或同步方法中
- wait(), notify(), notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则,会出现IllegalMonitorStateException异常
private Object obj = new Object();
synchronized (obj){
this.notify();
...
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- wait(), notify(), notifyAll()三个方法都定义在Object类中
sleep()与wait()的区别
-
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
-
不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步 代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中, sleep()不会释放锁,wait()会释放锁
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能的影响很大
思路:提前创建好多线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁的创建、销毁,实现重复利用。
-
ExecutorService: 真正的线程池接口。
void execute(Runnable command):执行任务/命令,没有返回值
void shutdown(): 关闭连接池
- Excutors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
package com.growingwang.Tread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//1.开启服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MThread());
service.execute(new TestThread1());
service.execute(new MThread());
service.execute(new MThread());
//2.关闭链接
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义