进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径
一个进程可以有多个线程;
1. 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable) : 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join() 等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
继承 Thread 类、实现 Runnable 接口、实现Callable接口
//主线程
/*
*实现多线程,分别打印不同的数字
*/
public class Test {
public static void main(String[] args) {
//新建线程
Thread test=new Test1();
//启动线程
test.start();
//主线程执行 代码
for (int i = 0; i < 100; i++) {
System.out.println("主线程打印:"+i);
}
}
}
//多线程继承Thread类
class Test1 extends Thread{
//重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//Thread.currentThread().getName()获得当前线程的名字
System.out.println(Thread.currentThread().getName()+"线程打印:"+i);
}
}
}
//执行main方法后,得到的结果(仅复制部分结果,可以自己试一下)
/*
主线程打印:0
Thread-0线程打印:0
主线程打印:1
Thread-0线程打印:1
主线程打印:2
主线程打印:3
主线程打印:4
*/
注意:Runnable也需要通过Thread类启动线程
public class Test01 {
//主线程
public static void main(String[] args) {
//新建线程
Test2 test=new Test2();
//启动线程:Runnable不能直接.start执行,也需要通过Thread类启动线程
Thread t1=new Thread(test);
t1.start();
//主线程执行 代码
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"线程打印:"+i);
}
}
}
//多线程实现Runnable接口
class Test2 implements Runnable{
//重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"线程打印:"+i);
}
}
}
//执行main方法后,得到的结果(仅复制部分结果,可以自己试一下)
/*
main线程打印:0
main线程打印:1
main线程打印:2
main线程打印:3
main线程打印:4
main线程打印:5
main线程打印:6
Thread-0线程打印:0
main线程打印:7
Thread-0线程打印:1
main线程打印:8
main线程打印:9
*/
Callable和Runnable都是接口的形式实现多线程但是Callable可以有返回值,也可以抛出异常,与Thread和Runnable不同的是它并不是通过重写run()方法而是call()方法。
import java.util.concurrent.*;
public class Test02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test3 t3=new Test3();
ExecutorService executorService= Executors.newSingleThreadExecutor();
Future submit = executorService.submit(t3);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"线程打印"+i);
}
System.out.println(submit.get());
}
}
class Test3 implements Callable {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"线程打印"+i);
}
return Thread.currentThread().getName()+"线程打印结束";
}
}
//执行main方法后,得到的结果(仅复制部分结果,可以自己试一下)
/*
pool-1-thread-1线程打印99
main线程打印92
main线程打印93
main线程打印94
main线程打印95
main线程打印96
main线程打印97
main线程打印98
main线程打印99
pool-1-thread-1线程打印结束
*/
多个线程操作同一个变量,存在线程不安全问题,数据紊乱
public class Test03 {
public static void main(String[] args) {
Test4 test4 = new Test4();
new Thread(test4,"小刘").start();
new Thread(test4,"小网").start();
new Thread(test4,"小赵").start();
}
}
class Test4 implements Runnable{
private int tick = 20;
@Override
public void run() {
while (true){
if(tick<=0){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买了地"+tick--+"张票");
}
}
}
//运行结果:
/*
小网买了地19张票
小赵买了地18张票
小刘买了地20张票
小赵买了地17张票
小刘买了地16张票
小网买了地17张票
小网买了地15张票
小赵买了地14张票
小刘买了地13张票
小网买了地12张票
小赵买了地11张票
小刘买了地10张票
小赵买了地9张票
小刘买了地8张票
小网买了地9张票
小网买了地7张票
小刘买了地6张票
小赵买了地7张票
小赵买了地5张票
小网买了地4张票
小刘买了地5张票
小赵买了地3张票
小刘买了地2张票
小网买了地3张票
小网买了地1张票
小刘买了地0张票
小赵买了地1张票
*/
1. synchronized原理
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。
不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
2. synchronized基本规则
第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
@Override
public synchronized void run() {
while (true){
if(tick<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"买了地"+tick--+"张票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//通过synchronized修饰方法,或者synchronized代码块的方式,使线程安全
@Override
public void run() {
while (true){
if(tick<=0){
break;
}
synchronized (this){
System.out.println(Thread.currentThread().getName()+"买了地"+tick--+"张票");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//打印结果
/*
小刘买了地20张票
小赵买了地19张票
小网买了地18张票
小刘买了地17张票
小网买了地16张票
小赵买了地15张票
小网买了地14张票
小赵买了地13张票
小刘买了地12张票
小赵买了地11张票
小刘买了地10张票
小网买了地9张票
小赵买了地8张票
小刘买了地7张票
小网买了地6张票
小刘买了地5张票
小网买了地4张票
小赵买了地3张票
小刘买了地2张票
小网买了地1张票
*/
//注意:如果使用synchronized修饰run方法,其他线程访问该方法时就会被阻塞,所以该实例会只有第一个线程买票(建议自己试一下)
/*
死锁
同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。例如,现在张三想要李四的画,李四想要张三的书,张三对李四说“把你的画给我,我就给你书”,李四也对张三说“把你的书给我,我就给你画”两个人互相等对方先行动,就这么干等没有结果,这实际上就是死锁的概念。
所谓死锁,就是两个线程都在等待对方先完成,造成程序的停滞,一般程序的死锁都是在程序运行时出现的。
*/
线程的强制运行
在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
线程的休眠
在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep() 即可实现休眠。
中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。
线程的优先级
在 Java 的线程操作中,所有的线程在运行前都会保持在就绪状态,那么此时,哪个线程的优先级高,哪个线程就有可能会先被执行。线程.setPriority(1~10) ;Thread类中有三个定义好的常量
线程的礼让
在线程操作中,也可以使用 yield() 方法将一个线程的操作暂时让给其他线程执行
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕才行,虚拟机不用等待守护线程执行完毕;
可以用来声明日志,监控信息等用途;
声明守护线程的方式:线程.setDaemon(boolean);true为守护线程,也就是说这个线程里面是死循环也不会报错,当用户线程执行完毕,他也会结束执行;
public class Test {
public static void main(String[] args) {
//新建线程
Thread test=new Test1();
//启动线程
test.setDaemon(true);
test.start();
//主线程执行 代码
for (int i = 0; i < 100; i++) {
System.out.println("主线程打印:"+i);
}
}
}
//多线程继承Thread类
class Test1 extends Thread{
//重写run()方法
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"线程打印:");
}
}
}
//部分结果
/*
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
主线程打印:94
主线程打印:95
主线程打印:96
主线程打印:97
主线程打印:98
主线程打印:99
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Thread-0线程打印:
Process finished with exit code 0(运行结束)
*/
补充(ArrayList也是线程不安全的)可以网上了解一下
synchronized是隐式的同步锁;
还有一种显式的锁Lock对象锁;
ReentrantLock(可重入锁)类实现了Lock,可以显式加锁,释放锁;
public class Test03 {
public static void main(String[] args) {
Test4 test4 = new Test4();
new Thread(test4,"小刘").start();
new Thread(test4,"小网").start();
new Thread(test4,"小赵").start();
}
}
class Test4 implements Runnable{
private volatile int tick = 10;
private final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
if(tick<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"买了地"+tick--+"张票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//未加锁执行结果
/*
小网买了地10张票
小刘买了地8张票
小网买了地8张票
小赵买了地7张票
小赵买了地6张票
小网买了地4张票
小刘买了地5张票
小赵买了地2张票
小刘买了地1张票
小网买了地3张票
*/
//加锁后代码
class Test4 implements Runnable{
private volatile int tick = 10;
private final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
//加锁
lock.lock();
if(tick<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"买了地"+tick--+"张票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
//释放锁
lock.unlock();
}
}
}
}
//再执行
/*
小刘买了地10张票
小刘买了地9张票
小刘买了地8张票
小刘买了地7张票
小刘买了地6张票
小刘买了地5张票
小网买了地4张票
小网买了地3张票
小网买了地2张票
小网买了地1张票
*/
生产者消费者问题是多线程的一个经典问题,描述成一块缓冲区作储存箱,生产者可以将产品放入储存箱,消费者则可以从储存箱中取走产品。
解决生产者/消费者问题的方法
采用某种机制保护生产者和消费者之间的同步
wait() / notify()方法(最高效)
wait() / nofity()方法是基类Object的两个方法:
wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
ExecutorService线程池接口
Executors工具类,线程池的工厂类
public class TestPool {
public static void main(String[] args) {
//创建服务,创建线程池;参数是线程池大小
ExecutorService service= Executors.newFixedThreadPool(10);
//启动线程
service.execute(new Test5());
service.execute(new Test5());
service.execute(new Test5());
service.execute(new Test5());
//关闭连接
service.shutdown();
}
}
class Test5 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"打印了");
}
}
//执行结果
/*
pool-1-thread-2打印了
pool-1-thread-4打印了
pool-1-thread-1打印了
pool-1-thread-3打印了
*/
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有