Java线程基础学习笔记
Java线程
进程和线程
-
进程是指运行中的程序
进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自身的产生、存在和消亡的过程
-
线程是由进程创建的,是进程的一个实体。一个进程可以拥有多个线程
并发与并行
单线程与多线程
- 单线程:同一时刻,只允许执行一个线程
- 多线程:同一个时刻,可以执行多个线程
并发
- 同一时刻,多个任务交替执行。比如单核CPU实现的多任务就是并发
并行
- 同一时刻,多个任务同时执行。比如多核CPU可以实现并行
线程基本使用
创建线程有两种方式:
继承Thread类,重写run方法
package thread_.createThread;
/**
* 通过继承Thread类创建一个线程
*
* 1.开启一个线程,该线程每隔1秒输出一句话
* 2.当输出10次之后结束线程
*/
public class Thread01_ extends Thread{
/**
* 执行程序就启动了一个进程 同时开启一个main线程
* 在main线程中 调用start方法的时候 又开启了一个子线程(Thread-0)
*/
public static void main(String[] args) {
//创建一个Cat对象 可以当做线程使用
Cat cat = new Cat();
cat.start();//启动线程 启动过程中会调用run方法
//当main线程启动一个子线程Thread-0 主线程不会阻塞 会继续执行
//此时主线程与子线程交替执行
for (int i = 0; i < 100; i++) {
System.out.println("主线程线程:"+Thread.currentThread().getName()+i);
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//1.当一个类继承了Thread 该类就可以当做线程使用
//2.会重写run方法 实现自己的业务代码 而Thread中的run方法是来自Runnable接口
class Cat extends Thread{
@Override
public void run() {
int times = 0;
while (true){
//业务代码
System.out.println("子线程:"+Thread.currentThread().getName()+(++times));
//让线程休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 8){
break;//退出循环之后 线程也退出
}
}
}
}
-
为什么要调用start方法
实现Runnable接口,重写run方法
package thread_.createThread;
/**
* 通过实现Runnable接口来创建线程
*/
public class Thread02_ {
public static void main(String[] args) {
Dog dog = new Dog();
dog.run(); //不能直接调用run方法 因为没有开启一个线程
//dog.start(); //也不能调用start
Thread thread = new Thread(dog); //底层使用了设计模式——静态代理
thread.start(); //在这里调用run方法
}
}
class Dog implements Runnable{
@Override
public void run() {
int count = 0;
while (true){
System.out.println("hello!:"+Thread.currentThread().getName()+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10){
break;
}
}
}
}
代理过程
//模拟一个最简单的Thread
class ThreadProxy implements Runnable{
private Runnable target = null;//初始化一个Runnable对象
@Override
public void run() {
if (target != null){
target.run(); //如果target不为空,就去调用run方法
}
}
public ThreadProxy(Runnable target){
this.target = target; //将传进来的target进行赋值
}
public void start(){ //最终应该去引用start方法
start0();
}
private void start0() {
run(); //最终实现了一个多线程
}
}
多个子线程
package thread_.createThread;
/**
* 在main线程启动两个子线程
*/
public class Thread03_ {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
}
}
class T1 implements Runnable{
@Override
public void run() {
int count = 0;
while (true){
System.out.println("Hello!:"+Thread.currentThread().getName()+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10){
break;
}
}
}
}
class T2 implements Runnable{
@Override
public void run() {
int count = 0;
while (true){
System.out.println("World!:"+Thread.currentThread().getName()+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5){
break;
}
}
}
}
从本质上来看,继承Thread和实现Runnable来创建线程并没有什么区别,但是通过实现Runnable接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
多线程售票系统
继承Thread方式
package thread_.ticket;
/**
* 使用多线程,模拟三个窗口同时售票
* 三个窗口售票100张
*/
public class SellTicket {
public static void main(String[] args) {
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket02 sellTicket02 = new SellTicket02();
SellTicket03 sellTicket03 = new SellTicket03();
sellTicket01.start();
sellTicket02.start();
sellTicket03.start();
//会出现线程超卖问题
}
}
//继承Thread方式
class SellTicket01 extends Thread{
private static Integer ticketNum = 100;//让多个线程共享num
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票结束...");
break;
}
//休眠50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 售出一张票" +
",剩余票数"+(--ticketNum));
}
}
}
class SellTicket02 extends Thread{
private static Integer ticketNum = 100;//让多个线程共享num
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票结束...");
break;
}
//休眠50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 售出一张票" +
",剩余票数"+(--ticketNum));
}
}
}
class SellTicket03 extends Thread{
private static Integer ticketNum = 100;//让多个线程共享num
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票结束...");
break;
}
//休眠50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 售出一张票" +
",剩余票数"+(--ticketNum));
}
}
}
以上代码出现了超卖问题
实现Runnable接口
package thread_.ticket;
public class SellTicket02_ {
public static void main(String[] args) {
SellTicket2_ ticket2 = new SellTicket2_();
new Thread(ticket2).start();
new Thread(ticket2).start();
new Thread(ticket2).start();
}
}
class SellTicket2_ implements Runnable{
private Integer ticketNum = 100;//让多个线程共享num
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票结束...");
break;
}
//休眠50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 售出一张票" +
",剩余票数"+(--ticketNum));
}
}
}
以上代码也出现了超卖问题
线程终止
通知线程退出
- 当线程完成任务之后,会自动退出
- 可以使用变量控制run方法退出的方式来停止线程,即通知方式
package thread_.exit_;
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
AThread aThread = new AThread();
aThread.start();
for (int i = 0; i < 60; i++) {
Thread.sleep(50);//main线程休眠
System.out.println("线程"+Thread.currentThread().getName()+i);
if (i == 20){
Thread.sleep(10000);
System.out.println("=========================休眠中===========================");
aThread.setLoop(false);
}
}
}
}
class AThread extends Thread {
private Boolean loop = true;
@Override
public void run() {
while (loop){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+Thread.currentThread().getName()+"运行中...");
}
}
public void setLoop(Boolean loop) {
this.loop = loop;
}
}
线程常用方法
- setName 设置线程名称
- getName 返回线程名称
- start 使线程开始执行
- run 调用线程对象run的方法
- setPriority 更改线程优先级
- getPriority 获取线程优先级
- sleep 在指定时间内让线程休眠
- interrupt 终端线程
线程中断
中断线程一般用于正在休眠的线程
线程插队
- yield:线程礼让。让出CPU,给其他线程执行
- join:线程插队。插队成功之后,插入的线程先执行完所有的任务
package thread_.exit_;
public class ThreadJoin_ {
public static void main(String[] args) throws InterruptedException {
JoinThread thread = new JoinThread();
thread.start();
for (int i = 0; i < 20; i++) {
Thread.sleep(1000);
System.out.println("主线程执行:"+i);
if (i == 5){
thread.join();
}
}
}
}
class JoinThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行"+i);
}
}
}
守护线程
用户线程
守护线程
一般为工作线程服务,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制
package thread_.exit_;
/**
* 守护线程
* 当主线程结束 子线程还在执行
* 实现:只要主线程结束 即使子线程无限执行 也要结束
*/
public class ThreadGuard {
public static void main(String[] args) throws InterruptedException {
SonThread sonThread = new SonThread();
//如果希望 当主线程结束 子线程也自动结束
//只需要将子线程设置成守护线程
sonThread.setDaemon(true);
sonThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程在执行:"+i);
Thread.sleep(1000);
}
}
}
class SonThread extends Thread{
int num = 0;
@Override
public void run() {
for (; ; ) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程执行:"+(++num));
}
}
}
线程七大状态
线程生命周期
-
NEW
尚未启动的线程
-
RUNNABLE
可运行状态
-
BLOCKED
被阻塞等待监视器锁定的线程
-
WAITING
正在等待另一个线程执行特定动作的线程
-
TIME_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程
-
TERMINATED
已退出的线程
线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就是用同步访问技术,保证数据在任何一个时刻,最多只有一个线程访问,以保证数据的完整性
- 线程同步 :当有一个线程正在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,知道该线程完成操作,其他线程才能对该内存地址进行操作
package thread_.ticket;
public class SellTicket02_ {
public static void main(String[] args) {
SellTicket2_ ticket2 = new SellTicket2_();
new Thread(ticket2).start();
new Thread(ticket2).start();
new Thread(ticket2).start();
}
}
//使用同步方法synchronized
class SellTicket2_ implements Runnable{
private Integer ticketNum = 10000000; //让多个线程共享num
public synchronized void sell (){ //在同一个时刻 只能有一个线程来执行run方法
if (ticketNum <= 0){
System.out.println("售票结束...");
return;
}
//休眠50ms
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 售出一张票" +
",剩余票数"+(--ticketNum));
}
@Override
public synchronized void run() {
while (true){
sell();
}
}
}
互斥锁
- 在Java中引入互斥锁,用来保证数据操作的完整性
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象
- 关键字synchonrized来与对象的互斥锁联系。当某个对象用synchonrized来修饰的时候,表明该对象上锁了
- 局限性: 程序执行效率降低了
- 同步方法的锁(非静态)可以使this,也可以是其他对象
- 同步方法的锁(静态)为当前类对象
线程死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁
释放锁
以下操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方嘎,当前线程暂停,并释放锁
以下操作不会释放锁
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。尽量避免使用suspent()和resume()方法来控制线程