1.线程的创建、使用
方法1: 继承Thread类的子类
/**
* 多线程创建
* 方式1:继承Thread类的子类
* 1.创建子类
* 2.重写Thread的run方法 ——》将此线程执行的操作声明在run()中
* 3.实例化该子类的对象
* 4.通过子类调用start方法
*
* @author shkstart
* @create 2022-11-02 下午 10:56
*/
//1.创建子类
class MyThread extends Thread {
//2.重写Thread的run方法
public void run() {
for(int i = 0; i < 100; i++){
if(i % 2 == 0)
System.out.println(i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.实例化该子类的对象
MyThread test = new MyThread();
//4.通过子类调用start方法
test.start();
}
}
start()的作用:
1.启动当前线程;2.调用当前线程的run();
注意:
- 不能直接通过子线程的对象调用run方法(此时还在main的线程中执行)
- 不能让已经调用start方法的线程再次调用start方法
方法2:创建实现Runnable接口的类
package com.coderleo.java;
/**
* 1.创建实现Runnable接口的类
* 2.实现类去实现Runnable中的run()
* 3.创建实现类的对象
* 4.将此对象作为参数,传递到Thread类的构造器,创建Thread类的对象
* 5.通过Thread类的对象调用start()
*
* @author shkstart
* @create 2022-11-03 下午 6:27
*/
public class ThreadTest2 {
public static void main(String[] args) {
//3.创建实现类的对象
Mthread test = new Mthread();
//4.将此对象作为参数,传递到Thread类的构造器,创建Thread类的对象
Thread test2 = new Thread(test);
//5.通过Thread类的对象调用start()
test2.run();
}
}
//1.创建实现Runnable接口的类
class Mthread implements Runnable{
//2.实现类去实现Runnable中的run()
public void run(){
System.out.println("这是实现类的run方法。。。");
}
}
两种方法的比较
- 实现类的方法(方法2)不存在单继承的局限性
- 方法2更适合于线程之间存在共享数据的情况
联系:
查看java源码可以发现:Thread类也实现了Runnable
因此Thread中的run()也重写了Runnable的run()
所以两种方法的本质都是重写Runnable接口中的抽象方法run()
Thread类中的常用方法:
- start()
- run()
- currentThread() :static方法,返回执行当前代码的线程
- getName() :获取当前线程的名称
- setName() :设置当前线程的名称
- yield() :释放当前CPU的执行权
- join() :在线程a中调用线程b.join() ,线程a进入阻塞状态,直至线程b执行完后线程a才结束阻塞。
- stop():强制结束线程声明,不建议使用
- sleep(long millitime) :让当前线程“睡眠”millitime毫秒,睡眠期间当前线程处于阻塞。
- isAlive() :判断当前线程是否存活
线程的调度:
线程的优先级(定义在Thread类中):
- MAX_PRIORITY:10
- MIN_PRIORITY:1️
- NORM_PRIORITY:5️(默认的优先级)
涉及的方法:
- getPriority():返回线程的优先级
- setPriority(int newPriority):改变线程的优先级
线程的优先级高,只是执行的概率更高,并不是高优先级的执行完后才能执行低优先级线程。
2.线程的生命周期
或者查看Thread类中的State枚举类
3.线程同步
同步机制1:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
/*
1.需要被同步的含义:操作共享数据的代码
2.同步监视器:又称锁。任何一个对象的类都可以充当锁。
但是必须保证:多个线程必须公用一把锁
*/
比如三个线程模拟卖票:
package com.coderLeo.java;
/**同步代码块的测试:三个窗口线程的卖票问题
* @author shkstart
* @create 2022-11-03 下午 8:14
*/
public class SynchronizationTest1 {
public static void main(String[] args) {
Window test = new Window();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
Thread t3 = new Thread(test);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
private int TICKETS = 100;
Clock clock = new Clock();
public void run() {
while (true){
synchronized (clock){
//加锁,任何一个类的对象都可以,但是要保证多个线程公用一把
//这里可以也可以考虑this充当锁。因为三个线程是共用Window类的test对象
if(TICKETS > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出票:" + TICKETS);
TICKETS--;
}
else
break;
}
}
}
}
//锁类
class Clock{
}
同样的方法,使用在继承类中:
package com.coderLeo.java;
/**
* @author shkstart
* @create 2022-11-03 下午 9:23
*/
public class SynchronizationTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window2 extends Thread{
private static int TICKETS = 100;
private static Object obj = new Object();
public void run() {
while (true) {
synchronized (obj){
//这里的锁也可考虑Window2.class(类的class也是对象,并且只加载一次,保证了唯一性)
if(TICKETS > 0){
try {
System.out.println(Thread.currentThread().getName() + "售票:" + TICKETS);
TICKETS--;
this.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else
break;
}
}
}
}
同步机制2:同步方法
//解决实现Runnable的同步问题
public class SynchronizationTest3 {
public static void main(String[] args) {
Window3 test = new Window3();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
Thread t3 = new Thread(test);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window3 implements Runnable {
private int TICKETS = 100;
public void run() {
while (true) {
//同步方法的执行
show();
if (TICKETS == 0)
break;
}
}
//直接将涉及到共享数据的代码定义成一个方法,将此方法上锁
public synchronized void show(){//此时的同步监视器:this
if (TICKETS > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出票:" + TICKETS);
TICKETS--;
}
}
}
//解决继承Thread的同步问题
public class SynchronizationTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window4 extends Thread{
private static int TICKETS = 100;
private static Object obj = new Object();
public void run() {
while (true) {
show();
if(TICKETS == 0)
break;
}
}
//注意此时一定要限制方法为static(看下面注释)
public static synchronized void show() {//此时的同步监视器:Window4.class
/*public synchronized void show()
如果模仿解决实现Runnable的方法直接定义show()是错的,因为此时t1\t2\t3有各自的show(),同步监视器是this(t1\t2\t3)
*/
if(TICKETS > 0){
try {
System.out.println(Thread.currentThread().getName() + "售票:" + TICKETS);
TICKETS--;
Thread.currentThread().sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同步方法的声明仍用到同步监视器,只是不需要显式地声明:
- 非static同步方法的同步监视器:this
- static同步方法的同步监视器:类本身(如上面的Window4.class)
同步机制3:Lock
jdk5新增的特性:显式锁
import java.util.concurrent.locks.ReentrantLock;
/**
* @author shkstart
* @create 2022-11-03 下午 11:23
*/
public class LockTest {
public static void main(String[] args) {
Window5 w1 = new Window5();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.start();
t2.start();
t3.start();
}
}
class Window5 implements Runnable {
private static int TICKETS = 100;
//实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();//参数true保证公平性(按阻塞的先后顺序?)
public void run() {
while (true) {
try {
//调用ReentrantLock对象的lock方法:上锁
lock.lock();
if(TICKETS > 0){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票:" + TICKETS + "号");
TICKETS--;
}
else
break;
}finally {
//解锁
lock.unlock();
}
}
}
}
synchronized与lock:
- synchronized方法是执行相应的同步代码后自动释放锁(隐式锁);有代码块锁和方法锁
- lock是手动地添加(lock())、释放锁(unlock());只有代码块锁。但是使用Lock锁,JVM花费更少的时间调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
对于线程同步方法的选择:
Lock——>同步代码块——>同步方法
4.线程通信
/**线程通信:
* 涉及到的方法
* 1.wait():执行wait后,当前线程被阻塞,同时释放同步监视器
* 2.notify():执行notify后,唤醒被wait的进程。有多个进程被wait时,唤醒优先级高的线程
* 3.notifyAll():唤醒所有被wait的进程
*
* @author shkstart
* @create 2022-11-04 下午 12:36
*/
public class CommunicationTest {
public static void main(String[] args) {
PrintNum test = new PrintNum();
Thread thread1 = new Thread(test);
Thread thread2 = new Thread(test);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
class PrintNum implements Runnable{
private int Num;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();//唤醒被wait的线程
if (Num <= 100){
System.out.println(Thread.currentThread().getName() + "打印:" + Num);
Num++;
try {
wait();//执行完打印后wait,同时释放同步监视器
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else
break;
}
}
}
}
关于notify、notifyAll、wait:
- 必须使用在同步代码块/同步方法中
- 上述三个方法的调用者必须是同步代码块/同步方法的同步监视器
- 上述三个方法定义在java.lang.Object中(因为同步监视器可以是任意一个类的对象,而调用这三个方法是通过同步监视器来调用)
5.JDK5.0新增线程的创建方式
实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author shkstart
* @create 2022-11-04 下午 9:58
*/
//1.创建实现Callable的实现类
class NumThread implements Callable{
//2.重写call方法,里面包含线程的具体操作。call方法有返回值
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 0; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
//这里其实是一个自动装箱(将int转为Integer,Object的子类)
return sum;//不关心返回值可以return null
}
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3.创建Callable接口实现类的对象
NumThread test = new NumThread();
//4.将3中的实现类对象作为参数,传入FutureTask的构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(test);
//5.将4中FutureTask对象作为参数,传入Thread构造器中,创建Thread类的对象,并执行start()
new Thread(futureTask).start();
/*
6.如果希望获得实现Callable的返回值:
get()返回值是FutureTask构造器中实现Callable接口的实现类(这里是test所属NumThread类)中重写的call方法的返回值
*/
Object sum = futureTask.get();
System.out.println(sum);
}
}
Callable比Runnable更强大:
- Callable有返回值
- Callable能抛出异常
- 支持泛型
线程池
开发中大部分情况都是使用线程池,有如下有点:
-
提高响应速度(减少创建线程的时间)
-
降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
-
便于线程管理,有如下参数:
corePoolSize:核心池的大小
MaximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间会终止
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author shkstart
* @create 2022-11-04 下午 10:35
*/
class NumPrint implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumPrint2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumPrint3 implements Callable {
@Override
public Object call() {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return sum;
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//ExecutorService是接口,其实现类为:ThreadPoolExecutor
ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
/*设置一些线程池的属性
service1.setCorePoolSize(15);
service1.setKeepAliveTime();
*/
//2.执行指定线程的操作
//service.submit(Callable callable);//适用于Callable
service.execute(new NumPrint());//适用于Runnable
//可以再造另一个线程
service.execute(new NumPrint2());
//3.关闭线程池
service.shutdown();
}
}