一、线程的创建
在Java中线程的创建一般分为两种方式:
- 继承Thread类创建
- 实现Runnable接口创建
1、继承Thread类
Java中的Thread类是java.lang包中的核心类,它代表一个线程。它主要用于创建新线程并在其中执行自定义任务。
Thread类的主要特点包括:
- 继承性:Thread类是java.lang.Object的子类,因此可以继承Object类的属性和方法。
- 抽象性:Thread类是一个抽象类,这意味着它不能直接实例化。为了使用Thread类,我们需要创建一个Thread的子类,并重写其run()方法。
- 线程安全性:Thread类是线程安全的,这意味着多个线程可以同时调用Thread类的方法,而不会导致数据不一致或其他并发问题。
- 常用方法:Thread类有许多有用的方法,包括start()(启动线程)、run()(执行线程)、sleep()(使线程休眠)、interrupt()(中断线程)等。
继承Thread类创建一个新的线程语法:
public class MyThread extends Thread {
@Override
public void run() {
// 在这里编写线程需要执行的代码
System.out.println("My thread is running.");
}
}
完成线程真正功能的代码放置在run方法中执行,该线程在执行完run方法中的代码后就会停止。
示例:
public class demo_1 {
public static void main(String[] args) {
/*
实现方式:1
自定义一个类继承Thread、或者构建Thread对象,重写run方法
重写run方法
启动线程
*/
MyThread t1=new MyThread();
MyThread t2=new MyThread();
// 为线程指定名字
t1.setName("线程一");
t2.setName("线程二");
t1.start(); // 开启线程
t2.start(); // 开启线程
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+ "芜湖");
}
}
}
2、实现Runnable接口
上一种方式创建线程的方式,是通过继承Thread类的方式创建的,但是Java是只支持单继承的语言,所以如果通过上一个方式创建线程的话,拓展性不太好。因此就可以使用实现接口的方式来创建线程。
实现过程:
- 自定义一个类实现Runnable接口
- 重写run方法,
- 创建自己类的对象,
- 创建Thread对象开启线程
示例:
package text_1;
public class demo_2 {
public static void main(String[] args) {
/*
* 第二种实现方式:
* 自定义一个类实现Runnable接口
* 重写run方法,
* 创建自己类的对象,
* 创建Thread对象开启线程
*
* */
MyRun mr=new MyRun();
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
t1.setName("芜湖");
t2.setName("呀呼");
t1.start();
t2.start();
}
}
class MyRun implements Runnable{
@Override
public void run() {
Thread thread = Thread.currentThread(); // 获取当前线程的对象
for (int i = 0; i < 10; i++) {
System.out.println(thread.getName()+ "爱坤");
}
}
}
只所以能通过这中方式创建线程,是因为Thread类就是Runnable接口的实现类。
且在Thread类中的构造方法中有Runnable的实例,使用这种构造方法就可以将Runnable实例与Thread实例相关联,也就是说,使用这种构造方法后,Thread类调用的run方法就是Runnable中的run方法。
二、线程的生命周期
三、操作线程的方法
操作线程的方法有很多,这些方法可以使得线程从某种状态过度到另一种状态。
1、线程的休眠
sleep方法
该方法使得当前线程在指定的时代内不会进入就绪状态。
该方法是被static修饰的,所以可以直接使用类名调用。
示例:
package text_2;
public class demo_1 {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
mt1.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
// 以毫秒为单位
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("芜湖");
}
}
}
2、线程的加入
join方法
在Java多线程编程中,join方法是一个非常重要的概念。它用于确保主线程等待其他线程完成其任务后再继续执行。当一个线程调用另一个线程的join方法时,调用线程会阻塞,直到被调用线程结束执行。
join方法通常在创建线程时使用,以确保主线程不会在子线程完成前结束。这有助于防止数据竞争和其他并发问题。
示例:
package text_2;
public class demo_1 {
public static void main(String[] args) throws InterruptedException {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
mt1.setName("wuhu");
mt2.setName("yahu");
mt1.start();
mt1.join();
mt2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 4; i++) {
try {
// 以毫秒为单位
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName());
}
}
}
3、线程的礼让
在Java中,线程礼让是指在线程A和线程B执行的时候,线程B由于某种原因需要先一步执行,那么可以对线程A执行yield方法,先让线程B执行一步。请注意,这里和join方法不一样,join方法是将CPU资源全都分出,直到线程B执行完,而yield只会让出一步。
4、线程的优先级
在Java中,线程的优先级是一个整数,范围从1(最低优先级)到10(最高优先级)。默认情况下,新创建的线程的优先级为5。线程优先级越小,线程越优先被执行;线程优先级越大,线程越后被执行。可以通过Thread类的setPriority(int)方法来设置线程的优先级。
请注意,如果优先级相同的线程同时存在,那么会按照提交顺序(也就是代码编写顺序)执行的方式。
package text_2;
public class demo_2 {
public static void main(String[] args) {
/*
* 设置优先级 :setPriority()
* 获取优先级 : get
* 守护线程 :setDaemon(boolean)
* 细节:守护线程会在所有非守护线程结束后结束
* */
// System.out.println(Thread.currentThread().getPriority());
MyThread_2 mt1 = new MyThread_2();
MyThread_2 mt2 = new MyThread_2();
mt1.setName("芜湖");
mt2.setName("呀呼");
System.out.println(mt1.getPriority()); // 获取当前优先级
mt1.setPriority(10);
System.out.println(mt1.getPriority()); // 获取当前优先级
// MyThread_3 mt3 = new MyThread_3();
// mt3.setName("run");
// mt3.setDaemon(true);
mt1.start();
mt2.start();
}
}
class MyThread_2 extends Thread{
@Override
public void run() {
Thread thread = Thread.currentThread();
for (int i = 0; i < 10; i++) {
System.out.println(thread.getName()+"i");
}
}
}
class MyThread_3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+""+i);
}
}
}
四、线程同步
Java线程同步是一个非常重要的概念,主要用于解决多线程并发控制问题。当多个线程同时操作一个可共享的资源变量时,可能会产生数据不准确和相互冲突的问题。为了解决这些问题,Java提供了多种同步机制,包括synchronized关键字、Lock接口和AtomicInteger类等。
其中,synchronized关键字是最基本的同步机制,可以用于方法或代码块的同步。当一个线程在执行一个synchronized方法时,其他试图访问该对象的线程将被阻塞,直到第一个线程执行完毕。这样可以确保同一时间只有一个线程可以访问共享资源,避免了数据不一致和程序异常的问题。
一般情况:窗口售票
package text_3;
public class demo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyThread extends Thread {
static int ticket = 0;
@Override
public void run() {
while (true) {
// 同步线程:线程锁(锁对象) 需要注意的是锁对象一定是要唯一的
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket < 100) {
++ticket;
System.out.println(getName() + "正在卖第" + ticket + "张票");
} else {
break;
}
}
}
}
运行结果:
通过运行结果,我们可以看出没有加线程同步的情况,多个线程对同一资源的访问,因为系统cpu轮转的情况,某一线程可能已经售出了某张票,但是另一个线程也在售出这张票,所以导致了重复售出的情况。
线程同步机制
线程同步机制是Java多线程编程中的重要概念,主要用于解决多线程并发控制问题。线程同步机制可以让多个线程按照一定的顺序执行,避免出现数据不一致和相互冲突的问题。Java中实现线程同步的方式有多种,包括synchronized关键字、Lock接口、信号量Semaphore、倒计时门闩CountDownLatch、循环栅栏CyclicBarrier和闭包等。
其中,synchronized关键字是最基本的线程同步机制之一,可以用于方法或代码块的同步。当一个线程在执行一个synchronized方法时,其他试图访问该对象的线程将被阻塞,直到第一个线程执行完毕。这样可以确保同一时间只有一个线程可以访问共享资源,避免了数据不一致和程序异常的问题。
需要注意的是,使用synchronized块进行同步线程时,同步代码块的对象一定需要是唯一的。
同步代码块改进
package text_3;
public class demo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyThread extends Thread {
static int ticket = 0;
@Override
public void run() {
while (true) {
// 同步线程:线程锁(锁对象) 需要注意的是锁对象一定是要唯一的
synchronized (MyThread.class) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket < 100) {
++ticket;
System.out.println(getName() + "正在卖第" + ticket + "张票");
} else {
break;
}
}
}
}
}
运行结果: 杜绝了重复票的情况